88import docbuild .cli .cmd_cli as cli_mod
99from docbuild .cli .context import DocBuildContext
1010from docbuild .models .config_model .app import AppConfig
11- from docbuild .models .config_model .env import EnvConfig
11+ from docbuild .models .config_model .env import EnvConfig
1212
1313cli = cli_mod .cli
1414
@@ -25,14 +25,12 @@ def capture(ctx):
2525
2626@pytest .fixture
2727def mock_config_models (monkeypatch ):
28- """
29- Fixture to mock AppConfig.from_dict and EnvConfig.from_dict.
30-
28+ """Fixture to mock AppConfig.from_dict and EnvConfig.from_dict.
29+
3130 Ensures the mock AppConfig instance has the necessary
3231 logging attributes and methods (.logging.model_dump) that the CLI calls
3332 during setup_logging.
3433 """
35-
3634 # Mock the nested logging attribute and its model_dump method
3735 mock_logging_dump = Mock (return_value = {'version' : 1 , 'log_setup' : True })
3836 mock_logging_attribute = Mock ()
@@ -42,21 +40,26 @@ def mock_config_models(monkeypatch):
4240 mock_app_instance = Mock (spec = AppConfig )
4341 # Assign the mock logging attribute to the app instance
4442 mock_app_instance .logging = mock_logging_attribute
45-
43+
4644 # Env config mock doesn't need logging setup
45+ # The CLI calls .model_dump() on the instance, so we mock that call.
46+ mock_env_dump = Mock (return_value = {'env_data' : 'from_mock_dump' })
4747 mock_env_instance = Mock (spec = EnvConfig )
48-
48+ mock_env_instance .model_dump .return_value = mock_env_dump
49+
4950 # Mock the static methods that perform validation
5051 mock_app_from_dict = Mock (return_value = mock_app_instance )
5152 mock_env_from_dict = Mock (return_value = mock_env_instance )
52-
53+
54+
5355 # Patch the actual classes
5456 monkeypatch .setattr (AppConfig , 'from_dict' , mock_app_from_dict )
5557 monkeypatch .setattr (EnvConfig , 'from_dict' , mock_env_from_dict )
56-
58+
5759 return {
5860 'app_instance' : mock_app_instance ,
5961 'env_instance' : mock_env_instance ,
62+ 'env_dump' : mock_env_dump ,
6063 'app_from_dict' : mock_app_from_dict ,
6164 'env_from_dict' : mock_env_from_dict ,
6265 }
@@ -79,26 +82,29 @@ def fake_handle_config(user_path, *a, **kw):
7982 return (Path ('default_env.toml' ),), {'env_data' : 'from_default' }, True
8083
8184 monkeypatch .setattr (cli_mod , 'handle_config' , fake_handle_config )
82-
85+
8386 context = DocBuildContext ()
84-
87+
8588 result = runner .invoke (
86- cli ,
89+ cli ,
8790 ['--app-config' , str (app_file ), 'capture' ],
8891 obj = context ,
8992 catch_exceptions = False
9093 )
91-
94+
9295 assert result .exit_code == 0
9396 assert 'capture' in result .output .strip ()
94-
97+
9598 # Assert that the raw data was passed to the Pydantic models
9699 mock_config_models ['app_from_dict' ].assert_called_once ()
97100 mock_config_models ['env_from_dict' ].assert_called_once ()
98-
99- # Assert that the context now holds the MOCKED VALIDATED OBJECTS
101+
102+ # Assert that the context holds the app's Pydantic model instance
100103 assert context .appconfig is mock_config_models ['app_instance' ]
101- assert context .envconfig is mock_config_models ['env_instance' ]
104+
105+ # Assert that the context holds the DICTIONARY from model_dump()
106+ # for the environment config.
107+ assert context .envconfig is mock_config_models ['env_dump' ]
102108 assert context .envconfig_from_defaults is True
103109
104110
@@ -107,15 +113,15 @@ def test_cli_with_app_and_env_config(monkeypatch, runner, tmp_path, mock_config_
107113 # Create real temporary files for Click to validate
108114 app_file = tmp_path / 'app.toml'
109115 env_file = tmp_path / 'env.toml'
110- app_file .write_text ('[logging]\n version=1' )
116+ app_file .write_text ('[logging]\n version=1' )
111117 env_file .write_text ('dummy = true' )
112118
113119 def fake_handle_config (user_path , * a , ** kw ):
114120 if str (user_path ) == str (app_file ):
115121 return (app_file ,), {'logging' : {'version' : 1 }}, False
116122 if str (user_path ) == str (env_file ):
117123 return (env_file ,), {'server' : {'host' : '1.2.3.4' }}, False
118- return (None ,), {'default_data' : 'default_content' }, True
124+ return (None ,), {'default_data' : 'default_content' }, True
119125
120126 monkeypatch .setattr (cli_mod , 'handle_config' , fake_handle_config )
121127
@@ -130,9 +136,9 @@ def fake_handle_config(user_path, *a, **kw):
130136 'capture' ,
131137 ],
132138 obj = context ,
133- catch_exceptions = False ,
139+ catch_exceptions = False ,
134140 )
135-
141+
136142 # Check for success and context variables
137143 assert result .exit_code == 0
138144
@@ -141,7 +147,7 @@ def fake_handle_config(user_path, *a, **kw):
141147
142148 # Assert that the context now holds the MOCKED VALIDATED OBJECTS
143149 assert context .appconfig is mock_config_models ['app_instance' ]
144- assert context .envconfig is mock_config_models ['env_instance ' ]
150+ assert context .envconfig is mock_config_models ['env_dump ' ]
145151 assert context .envconfigfiles == (env_file ,)
146152 assert context .envconfig_from_defaults is False
147153
@@ -151,44 +157,44 @@ def test_cli_config_validation_failure(
151157 monkeypatch , runner , tmp_path , mock_config_models , is_app_config_failure
152158):
153159 """Test that the CLI handles Pydantic validation errors gracefully for both configs."""
154-
160+
155161 app_file = tmp_path / 'app.toml'
156- app_file .write_text ('bad data' )
157-
162+ app_file .write_text ('bad data' )
163+
158164 # 1. Mock the log.error function to check output
159165 mock_log_error = Mock ()
160166 monkeypatch .setattr (cli_mod .log , 'error' , mock_log_error )
161167
162168 # 2. Configure the Pydantic mocks to simulate failure
163169 mock_validation_error = ValidationError .from_exception_data (
164- 'TestModel' ,
170+ 'TestModel' ,
165171 [
166172 {
167- 'type' : 'int_parsing' ,
173+ 'type' : 'int_parsing' ,
168174 'loc' : ('server' , 'port' ),
169175 'input' : 'not_an_int' ,
170176 }
171177 ]
172178 )
173-
179+
174180 # Define the simple error structure that the CLI error formatting relies on:
175181 MOCK_ERROR_DETAIL = {
176- 'loc' : ('server' , 'port' ),
177- 'msg' : 'value is not a valid integer (mocked)' ,
182+ 'loc' : ('server' , 'port' ),
183+ 'msg' : 'value is not a valid integer (mocked)' ,
178184 'input' : 'not_an_int'
179185 }
180186
181-
187+
182188 if is_app_config_failure :
183189 mock_config_models ['app_from_dict' ].side_effect = mock_validation_error
184190 else :
185191 mock_config_models ['env_from_dict' ].side_effect = mock_validation_error
186-
192+
187193 # 3. Mock handle_config to return raw data successfully (no file read error)
188194 def fake_handle_config (user_path , * a , ** kw ):
189195 if user_path == app_file :
190196 return (app_file ,), {'raw_app_data' : 'x' }, False
191- return (Path ('env.toml' ),), {'raw_env_data' : 'y' }, False
197+ return (Path ('env.toml' ),), {'raw_env_data' : 'y' }, False
192198
193199 monkeypatch .setattr (cli_mod , 'handle_config' , fake_handle_config )
194200
@@ -199,28 +205,28 @@ def fake_handle_config(user_path, *a, **kw):
199205 obj = context ,
200206 catch_exceptions = True ,
201207 )
202-
208+
203209 # 4. Assertions
204- assert result .exit_code == 1
205-
210+ assert result .exit_code == 1
211+
206212 if is_app_config_failure :
207213 assert 'Application configuration failed validation' in mock_log_error .call_args_list [0 ][0 ][0 ]
208214 else :
209215 assert 'Environment configuration failed validation' in mock_log_error .call_args_list [0 ][0 ][0 ]
210-
216+
211217 # --- REMOVE FRAGILE ASSERTIONS ON LOG CALL COUNT ---
212- # assert mock_log_error.call_count > 1
218+ # assert mock_log_error.call_count > 1
213219 # assert mock_log_error.call_count >= 2
214220 # assert any("Field: (" in call[0][0] for call in mock_log_error.call_args_list)
215221
216- assert mock_log_error .call_count >= 1
222+ assert mock_log_error .call_count >= 1
217223
218224
219225def test_cli_verbose_and_debug (monkeypatch , runner , tmp_path , mock_config_models ):
220226 """Test that verbosity and debug flags are passed correctly to context."""
221227 # Create a real temporary file for Click to validate
222228 app_file = tmp_path / 'app.toml'
223- app_file .write_text ('[logging]\n version=1' )
229+ app_file .write_text ('[logging]\n version=1' )
224230
225231 def fake_handle_config (user_path , * a , ** kw ):
226232 if user_path == app_file :
@@ -237,13 +243,13 @@ def fake_handle_config(user_path, *a, **kw):
237243 obj = context ,
238244 catch_exceptions = False ,
239245 )
240-
246+
241247 # Check for success and context variables
242248 assert result .exit_code == 0
243249 assert 'capture\n ' in result .output
244250 assert context .verbose == 3
245251 assert context .debug is True
246-
252+
247253 # Assertions on config structure must now reference the MOCKED Pydantic objects
248254 assert context .appconfig is mock_config_models ['app_instance' ]
249- assert context .envconfig is mock_config_models ['env_instance ' ]
255+ assert context .envconfig is mock_config_models ['env_dump ' ]
0 commit comments