|
6 | 6 | import pytest |
7 | 7 | import yaml |
8 | 8 |
|
9 | | -from dialogchain import config |
10 | | - |
11 | | - |
12 | | -def test_load_config_from_file(tmp_path): |
13 | | - """Test loading configuration from a file.""" |
14 | | - # Create a sample config file |
15 | | - config_data = {"version": "1.0", "name": "test_config", "settings": {"debug": True}} |
16 | | - config_path = tmp_path / "config.yaml" |
17 | | - with open(config_path, "w") as f: |
18 | | - yaml.dump(config_data, f) |
19 | | - |
20 | | - # Test loading the config |
21 | | - loaded = config.load_config(str(config_path)) |
22 | | - assert loaded == config_data |
23 | | - |
24 | | - |
25 | | -def test_load_config_from_dict(): |
26 | | - """Test loading configuration from a dictionary.""" |
27 | | - config_data = {"version": "1.0", "name": "test_config"} |
28 | | - assert config.load_config(config_data) == config_data |
29 | | - |
30 | | - |
31 | | -def test_load_config_invalid(): |
32 | | - """Test loading invalid configuration.""" |
33 | | - with pytest.raises(ValueError): |
34 | | - config.load_config(123) # type: ignore |
35 | | - |
36 | | - |
37 | | -@patch("os.path.exists", return_value=True) |
38 | | -@patch("builtins.open", new_callable=mock_open, read_data="invalid: yaml") |
39 | | -def test_load_config_invalid_yaml(mock_file, mock_exists): |
40 | | - """Test loading invalid YAML from file.""" |
41 | | - with pytest.raises(yaml.YAMLError): |
42 | | - config.load_config("dummy_path.yaml") |
43 | | - |
44 | | - |
45 | | -def test_get_config_value(sample_config): |
46 | | - """Test getting a value from configuration.""" |
47 | | - value = config.get_config_value(sample_config, ["connectors", "http", "timeout"]) |
48 | | - assert value == 30 |
49 | | - |
50 | | - |
51 | | -def test_get_config_value_default(sample_config): |
52 | | - """Test getting a default value from configuration.""" |
53 | | - value = config.get_config_value(sample_config, ["nonexistent", "key"], default=42) |
54 | | - assert value == 42 |
55 | | - |
56 | | - |
57 | | -def test_get_config_value_required(sample_config): |
58 | | - """Test getting a required value that doesn't exist.""" |
59 | | - with pytest.raises(KeyError): |
60 | | - config.get_config_value(sample_config, ["nonexistent", "key"], required=True) |
61 | | - |
62 | | - |
63 | | -def test_validate_config_valid(sample_config): |
64 | | - """Test validating a valid configuration.""" |
65 | | - assert config.validate_config(sample_config) is None |
66 | | - |
67 | | - |
68 | | -def test_validate_config_invalid(): |
69 | | - """Test validating an invalid configuration.""" |
70 | | - with pytest.raises(ValueError): |
71 | | - config.validate_config({"invalid": "config"}) |
72 | | - |
73 | | - |
74 | | -@patch.dict(os.environ, {"DIALOGCHAIN_DEBUG": "true"}) |
75 | | -def test_get_env_bool(): |
76 | | - """Test getting a boolean value from environment.""" |
77 | | - assert config.get_env_bool("DIALOGCHAIN_DEBUG") is True |
78 | | - assert config.get_env_bool("NON_EXISTENT", False) is False |
79 | | - |
80 | | - |
81 | | -def test_get_env_int(): |
82 | | - """Test getting an integer value from environment.""" |
83 | | - with patch.dict(os.environ, {"DIALOGCHAIN_TIMEOUT": "30"}): |
84 | | - assert config.get_env_int("DIALOGCHAIN_TIMEOUT", 10) == 30 |
85 | | - assert config.get_env_int("NON_EXISTENT", 42) == 42 |
86 | | - |
87 | | - |
88 | | -def test_merge_configs(): |
89 | | - """Test merging two configurations.""" |
90 | | - base = {"a": 1, "b": {"x": 1, "y": 2}} |
91 | | - override = {"b": {"y": 3, "z": 4}, "c": 5} |
92 | | - expected = {"a": 1, "b": {"x": 1, "y": 3, "z": 4}, "c": 5} |
93 | | - assert config.merge_configs(base, override) == expected |
| 9 | +from dialogchain.config import RouteConfig, ConfigResolver, ConfigValidator |
| 10 | +from dialogchain.exceptions import ValidationError, ConfigurationError |
| 11 | + |
| 12 | + |
| 13 | +def test_route_config_validation(): |
| 14 | + """Test route configuration validation.""" |
| 15 | + # Valid config |
| 16 | + valid_config = { |
| 17 | + "routes": [ |
| 18 | + { |
| 19 | + "name": "test-route", |
| 20 | + "from": "rtsp://camera1", |
| 21 | + "to": "http://api.example.com" |
| 22 | + } |
| 23 | + ] |
| 24 | + } |
| 25 | + route_config = RouteConfig(valid_config) |
| 26 | + assert route_config.data == valid_config |
| 27 | + |
| 28 | + # Invalid config - missing required fields |
| 29 | + invalid_config = {"routes": [{"name": "invalid-route"}]} |
| 30 | + with pytest.raises(ValidationError) as excinfo: |
| 31 | + RouteConfig(invalid_config) |
| 32 | + assert "Missing 'from' field" in str(excinfo.value) |
| 33 | + |
| 34 | + |
| 35 | +def test_route_config_loading(): |
| 36 | + """Test loading route configuration from a file.""" |
| 37 | + # This is a simplified test - in a real scenario, we'd mock the file I/O |
| 38 | + config_data = { |
| 39 | + "routes": [ |
| 40 | + { |
| 41 | + "name": "test-route", |
| 42 | + "from": "rtsp://camera1", |
| 43 | + "to": "http://api.example.com" |
| 44 | + } |
| 45 | + ] |
| 46 | + } |
| 47 | + route_config = RouteConfig(config_data) |
| 48 | + assert route_config.data == config_data |
| 49 | + |
| 50 | + |
| 51 | +def test_resolve_env_vars(): |
| 52 | + """Test resolving environment variables in configuration.""" |
| 53 | + resolver = ConfigResolver() |
| 54 | + |
| 55 | + # Test with no env vars |
| 56 | + assert resolver.resolve_env_vars("test") == "test" |
| 57 | + |
| 58 | + # Test with env var - using Jinja2 syntax |
| 59 | + with patch.dict(os.environ, {"TEST_VAR": "test_value"}): |
| 60 | + assert resolver.resolve_env_vars("prefix_{{ TEST_VAR }}_suffix") == "prefix_test_value_suffix" |
| 61 | + |
| 62 | + # Test with missing var - Jinja2 will leave the variable as is |
| 63 | + with patch.dict(os.environ, {}, clear=True): |
| 64 | + result = resolver.resolve_env_vars("prefix_{{ MISSING_VAR }}_suffix") |
| 65 | + assert result == "prefix__suffix" # Jinja2 leaves undefined variables as empty strings |
| 66 | + |
| 67 | + |
| 68 | +def test_check_required_env_vars(): |
| 69 | + """Test checking for required environment variables.""" |
| 70 | + resolver = ConfigResolver() |
| 71 | + |
| 72 | + # All vars present |
| 73 | + with patch.dict(os.environ, {"VAR1": "value1", "VAR2": "value2"}): |
| 74 | + missing = resolver.check_required_env_vars(["VAR1", "VAR2"]) |
| 75 | + assert missing == [] |
| 76 | + |
| 77 | + # Missing vars |
| 78 | + with patch.dict(os.environ, {}, clear=True): |
| 79 | + missing = resolver.check_required_env_vars(["MISSING_VAR"]) |
| 80 | + assert missing == ["MISSING_VAR"] |
| 81 | + |
| 82 | + |
| 83 | +def test_validate_uri(): |
| 84 | + """Test URI validation.""" |
| 85 | + validator = ConfigValidator() |
| 86 | + |
| 87 | + # Valid URIs |
| 88 | + assert validator.validate_uri("rtsp://camera1", "sources") == [] |
| 89 | + assert validator.validate_uri("http://example.com", "destinations") == [] |
| 90 | + |
| 91 | + # Invalid scheme |
| 92 | + errors = validator.validate_uri("invalid://test", "sources") |
| 93 | + assert any("Unsupported scheme 'invalid'" in e for e in errors) |
| 94 | + |
| 95 | + # Missing netloc (should be allowed for certain schemes like 'file' and 'log') |
| 96 | + errors = validator.validate_uri("file:///path/to/file", "sources") |
| 97 | + assert not errors # Should be valid for file scheme |
| 98 | + |
| 99 | + # Missing netloc for http should be invalid |
| 100 | + errors = validator.validate_uri("http://", "sources") |
| 101 | + assert any("Missing host/netloc in URI" in e for e in errors) |
| 102 | + |
| 103 | + |
| 104 | +def test_validate_processor(): |
| 105 | + """Test processor configuration validation.""" |
| 106 | + validator = ConfigValidator() |
| 107 | + |
| 108 | + # Valid processor |
| 109 | + valid_processor = {"type": "external", "command": "python script.py"} |
| 110 | + assert validator.validate_processor(valid_processor) == [] |
| 111 | + |
| 112 | + # Missing type |
| 113 | + errors = validator.validate_processor({"name": "test"}) |
| 114 | + assert any("Unsupported processor type 'None'" in e for e in errors) |
| 115 | + |
| 116 | + # Invalid type |
| 117 | + errors = validator.validate_processor({"type": "invalid"}) |
| 118 | + assert any("Unsupported processor type 'invalid'" in e for e in errors) |
| 119 | + |
| 120 | + # Missing command for external processor |
| 121 | + errors = validator.validate_processor({"type": "external"}) |
| 122 | + assert any("External processor requires 'command' field" in e for e in errors) |
| 123 | + |
| 124 | + # Test filter processor validation |
| 125 | + errors = validator.validate_processor({"type": "filter"}) |
| 126 | + assert any("Filter processor requires 'condition' field" in e for e in errors) |
| 127 | + |
| 128 | + # Test transform processor validation |
| 129 | + errors = validator.validate_processor({"type": "transform"}) |
| 130 | + assert any("Transform processor requires 'template' field" in e for e in errors) |
0 commit comments