Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 18 additions & 29 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from meshcore_api.database.models import Base
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Base' is not used.

Suggested change
from meshcore_api.database.models import Base

Copilot uses AI. Check for mistakes.
from meshcore_api.meshcore.mock import MockMeshCore
from meshcore_api.queue.manager import CommandQueueManager
from meshcore_api.queue.models import QueueFullBehavior
from meshcore_api.subscriber.event_handler import EventHandler
from meshcore_api.webhook.handler import WebhookHandler

Expand Down Expand Up @@ -74,7 +75,7 @@ def test_config(temp_db_path: str) -> Config:
webhook_advertisement_jsonpath="$",
# Queue
queue_max_size=100,
queue_full_behavior="reject",
queue_full_behavior=QueueFullBehavior.REJECT,
# Rate limiting (disabled for fast tests)
rate_limit_enabled=False,
rate_limit_per_second=10.0,
Expand All @@ -100,7 +101,7 @@ def test_config_with_auth(test_config: Config) -> Config:
def db_engine(test_config: Config) -> Generator[DatabaseEngine, None, None]:
"""Create a database engine for testing."""
engine = DatabaseEngine(test_config.db_path)
engine.init_db()
engine.initialize()
yield engine
engine.close()

Expand Down Expand Up @@ -135,7 +136,15 @@ async def queue_manager(
"""Create a CommandQueueManager for testing."""
manager = CommandQueueManager(
meshcore=mock_meshcore,
config=test_config,
max_queue_size=test_config.queue_max_size,
queue_full_behavior=test_config.queue_full_behavior,
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: test_config.queue_full_behavior is a string (from Config dataclass line 59), but CommandQueueManager.__init__ expects a QueueFullBehavior enum. This will cause a type error at runtime.

The config value should be converted to the enum, e.g.:

queue_full_behavior=QueueFullBehavior(test_config.queue_full_behavior),

This assumes the string values in Config match the enum values ("reject" or "drop_oldest").

Suggested change
queue_full_behavior=test_config.queue_full_behavior,
queue_full_behavior=QueueFullBehavior(test_config.queue_full_behavior),

Copilot uses AI. Check for mistakes.
rate_limit_per_second=test_config.rate_limit_per_second,
rate_limit_burst=test_config.rate_limit_burst,
rate_limit_enabled=test_config.rate_limit_enabled,
debounce_window_seconds=test_config.debounce_window_seconds,
debounce_cache_max_size=test_config.debounce_cache_max_size,
debounce_enabled=test_config.debounce_enabled,
debounce_commands=set(test_config.debounce_commands.split(",")),
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type mismatch: CommandQueueManager.__init__ expects debounce_commands to be Optional[set[CommandType]], but this code passes a set of strings. The string values need to be converted to CommandType enum values.

Example fix:

debounce_commands={CommandType(cmd.strip()) for cmd in test_config.debounce_commands.split(",")},

This assumes the string values in Config match CommandType enum values ("send_message", "send_channel_message", etc.).

Copilot uses AI. Check for mistakes.
)
await manager.start()
yield manager
Expand Down Expand Up @@ -169,36 +178,16 @@ async def event_handler(


@pytest.fixture(scope="function")
def test_app(
test_config: Config,
db_engine: DatabaseEngine,
mock_meshcore: MockMeshCore,
queue_manager: CommandQueueManager,
) -> TestClient:
"""Create a FastAPI test client."""
app = create_app(
config=test_config,
db_engine=db_engine,
meshcore=mock_meshcore,
queue_manager=queue_manager,
)
def test_app() -> TestClient:
"""Create a simple FastAPI test client without dependencies."""
app = create_app()
return TestClient(app)


@pytest.fixture(scope="function")
def test_app_with_auth(
test_config_with_auth: Config,
db_engine: DatabaseEngine,
mock_meshcore: MockMeshCore,
queue_manager: CommandQueueManager,
) -> TestClient:
"""Create a FastAPI test client with authentication."""
app = create_app(
config=test_config_with_auth,
db_engine=db_engine,
meshcore=mock_meshcore,
queue_manager=queue_manager,
)
def test_app_with_auth() -> TestClient:
"""Create a simple FastAPI test client with authentication."""
app = create_app(bearer_token="test-token-12345")
return TestClient(app)


Expand Down
195 changes: 195 additions & 0 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""Unit tests for configuration module - fixed to match actual implementation."""

import pytest
Copy link

Copilot AI Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'pytest' is not used.

Suggested change
import pytest

Copilot uses AI. Check for mistakes.

from meshcore_api.config import Config


class TestConfig:
"""Test configuration parsing and validation."""

def test_default_config_values(self):
"""Test default configuration values."""
config = Config()

# Connection defaults
assert config.serial_port == "/dev/ttyUSB0"
assert config.serial_baud == 115200
assert config.use_mock is False
assert config.mock_scenario is None
assert config.mock_loop is False
assert config.mock_nodes == 10
assert config.mock_min_interval == 1.0
assert config.mock_max_interval == 10.0
assert config.mock_center_lat == 45.5231
assert config.mock_center_lon == -122.6765

# Database defaults
assert config.db_path == "./data/meshcore.db"
assert config.retention_days == 30
assert config.cleanup_interval_hours == 1

# API defaults
assert config.api_host == "0.0.0.0"
assert config.api_port == 8000
assert config.api_title == "MeshCore API"
assert config.api_version == "1.0.0"
assert config.api_bearer_token is None

# Other defaults
assert config.metrics_enabled is True
assert config.enable_write is True
assert config.log_level == "INFO"
assert config.log_format == "json"

# Webhook defaults
assert config.webhook_message_direct is None
assert config.webhook_message_channel is None
assert config.webhook_advertisement is None

def test_config_custom_values(self):
"""Test configuration with custom values."""
config = Config(
serial_port="/dev/ttyACM0",
use_mock=True,
mock_nodes=20,
api_port=9000,
db_path="/tmp/test.db",
log_level="DEBUG"
)

assert config.serial_port == "/dev/ttyACM0"
assert config.use_mock is True
assert config.mock_nodes == 20
assert config.api_port == 9000
assert config.db_path == "/tmp/test.db"
assert config.log_level == "DEBUG"

def test_config_webhook_configuration(self):
"""Test webhook configuration."""
webhook_url = "http://localhost:9000/webhook"
config = Config(
webhook_message_direct=webhook_url,
webhook_message_channel=webhook_url,
webhook_advertisement=webhook_url
)

assert config.webhook_message_direct == webhook_url
assert config.webhook_message_channel == webhook_url
assert config.webhook_advertisement == webhook_url

def test_config_mock_settings(self):
"""Test mock configuration settings."""
config = Config(
mock_scenario="test_scenario",
mock_loop=True,
mock_nodes=5,
mock_min_interval=0.5,
mock_max_interval=2.0,
mock_center_lat=40.7128,
mock_center_lon=-74.0060
)

assert config.mock_scenario == "test_scenario"
assert config.mock_loop is True
assert config.mock_nodes == 5
assert config.mock_min_interval == 0.5
assert config.mock_max_interval == 2.0
assert config.mock_center_lat == 40.7128
assert config.mock_center_lon == -74.0060

def test_config_database_settings(self):
"""Test database configuration settings."""
config = Config(
db_path="/custom/path/database.db",
retention_days=90,
cleanup_interval_hours=24
)

assert config.db_path == "/custom/path/database.db"
assert config.retention_days == 90
assert config.cleanup_interval_hours == 24

def test_config_api_settings(self):
"""Test API configuration settings."""
config = Config(
api_host="127.0.0.1",
api_port=8080,
api_title="Custom API",
api_version="2.0.0",
api_bearer_token="secret-token"
)

assert config.api_host == "127.0.0.1"
assert config.api_port == 8080
assert config.api_title == "Custom API"
assert config.api_version == "2.0.0"
assert config.api_bearer_token == "secret-token"

def test_config_other_settings(self):
"""Test other configuration settings."""
config = Config(
metrics_enabled=False,
enable_write=False,
log_level="ERROR",
log_format="text"
)

assert config.metrics_enabled is False
assert config.enable_write is False
assert config.log_level == "ERROR"
assert config.log_format == "text"

def test_config_dataclass_behavior(self):
"""Test that Config behaves as a dataclass."""
config = Config(api_port=9000)

# Test equality
config2 = Config(api_port=9000)
assert config == config2

# Test inequality
config3 = Config(api_port=8000)
assert config != config3

# Test string representation
config_str = str(config)
assert "Config" in config_str
assert "api_port=9000" in config_str

def test_config_optional_fields(self):
"""Test optional field handling."""
config = Config()

# None values for optional fields
assert config.mock_scenario is None
assert config.api_bearer_token is None
assert config.webhook_message_direct is None
assert config.webhook_message_channel is None
assert config.webhook_advertisement is None

def test_config_type_hints(self):
"""Test that config values have correct types."""
config = Config()

# Type assertions
assert isinstance(config.serial_port, str)
assert isinstance(config.serial_baud, int)
assert isinstance(config.use_mock, bool)
assert isinstance(config.mock_nodes, int)
assert isinstance(config.mock_min_interval, float)
assert isinstance(config.mock_max_interval, float)
assert isinstance(config.db_path, str)
assert isinstance(config.retention_days, int)
assert isinstance(config.api_port, int)
assert isinstance(config.log_level, str)

def test_config_immutability_of_defaults(self):
"""Test that default values are appropriate."""
config1 = Config()
config2 = Config()

# Ensure default values are consistent
assert config1.serial_port == config2.serial_port
assert config1.api_port == config2.api_port
assert config1.use_mock == config2.use_mock
Loading
Loading