Skip to content

Commit 63e84cb

Browse files
author
Tom Softreck
committed
update
1 parent 30a8cee commit 63e84cb

File tree

5 files changed

+520
-22
lines changed

5 files changed

+520
-22
lines changed

.coveragerc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[run]
2+
source = src/dialogchain
3+
branch = true
4+
omit =
5+
**/tests/*
6+
**/__init__.py
7+
**/version.py
8+
9+
[report]
10+
exclude_lines =
11+
pragma: no cover
12+
def __repr__
13+
if self.debug:
14+
raise NotImplementedError
15+
if 0:
16+
if __name__ == .__main__.:
17+
raise ImportError
18+
except ImportError
19+
20+
[html]
21+
directory = htmlcov
22+
23+
[xml]
24+
output = coverage.xml

pytest.ini

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ python_functions = test_*
66
asyncio_mode = auto
77

88
# Test execution options
9-
addopts = -v --strict-markers --disable-warnings --durations=10 --cov=src/dialogchain --cov-report=term-missing --cov-report=xml:coverage.xml -p no:warnings
9+
addopts = -v --strict-markers --disable-warnings --durations=10 -p no:warnings
1010

1111
# Markers for test categorization
1212
markers =
@@ -21,28 +21,9 @@ log_cli_level = INFO
2121
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
2222
log_cli_date_format = %Y-%m-%d %H:%M:%S
2323

24-
# Coverage configuration
24+
# Test configuration
2525
[tool:pytest]
2626
testpaths = tests
2727
python_files = test_*.py
2828
python_classes = Test*
29-
python_functions = test_*
30-
31-
[coverage:run]
32-
source = src
33-
branch = true
34-
omit =
35-
**/tests/*
36-
**/__init__.py
37-
**/version.py
38-
39-
[coverage:report]
40-
exclude_lines =
41-
pragma: no cover
42-
def __repr__
43-
if self.debug:
44-
raise NotImplementedError
45-
if 0:
46-
if __name__ == .__main__.:
47-
raise ImportError
48-
except ImportError
29+
python_functions = test_*

