Skip to content

Commit c88ed9c

Browse files
committed
fix: update minimum expected version in test_version_functional.py to 0.1.0; add unit tests for ControllerGenerator in test_generators.py
1 parent ffe4913 commit c88ed9c

File tree

2 files changed

+295
-1
lines changed

2 files changed

+295
-1
lines changed

tests/test_version_functional.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def test_version_comparison_with_previous():
9696
"""Test that current version is reasonable compared to expected previous versions."""
9797
current_version = tuple(map(int, __version__.split(".")))
9898

99-
minimum_expected = (2, 9, 0)
99+
minimum_expected = (0,1,0)
100100
assert (
101101
current_version >= minimum_expected
102102
), f"Current version {__version__} is lower than expected minimum {'.'.join(map(str, minimum_expected))}"

tests/unit/test_generators.py

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
"""Tests for generators module."""
2+
3+
import pytest
4+
from pathlib import Path
5+
from unittest.mock import Mock, patch, mock_open
6+
import tempfile
7+
import shutil
8+
9+
from mvc_flask.core.generators import ControllerGenerator
10+
from mvc_flask.core.exceptions import ControllerGenerationError, InvalidControllerNameError
11+
12+
13+
class TestControllerGenerator:
14+
"""Test cases for ControllerGenerator class."""
15+
16+
def setup_method(self):
17+
"""Set up test fixtures."""
18+
self.temp_dir = Path(tempfile.mkdtemp())
19+
self.templates_dir = self.temp_dir / "templates"
20+
self.templates_dir.mkdir(parents=True, exist_ok=True)
21+
22+
# Create a mock template file
23+
template_file = self.templates_dir / "base_controller.jinja2"
24+
template_content = """class {{ class_name }}:
25+
\"\"\"Controller class.\"\"\"
26+
27+
def index(self):
28+
return "Hello from {{ class_name }}"
29+
"""
30+
template_file.write_text(template_content)
31+
32+
self.generator = ControllerGenerator(templates_dir=self.templates_dir)
33+
34+
def teardown_method(self):
35+
"""Clean up test fixtures."""
36+
if self.temp_dir.exists():
37+
shutil.rmtree(self.temp_dir)
38+
39+
def test_init_with_templates_dir(self):
40+
"""Test ControllerGenerator initialization with custom templates directory."""
41+
generator = ControllerGenerator(templates_dir=self.templates_dir)
42+
43+
assert generator.template_renderer is not None
44+
assert generator.file_handler is not None
45+
assert generator.name_utils is not None
46+
assert generator.config is not None
47+
48+
@patch('mvc_flask.core.generators.CLIConfig.get_templates_dir')
49+
def test_init_without_templates_dir(self, mock_get_templates_dir):
50+
"""Test ControllerGenerator initialization with default templates directory."""
51+
mock_get_templates_dir.return_value = self.templates_dir
52+
53+
generator = ControllerGenerator()
54+
55+
assert generator.template_renderer is not None
56+
assert generator.file_handler is not None
57+
assert generator.name_utils is not None
58+
assert generator.config is not None
59+
mock_get_templates_dir.assert_called_once()
60+
61+
def test_generate_controller_success(self):
62+
"""Test successful controller generation."""
63+
output_dir = self.temp_dir / "controllers"
64+
65+
with patch.object(self.generator.name_utils, 'validate_controller_name') as mock_validate, \
66+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
67+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
68+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
69+
patch.object(self.generator.file_handler, 'ensure_directory_exists') as mock_ensure_dir, \
70+
patch.object(self.generator.file_handler, 'write_file') as mock_write, \
71+
patch.object(self.generator.template_renderer, 'render') as mock_render:
72+
73+
mock_normalize.return_value = "test_controller"
74+
mock_class_name.return_value = "TestController"
75+
mock_exists.return_value = False
76+
mock_render.return_value = "class TestController:\n pass"
77+
78+
result = self.generator.generate("test", str(output_dir))
79+
80+
expected_path = output_dir / "test_controller.py"
81+
assert result == expected_path
82+
83+
mock_validate.assert_called_once_with("test")
84+
mock_normalize.assert_called_once_with("test")
85+
mock_class_name.assert_called_once_with("test_controller")
86+
mock_exists.assert_called_once_with(expected_path)
87+
mock_ensure_dir.assert_called_once_with(output_dir)
88+
mock_render.assert_called_once_with(
89+
self.generator.config.CONTROLLER_TEMPLATE,
90+
{"class_name": "TestController"}
91+
)
92+
mock_write.assert_called_once_with(expected_path, "class TestController:\n pass")
93+
94+
@patch('mvc_flask.core.generators.CLIConfig.get_controllers_path')
95+
def test_generate_controller_default_path(self, mock_get_path):
96+
"""Test controller generation with default output path."""
97+
mock_get_path.return_value = "app/controllers"
98+
output_dir = Path("app/controllers")
99+
100+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
101+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
102+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
103+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
104+
patch.object(self.generator.file_handler, 'ensure_directory_exists'), \
105+
patch.object(self.generator.file_handler, 'write_file'), \
106+
patch.object(self.generator.template_renderer, 'render') as mock_render:
107+
108+
mock_normalize.return_value = "home_controller"
109+
mock_class_name.return_value = "HomeController"
110+
mock_exists.return_value = False
111+
mock_render.return_value = "class HomeController:\n pass"
112+
113+
result = self.generator.generate("home")
114+
115+
expected_path = output_dir / "home_controller.py"
116+
assert result == expected_path
117+
mock_get_path.assert_called_once()
118+
119+
def test_generate_controller_file_exists_no_force(self):
120+
"""Test controller generation when file exists and force is False."""
121+
output_dir = self.temp_dir / "controllers"
122+
123+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
124+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
125+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists:
126+
127+
mock_normalize.return_value = "existing_controller"
128+
mock_exists.return_value = True
129+
130+
with pytest.raises(ControllerGenerationError) as exc_info:
131+
self.generator.generate("existing", str(output_dir), force=False)
132+
133+
assert "already exists" in str(exc_info.value)
134+
assert "Use --force to overwrite" in str(exc_info.value)
135+
136+
def test_generate_controller_file_exists_with_force(self):
137+
"""Test controller generation when file exists and force is True."""
138+
output_dir = self.temp_dir / "controllers"
139+
140+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
141+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
142+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
143+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
144+
patch.object(self.generator.file_handler, 'ensure_directory_exists'), \
145+
patch.object(self.generator.file_handler, 'write_file') as mock_write, \
146+
patch.object(self.generator.template_renderer, 'render') as mock_render:
147+
148+
mock_normalize.return_value = "existing_controller"
149+
mock_class_name.return_value = "ExistingController"
150+
mock_exists.return_value = True
151+
mock_render.return_value = "class ExistingController:\n pass"
152+
153+
result = self.generator.generate("existing", str(output_dir), force=True)
154+
155+
expected_path = output_dir / "existing_controller.py"
156+
assert result == expected_path
157+
mock_write.assert_called_once()
158+
159+
def test_generate_controller_invalid_name_error(self):
160+
"""Test controller generation with invalid name."""
161+
with patch.object(self.generator.name_utils, 'validate_controller_name') as mock_validate:
162+
mock_validate.side_effect = InvalidControllerNameError("Invalid name")
163+
164+
with pytest.raises(InvalidControllerNameError):
165+
self.generator.generate("123invalid")
166+
167+
def test_generate_controller_template_render_error(self):
168+
"""Test controller generation when template rendering fails."""
169+
output_dir = self.temp_dir / "controllers"
170+
171+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
172+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
173+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
174+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
175+
patch.object(self.generator.file_handler, 'ensure_directory_exists'), \
176+
patch.object(self.generator.template_renderer, 'render') as mock_render:
177+
178+
mock_normalize.return_value = "test_controller"
179+
mock_class_name.return_value = "TestController"
180+
mock_exists.return_value = False
181+
mock_render.side_effect = Exception("Template error")
182+
183+
with pytest.raises(ControllerGenerationError) as exc_info:
184+
self.generator.generate("test", str(output_dir))
185+
186+
assert "Failed to generate controller" in str(exc_info.value)
187+
188+
def test_generate_controller_file_write_error(self):
189+
"""Test controller generation when file writing fails."""
190+
output_dir = self.temp_dir / "controllers"
191+
192+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
193+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
194+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
195+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
196+
patch.object(self.generator.file_handler, 'ensure_directory_exists'), \
197+
patch.object(self.generator.file_handler, 'write_file') as mock_write, \
198+
patch.object(self.generator.template_renderer, 'render') as mock_render:
199+
200+
mock_normalize.return_value = "test_controller"
201+
mock_class_name.return_value = "TestController"
202+
mock_exists.return_value = False
203+
mock_render.return_value = "class TestController:\n pass"
204+
mock_write.side_effect = Exception("Write error")
205+
206+
with pytest.raises(ControllerGenerationError) as exc_info:
207+
self.generator.generate("test", str(output_dir))
208+
209+
assert "Failed to generate controller" in str(exc_info.value)
210+
211+
def test_generate_controller_directory_creation_error(self):
212+
"""Test controller generation when directory creation fails."""
213+
output_dir = self.temp_dir / "controllers"
214+
215+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
216+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
217+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
218+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
219+
patch.object(self.generator.file_handler, 'ensure_directory_exists') as mock_ensure_dir, \
220+
patch.object(self.generator.template_renderer, 'render') as mock_render:
221+
222+
mock_normalize.return_value = "test_controller"
223+
mock_class_name.return_value = "TestController"
224+
mock_exists.return_value = False
225+
mock_render.return_value = "class TestController:\n pass"
226+
mock_ensure_dir.side_effect = Exception("Directory error")
227+
228+
with pytest.raises(ControllerGenerationError) as exc_info:
229+
self.generator.generate("test", str(output_dir))
230+
231+
assert "Failed to generate controller" in str(exc_info.value)
232+
233+
def test_generate_controller_complex_name(self):
234+
"""Test controller generation with complex names."""
235+
output_dir = self.temp_dir / "controllers"
236+
237+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
238+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
239+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
240+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists, \
241+
patch.object(self.generator.file_handler, 'ensure_directory_exists'), \
242+
patch.object(self.generator.file_handler, 'write_file') as mock_write, \
243+
patch.object(self.generator.template_renderer, 'render') as mock_render:
244+
245+
mock_normalize.return_value = "api_v1_user_controller"
246+
mock_class_name.return_value = "ApiV1UserController"
247+
mock_exists.return_value = False
248+
mock_render.return_value = "class ApiV1UserController:\n pass"
249+
250+
result = self.generator.generate("api_v1_user", str(output_dir))
251+
252+
expected_path = output_dir / "api_v1_user_controller.py"
253+
assert result == expected_path
254+
255+
def test_generate_controller_re_raise_known_exceptions(self):
256+
"""Test that known exceptions are re-raised without wrapping."""
257+
with patch.object(self.generator.name_utils, 'validate_controller_name') as mock_validate:
258+
# Test InvalidControllerNameError is re-raised
259+
mock_validate.side_effect = InvalidControllerNameError("Invalid name")
260+
261+
with pytest.raises(InvalidControllerNameError):
262+
self.generator.generate("invalid")
263+
264+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
265+
patch.object(self.generator.name_utils, 'normalize_controller_name'), \
266+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists:
267+
268+
# Test ControllerGenerationError is re-raised
269+
mock_exists.side_effect = ControllerGenerationError("Generation error")
270+
271+
with pytest.raises(ControllerGenerationError):
272+
self.generator.generate("test")
273+
274+
def test_generate_controller_integration_with_real_template(self):
275+
"""Test controller generation with actual template rendering."""
276+
output_dir = self.temp_dir / "controllers"
277+
output_dir.mkdir(parents=True, exist_ok=True)
278+
279+
with patch.object(self.generator.name_utils, 'validate_controller_name'), \
280+
patch.object(self.generator.name_utils, 'normalize_controller_name') as mock_normalize, \
281+
patch.object(self.generator.name_utils, 'generate_class_name') as mock_class_name, \
282+
patch.object(self.generator.file_handler, 'file_exists') as mock_exists:
283+
284+
mock_normalize.return_value = "test_controller"
285+
mock_class_name.return_value = "TestController"
286+
mock_exists.return_value = False
287+
288+
result = self.generator.generate("test", str(output_dir))
289+
290+
# Check that file was actually created
291+
assert result.exists()
292+
content = result.read_text()
293+
assert "TestController" in content
294+
assert "class TestController:" in content

0 commit comments

Comments
 (0)