1
+ """Test suite for the ConfigManager class.
2
+
3
+ This refactored version removes duplicated configuration stubs by introducing a
4
+ single reusable ``base_config`` fixture. Where individual tests need to tweak
5
+ values, they operate on a ``deepcopy`` so that the canonical fixture remains
6
+ pristine for all subsequent tests.
1
7
"""
2
- Test suite for the ConfigManager class.
3
- """
8
+ from __future__ import annotations
9
+
10
+ from copy import deepcopy
4
11
from pathlib import Path
5
12
from unittest import mock
6
13
10
17
from src .utils .config_manager import ConfigManager
11
18
12
19
13
- @pytest .fixture
14
- def mock_config_data (dummy_credentials ):
15
- """Fixture providing standard test configuration data."""
16
- rabbit_creds = dummy_credentials .get ("rabbitmq" , {})
20
+ @pytest .fixture (scope = "module" )
21
+ def base_config (dummy_credentials : dict ) -> dict :
22
+ """Return a fully‑populated configuration template shared by all tests."""
23
+
24
+ rabbit_creds : dict = dummy_credentials .get ("rabbitmq" , {})
17
25
return {
18
26
"agent" : {"agent_id" : "matlab" },
19
27
"rabbitmq" : {
20
28
"host" : "localhost" ,
21
29
"port" : 5672 ,
22
30
"username" : rabbit_creds .get ("username" , "guest" ),
23
31
"password" : rabbit_creds .get ("password" , "guest" ),
24
- "heartbeat" : 600
32
+ "heartbeat" : 600 ,
25
33
},
26
34
"exchanges" : {
27
35
"input" : "ex.bridge.output" ,
28
- "output" : "ex.sim.result"
36
+ "output" : "ex.sim.result" ,
29
37
},
30
38
"queue" : {
31
39
"durable" : True ,
32
- "prefetch_count" : 1
40
+ "prefetch_count" : 1 ,
33
41
},
34
42
"logging" : {
35
43
"level" : "INFO" ,
36
- "file" : "logs/matlab_agent.log"
44
+ "file" : "logs/matlab_agent.log" ,
37
45
},
38
46
"tcp" : {
39
47
"host" : "localhost" ,
40
- "port" : 5678
48
+ "port" : 5678 ,
41
49
},
42
50
"response_templates" : {
43
51
"success" : {
44
52
"status" : "success" ,
45
53
"simulation" : {"type" : "batch" },
46
54
"timestamp_format" : "%Y-%m-%dT%H:%M:%SZ" ,
47
55
"include_metadata" : True ,
48
- "metadata_fields" : ["execution_time" , "memory_usage" , "matlab_version" ]
56
+ "metadata_fields" : [
57
+ "execution_time" ,
58
+ "memory_usage" ,
59
+ "matlab_version" ,
60
+ ],
49
61
},
50
62
"error" : {
51
63
"status" : "error" ,
@@ -55,195 +67,97 @@ def mock_config_data(dummy_credentials):
55
67
"matlab_start_failure" : 500 ,
56
68
"execution_error" : 500 ,
57
69
"timeout" : 504 ,
58
- "missing_file" : 404
70
+ "missing_file" : 404 ,
59
71
},
60
- "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ"
72
+ "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ" ,
61
73
},
62
74
"progress" : {
63
75
"status" : "in_progress" ,
64
76
"include_percentage" : True ,
65
77
"update_interval" : 5 ,
66
- "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ"
67
- }
68
- }
78
+ "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ" ,
79
+ },
80
+ },
69
81
}
70
82
71
83
72
84
@pytest .fixture
73
- def mock_config_path ():
74
- """Fixture providing a standard mock config path."""
75
- return "/fake/path/config.yaml"
85
+ def config_path () -> Path :
86
+ """Fake configuration path used throughout the tests."""
76
87
88
+ return Path ("/fake/path/config.yaml" )
77
89
78
- @pytest .fixture
79
- def mock_load_config (mock_config_data ):
80
- """
81
- Fixture that patches the load_config function to return test configuration data.
82
90
83
- Args:
84
- mock_config_data: The test configuration data fixture
91
+ @pytest .fixture
92
+ def patched_load (base_config : dict ):
93
+ """Patch the ``load_config`` helper used by :class:`ConfigManager`."""
85
94
86
- Returns:
87
- The mocked load_config function
88
- """
89
- with mock .patch ("src.utils.config_manager.load_config" ) as mocked_load :
90
- mocked_load .return_value = mock_config_data
91
- yield mocked_load
95
+ with mock .patch (
96
+ "src.utils.config_manager.load_config" , return_value = deepcopy (base_config )
97
+ ) as mocked :
98
+ yield mocked
92
99
93
100
94
101
@pytest .fixture
95
- def mock_path_exists ():
96
- """Fixture that patches Path.exists to always return True."""
102
+ def patched_exists ():
103
+ """Pretend that any ``Path.exists`` call returns *True*."""
104
+
97
105
with mock .patch .object (Path , "exists" , return_value = True ):
98
106
yield
99
107
100
108
101
109
@pytest .fixture
102
- def config_manager (mock_config_path , mock_load_config , mock_path_exists ):
103
- """
104
- Fixture that creates a pre-configured ConfigManager instance.
105
-
106
- Args:
107
- mock_config_path: The mock config path fixture
108
- mock_load_config: The mocked load_config function fixture
109
- mock_path_exists: The mocked Path.exists fixture
110
-
111
- Returns:
112
- A ConfigManager instance initialized with the test configuration
113
- """
114
- return ConfigManager (mock_config_path )
115
-
116
-
117
- def test_config_manager_initialization (dummy_credentials ):
118
- """Test that ConfigManager initializes correctly with the provided configuration."""
119
- rabbit_creds = dummy_credentials .get ("rabbitmq" , {})
120
- config_path = "/fake/path/config.yaml"
121
- test_config_data = {
122
- "agent" : {"agent_id" : "matlab" },
123
- "rabbitmq" : {
124
- "host" : "localhost" ,
125
- "port" : 5672 ,
126
- "username" : rabbit_creds .get ("username" , "guest" ),
127
- "password" : rabbit_creds .get ("password" , "guest" ),
128
- "heartbeat" : 600
129
- },
130
- "exchanges" : {
131
- "input" : "ex.bridge.output" ,
132
- "output" : "ex.sim.result"
133
- },
134
- "queue" : {
135
- "durable" : True ,
136
- "prefetch_count" : 1
137
- },
138
- "logging" : {
139
- "level" : "INFO" ,
140
- "file" : "logs/matlab_agent.log"
141
- },
142
- "tcp" : {
143
- "host" : "localhost" ,
144
- "port" : 5678
145
- },
146
- "response_templates" : {
147
- "success" : {
148
- "status" : "success" ,
149
- "simulation" : {"type" : "batch" },
150
- "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ" ,
151
- "include_metadata" : True ,
152
- "metadata_fields" : ["execution_time" , "memory_usage" , "matlab_version" ]
153
- },
154
- "error" : {
155
- "status" : "error" ,
156
- "include_stacktrace" : False ,
157
- "error_codes" : {
158
- "invalid_config" : 400 ,
159
- "matlab_start_failure" : 500 ,
160
- "execution_error" : 500 ,
161
- "timeout" : 504 ,
162
- "missing_file" : 404
163
- },
164
- "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ"
165
- },
166
- "progress" : {
167
- "status" : "in_progress" ,
168
- "include_percentage" : True ,
169
- "update_interval" : 5 ,
170
- "timestamp_format" : "%Y-%m-%dT%H:%M:%SZ"
171
- }
172
- }
173
- }
174
- with mock .patch ("src.utils.config_manager.load_config" ) as load_config_mock , \
175
- mock .patch .object (Path , "exists" , return_value = True ):
176
- load_config_mock .return_value = test_config_data
177
- manager = ConfigManager (config_path )
178
- load_config_mock .assert_called_once_with (Path (config_path ))
179
- assert manager .config ["agent" ]["agent_id" ] == "matlab"
180
- assert manager .config ["rabbitmq" ]["host" ] == "localhost"
110
+ def manager (config_path : Path , patched_load , patched_exists ):
111
+ """A :class:`ConfigManager` instance pre‑loaded with *base_config*."""
112
+
113
+ return ConfigManager (config_path )
114
+
115
+ def test_manager_initialization (manager : ConfigManager , patched_load , config_path ):
116
+ """The manager should forward *config_path* to ``load_config`` exactly once."""
117
+
118
+ patched_load .assert_called_once_with (Path (config_path ))
119
+ assert manager .config ["agent" ]["agent_id" ] == "matlab"
181
120
182
121
183
122
def test_get_default_config ():
184
- """Test that default configuration values are correct."""
185
- manager = ConfigManager ()
186
- default_config = manager .get_default_config ()
123
+ """The factory default configuration is stable and complete."""
187
124
188
- assert isinstance (default_config , dict )
189
- assert default_config ["agent" ]["agent_id" ] == "matlab"
190
- assert default_config ["rabbitmq" ]["port" ] == 5672
125
+ cm = ConfigManager ()
126
+ default_cfg = cm .get_default_config ()
191
127
128
+ assert default_cfg ["agent" ]["agent_id" ] == "matlab"
129
+ assert default_cfg ["rabbitmq" ]["port" ] == 5672
192
130
193
- def test_get_config ():
194
- """Test that get_config returns the correct configuration values."""
195
- config_path = "/fake/path/config.yaml"
196
- test_config_data = {
197
- "agent" : {"agent_id" : "matlab" },
198
- "rabbitmq" : {"host" : "localhost" , "port" : 5672 }
199
- }
200
- with mock .patch ("src.utils.config_manager.load_config" ) as load_config_mock , \
201
- mock .patch .object (Path , "exists" , return_value = True ):
202
- load_config_mock .return_value = test_config_data
203
- manager = ConfigManager (config_path )
204
- config = manager .get_config ()
205
131
206
- assert config [ "agent" ][ "agent_id" ] == "matlab"
207
- assert config [ "rabbitmq" ][ "host" ] == "localhost "
132
+ def test_get_config ( manager : ConfigManager ):
133
+ """``get_config`` should return the same data originally loaded."" "
208
134
135
+ assert manager .get_config ()["rabbitmq" ]["host" ] == "localhost"
209
136
210
- def test_validate_config_success (dummy_credentials ):
211
- """Test that config validation succeeds with correct data."""
212
- rabbit_creds = dummy_credentials .get ("rabbitmq" , {})
213
- test_config_data = {
214
- "agent" : {"agent_id" : "matlab" },
215
- "rabbitmq" : {
216
- "host" : "localhost" ,
217
- "port" : 5672 ,
218
- "username" : rabbit_creds .get ("username" , "guest" ),
219
- "password" : rabbit_creds .get ("password" , "guest" ),
220
- "heartbeat" : 600
221
- }
222
- }
223
- with mock .patch ("src.utils.config_manager.load_config" ) as load_config_mock , \
224
- mock .patch .object (Path , "exists" , return_value = True ):
225
- load_config_mock .return_value = test_config_data
226
- manager = ConfigManager ("/fake/path/config.yaml" )
227
137
228
- # Test the protected method directly
229
- validated_config = manager ._validate_config (
230
- test_config_data ) # pylint: disable=protected-access
231
- assert validated_config ["agent" ]["agent_id" ] == "matlab"
138
+ @pytest .mark .parametrize ("agent_id" , ["matlab" , "python_sim" ])
139
+ def test_validate_config_success (agent_id : str , base_config : dict ):
140
+ """Any valid configuration variant should pass model validation."""
141
+
142
+ cfg = deepcopy (base_config )
143
+ cfg ["agent" ]["agent_id" ] = agent_id
144
+
145
+ validated = ConfigManager ()._validate_config (cfg ) # pylint: disable=protected-access
146
+ assert validated ["agent" ]["agent_id" ] == agent_id
232
147
233
148
234
149
def test_validate_config_failure ():
235
- """Test that validation error is raised with invalid data."""
236
- manager = ConfigManager ()
237
- invalid_config = {"rabbitmq" : {"port" : "not_a_number" }}
150
+ """An invalid configuration must raise a :class:`ValidationError`."""
238
151
239
152
with pytest .raises (ValidationError ):
240
- manager ._validate_config (
241
- invalid_config ) # pylint: disable=protected-access
153
+ ConfigManager ()._validate_config ({"rabbitmq" : {"port" : "not_a_number" }})
154
+
155
+
156
+ def test_initialization_with_invalid_path (monkeypatch ):
157
+ """If the file is missing, the manager should fall back to defaults."""
242
158
159
+ monkeypatch .setattr (Path , "exists" , lambda * _ : False )
243
160
244
- def test_initialization_with_invalid_path ():
245
- """Test initialization when the configuration file does not exist."""
246
- with mock .patch .object (Path , "exists" , return_value = False ):
247
- manager = ConfigManager ("/invalid/path/config.yaml" )
161
+ cm = ConfigManager ("/invalid/path/config.yaml" )
248
162
249
- assert manager .config == manager .get_default_config ()
163
+ assert cm .config == cm .get_default_config ()
0 commit comments