tests/unit/test_config_module.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""Unit tests for the config module."""
2+
import os
3+
import pytest
4+
import yaml
5+
from pathlib import Path
6+
from unittest.mock import patch, mock_open
7+
8+
from dialogchain.config import (
9+
RouteConfig,
10+
ConfigResolver,
11+
ConfigValidator
12+
)
13+
14+
15+
class TestRouteConfig:
16+
"""Test the RouteConfig class."""
17+
18+
@pytest.fixture
19+
def sample_config(self):
20+
"""Return a sample route configuration."""
21+
return {
22+
"routes": [
23+
{
24+
"name": "test_route",
25+
"from": "rtsp://camera1",
26+
"to": "http://api.example.com/webhook",
27+
"processors": [
28+
{
29+
"type": "filter",
30+
"config": {"min_confidence": 0.5}
31+
}
32+
]
33+
}
34+
]
35+
}
36+
37+
def test_route_config_validation(self, sample_config):
38+
"""Test route configuration validation."""
39+
# Test valid config
40+
config = RouteConfig(sample_config)
41+
assert isinstance(config.data, dict)
42+
assert "routes" in config.data
43+
44+
# Test missing required fields
45+
invalid_config = {"routes": [{"name": "invalid"}]}
46+
with pytest.raises(ValueError) as exc_info:
47+
RouteConfig(invalid_config)
48+
assert "Missing 'from' field" in str(exc_info.value)
49+
50+
def test_route_config_loading(self, sample_config, tmp_path):
51+
"""Test loading route config from a YAML file."""
52+
# Create a temporary YAML file
53+
config_file = tmp_path / "config.yaml"
54+
config_file.write_text(yaml.dump(sample_config))
55+
56+
# Load the config from file
57+
with open(config_file) as f:
58+
loaded_config = yaml.safe_load(f)
59+
60+
config = RouteConfig(loaded_config)
61+
assert len(config.data["routes"]) == 1
62+
assert config.data["routes"][0]["name"] == "test_route"
63+
64+
65+
class TestConfigResolver:
66+
"""Test the ConfigResolver class."""
67+
68+
def test_resolve_env_vars(self):
69+
"""Test environment variable resolution."""
70+
resolver = ConfigResolver()
71+
env_vars = {
72+
"DB_HOST": "localhost",
73+
"DB_PORT": "5432"
74+
}
75+
76+
# Test with direct variable
77+
result = resolver.resolve_env_vars(
78+
"postgresql://${DB_HOST}:${DB_PORT}/mydb",
79+
env_vars
80+
)
81+
assert result == "postgresql://localhost:5432/mydb"
82+
83+
# Test with default value
84+
result = resolver.resolve_env_vars(
85+
"postgresql://${DB_HOST:127.0.0.1}:${DB_PORT:5432}/mydb",
86+
{}
87+
)
88+
assert result == "postgresql://127.0.0.1:5432/mydb"
89+
90+
def test_check_required_env_vars(self):
91+
"""Test required environment variable validation."""
92+
resolver = ConfigResolver()
93+
94+
# Test with all required vars present
95+
env_vars = {
96+
"REQUIRED_VAR_1": "value1",
97+
"REQUIRED_VAR_2": "value2"
98+
}
99+
resolver.check_required_env_vars(["REQUIRED_VAR_1", "REQUIRED_VAR_2"], env_vars)
100+
101+
# Test with missing required var
102+
with pytest.raises(ValueError) as exc_info:
103+
resolver.check_required_env_vars(
104+
["REQUIRED_VAR_1", "MISSING_VAR"],
105+
env_vars
106+
)
107+
assert "Missing required environment variable: MISSING_VAR" in str(exc_info.value)
108+
109+
110+
class TestConfigValidator:
111+
"""Test the ConfigValidator class."""
112+
113+
def test_validate_uri(self):
114+
"""Test URI validation."""
115+
# Test valid RTSP URI
116+
assert ConfigValidator.validate_uri("rtsp://camera1:554/stream", "sources") is None
117+
118+
# Test invalid scheme
119+
with pytest.raises(ValueError) as exc_info:
120+
ConfigValidator.validate_uri("invalid://test", "sources")
121+
assert "Unsupported scheme 'invalid' for source" in str(exc_info.value)
122+
123+
# Test invalid destination
124+
with pytest.raises(ValueError) as exc_info:
125+
ConfigValidator.validate_uri("rtsp://test", "destinations")
126+
assert "Unsupported scheme 'rtsp' for destination" in str(exc_info.value)
127+
128+
def test_validate_processor(self):
129+
"""Test processor configuration validation."""
130+
# Test valid processor
131+
valid_processor = {
132+
"type": "filter",
133+
"config": {"min_confidence": 0.5}
134+
}
135+
assert ConfigValidator.validate_processor(valid_processor) is None
136+
137+
# Test missing type
138+
with pytest.raises(ValueError) as exc_info:
139+
ConfigValidator.validate_processor({"config": {}})
140+
assert "Processor config missing 'type' field" in str(exc_info.value)
141+
142+
# Test invalid type
143+
with pytest.raises(ValueError) as exc_info:
144+
ConfigValidator.validate_processor({"type": "invalid"})
145+
assert "Unsupported processor type: invalid" in str(exc_info.value)
146+
147+
# Test missing config
148+
with pytest.raises(ValueError) as exc_info:
149+
ConfigValidator.validate_processor({"type": "filter"})
150+
assert "Processor config missing 'config' field" in str(exc_info.value)
151+
152+
153+
def test_config_loading_from_file(tmp_path):
154+
"""Test loading configuration from a file."""
155+
# Create a temporary config file
156+
config_data = {
157+
"routes": [
158+
{
159+
"name": "test_route",
160+
"from": "rtsp://camera1",
161+
"to": "http://api.example.com/webhook",
162+
"processors": [{"type": "filter"}]
163+
}
164+
]
165+
}
166+
config_file = tmp_path / "config.yaml"
167+
config_file.write_text(yaml.dump(config_data))
168+
169+
# Test loading the config
170+
with open(config_file) as f:
171+
loaded_config = yaml.safe_load(f)
172+
173+
config = RouteConfig(loaded_config)
174+
assert len(config.data["routes"]) == 1
175+
assert config.data["routes"][0]["name"] == "test_route"

0 commit comments

Comments
 (0)