Skip to content

Commit 1ba6149

Browse files
committed
[FIX] update test cases - coverage 79.65%
1 parent 27e124b commit 1ba6149

File tree

2 files changed

+577
-37
lines changed

2 files changed

+577
-37
lines changed

tests/test_backends/test_main.py

Lines changed: 283 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
import subprocess
88
import tempfile
99
from pathlib import Path
10-
from unittest.mock import MagicMock, patch
10+
from unittest.mock import MagicMock, mock_open, patch
1111

1212
import pytest
1313

1414
from fastapi_fastkit.backend.main import (
15+
_parse_setup_dependencies,
16+
_process_config_file,
17+
_process_setup_file,
1518
add_new_route,
1619
create_venv,
1720
find_template_core_modules,
@@ -63,6 +66,16 @@ def test_create_venv_failure(self, mock_subprocess: MagicMock) -> None:
6366
with pytest.raises(BackendExceptions, match="Failed to create venv"):
6467
create_venv(str(self.project_path))
6568

69+
@patch("subprocess.run")
70+
def test_create_venv_os_error(self, mock_subprocess: MagicMock) -> None:
71+
"""Test create_venv function with OSError."""
72+
# given
73+
mock_subprocess.side_effect = OSError("Permission denied")
74+
75+
# when & then
76+
with pytest.raises(BackendExceptions, match="Failed to create venv"):
77+
create_venv(str(self.project_path))
78+
6679
def test_find_template_core_modules(self) -> None:
6780
"""Test find_template_core_modules function."""
6881
# given
@@ -122,6 +135,47 @@ def test_install_dependencies_success(self, mock_subprocess: MagicMock) -> None:
122135
# Should be called twice: pip upgrade and install requirements
123136
assert mock_subprocess.call_count == 2
124137

138+
@patch("subprocess.run")
139+
def test_install_dependencies_pip_upgrade_failure(
140+
self, mock_subprocess: MagicMock
141+
) -> None:
142+
"""Test install_dependencies function with pip upgrade failure."""
143+
# given
144+
requirements_txt = self.project_path / "requirements.txt"
145+
requirements_txt.write_text("fastapi==0.104.1")
146+
venv_path = str(self.project_path / ".venv")
147+
(self.project_path / ".venv").mkdir()
148+
mock_subprocess.side_effect = subprocess.CalledProcessError(
149+
1, ["pip", "install", "--upgrade", "pip"]
150+
)
151+
152+
# when & then
153+
with pytest.raises(BackendExceptions, match="Failed to install dependencies"):
154+
install_dependencies(str(self.project_path), venv_path)
155+
156+
@patch("subprocess.run")
157+
def test_install_dependencies_requirements_failure(
158+
self, mock_subprocess: MagicMock
159+
) -> None:
160+
"""Test install_dependencies function with requirements installation failure."""
161+
# given
162+
requirements_txt = self.project_path / "requirements.txt"
163+
requirements_txt.write_text("fastapi==0.104.1")
164+
venv_path = str(self.project_path / ".venv")
165+
(self.project_path / ".venv").mkdir()
166+
167+
# Mock successful pip upgrade but failed requirements install
168+
mock_subprocess.side_effect = [
169+
MagicMock(returncode=0), # successful pip upgrade
170+
subprocess.CalledProcessError(
171+
1, ["pip", "install", "-r", "requirements.txt"]
172+
), # failed requirements install
173+
]
174+
175+
# when & then
176+
with pytest.raises(BackendExceptions, match="Failed to install dependencies"):
177+
install_dependencies(str(self.project_path), venv_path)
178+
125179
def test_inject_project_metadata(self) -> None:
126180
"""Test inject_project_metadata function."""
127181
# given
@@ -163,6 +217,26 @@ def test_inject_project_metadata(self) -> None:
163217
config_content = config_py.read_text()
164218
assert 'PROJECT_NAME = "test-project"' in config_content
165219

220+
@patch("fastapi_fastkit.backend.main.find_template_core_modules")
221+
def test_inject_project_metadata_with_exception(
222+
self, mock_find_modules: MagicMock
223+
) -> None:
224+
"""Test inject_project_metadata function with exception handling."""
225+
# given
226+
mock_find_modules.side_effect = Exception("Mock error")
227+
228+
# when & then
229+
with pytest.raises(
230+
BackendExceptions, match="Failed to inject project metadata"
231+
):
232+
inject_project_metadata(
233+
str(self.project_path),
234+
"test-project",
235+
"Test Author",
236+
237+
"Test description",
238+
)
239+
166240
def test_read_template_stack(self) -> None:
167241
"""Test read_template_stack function."""
168242
# given
@@ -199,7 +273,212 @@ def test_read_template_stack(self) -> None:
199273
finally:
200274
import shutil
201275

202-
shutil.rmtree(template_path)
276+
shutil.rmtree(str(template_path))
277+
278+
def test_read_template_stack_requirements_file(self) -> None:
279+
"""Test read_template_stack function with requirements.txt file."""
280+
# given
281+
template_path = Path(tempfile.mkdtemp())
282+
try:
283+
requirements_txt = template_path / "requirements.txt-tpl"
284+
requirements_txt.write_text(
285+
"fastapi>=0.100.0\nuvicorn[standard]>=0.23.0\npydantic>=2.0.0"
286+
)
287+
288+
# when
289+
result = read_template_stack(str(template_path))
290+
291+
# then
292+
assert len(result) == 3
293+
assert "fastapi>=0.100.0" in result
294+
assert "uvicorn[standard]>=0.23.0" in result
295+
assert "pydantic>=2.0.0" in result
296+
297+
finally:
298+
import shutil
299+
300+
shutil.rmtree(str(template_path))
301+
302+
@patch("builtins.open", mock_open(read_data="fastapi>=0.100.0"))
303+
@patch("os.path.exists", return_value=True)
304+
def test_read_template_stack_file_read_error(self, mock_exists: MagicMock) -> None:
305+
"""Test read_template_stack function with file read error."""
306+
# given
307+
template_path = "/fake/path"
308+
309+
# Mock file read error
310+
with patch("builtins.open", side_effect=OSError("Permission denied")):
311+
# when
312+
result = read_template_stack(template_path)
313+
314+
# then
315+
assert result == []
316+
317+
@patch("builtins.open", mock_open(read_data="fastapi>=0.100.0"))
318+
@patch("os.path.exists", return_value=True)
319+
def test_read_template_stack_unicode_error(self, mock_exists: MagicMock) -> None:
320+
"""Test read_template_stack function with unicode decode error."""
321+
# given
322+
template_path = "/fake/path"
323+
324+
# Mock unicode decode error
325+
with patch(
326+
"builtins.open",
327+
side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "invalid start byte"),
328+
):
329+
# when
330+
result = read_template_stack(template_path)
331+
332+
# then
333+
assert result == []
334+
335+
def test_parse_setup_dependencies_list_format(self) -> None:
336+
"""Test _parse_setup_dependencies function with list format."""
337+
# given
338+
content = """
339+
install_requires: list[str] = [
340+
"fastapi>=0.100.0",
341+
"uvicorn>=0.23.0",
342+
# "commented-out-package",
343+
"pydantic>=2.0.0",
344+
]
345+
"""
346+
347+
# when
348+
result = _parse_setup_dependencies(content)
349+
350+
# then
351+
assert len(result) == 3
352+
assert "fastapi>=0.100.0" in result
353+
assert "uvicorn>=0.23.0" in result
354+
assert "pydantic>=2.0.0" in result
355+
356+
def test_parse_setup_dependencies_traditional_format(self) -> None:
357+
"""Test _parse_setup_dependencies function with traditional format."""
358+
# given
359+
content = """
360+
install_requires = [
361+
'fastapi>=0.100.0',
362+
'uvicorn>=0.23.0',
363+
'pydantic>=2.0.0',
364+
]
365+
"""
366+
367+
# when
368+
result = _parse_setup_dependencies(content)
369+
370+
# then
371+
assert len(result) == 3
372+
assert "fastapi>=0.100.0" in result
373+
assert "uvicorn>=0.23.0" in result
374+
assert "pydantic>=2.0.0" in result
375+
376+
def test_parse_setup_dependencies_empty_content(self) -> None:
377+
"""Test _parse_setup_dependencies function with empty content."""
378+
# given
379+
content = ""
380+
381+
# when
382+
result = _parse_setup_dependencies(content)
383+
384+
# then
385+
assert result == []
386+
387+
def test_process_setup_file_success(self) -> None:
388+
"""Test _process_setup_file function with successful processing."""
389+
# given
390+
setup_py = self.project_path / "setup.py"
391+
setup_py.write_text(
392+
"""
393+
setup(
394+
name="<project_name>",
395+
author="<author>",
396+
author_email="<author_email>",
397+
description="<description>",
398+
)
399+
"""
400+
)
401+
402+
# when
403+
_process_setup_file(
404+
str(setup_py),
405+
"test-project",
406+
"Test Author",
407+
408+
"Test description",
409+
)
410+
411+
# then
412+
content = setup_py.read_text()
413+
assert "test-project" in content
414+
assert "Test Author" in content
415+
assert "[email protected]" in content
416+
assert "Test description" in content
417+
418+
def test_process_setup_file_missing_file(self) -> None:
419+
"""Test _process_setup_file function with missing file."""
420+
# given
421+
setup_py = str(self.project_path / "nonexistent.py")
422+
423+
# when & then (should not raise exception)
424+
_process_setup_file(
425+
setup_py,
426+
"test-project",
427+
"Test Author",
428+
429+
"Test description",
430+
)
431+
432+
def test_process_setup_file_read_error(self) -> None:
433+
"""Test _process_setup_file function with file read error."""
434+
# given
435+
setup_py = self.project_path / "setup.py"
436+
setup_py.write_text("content")
437+
438+
# when & then
439+
with patch("builtins.open", side_effect=OSError("Permission denied")):
440+
with pytest.raises(BackendExceptions, match="Failed to process setup.py"):
441+
_process_setup_file(
442+
str(setup_py),
443+
"test-project",
444+
"Test Author",
445+
446+
"Test description",
447+
)
448+
449+
def test_process_config_file_success(self) -> None:
450+
"""Test _process_config_file function with successful processing."""
451+
# given
452+
config_py = self.project_path / "config.py"
453+
config_py.write_text('PROJECT_NAME = "<project_name>"')
454+
455+
# when
456+
_process_config_file(str(config_py), "test-project")
457+
458+
# then
459+
content = config_py.read_text()
460+
assert 'PROJECT_NAME = "test-project"' in content
461+
462+
def test_process_config_file_missing_file(self) -> None:
463+
"""Test _process_config_file function with missing file."""
464+
# given
465+
config_py = str(self.project_path / "nonexistent.py")
466+
467+
# when & then (should not raise exception)
468+
_process_config_file(config_py, "test-project")
469+
470+
def test_process_config_file_read_error(self) -> None:
471+
"""Test _process_config_file function with file read error."""
472+
# given
473+
config_py = self.project_path / "config.py"
474+
config_py.write_text("content")
475+
476+
# when & then
477+
with patch("builtins.open", side_effect=OSError("Permission denied")):
478+
with pytest.raises(
479+
BackendExceptions, match="Failed to process config file"
480+
):
481+
_process_config_file(str(config_py), "test-project")
203482

204483
@patch("fastapi_fastkit.backend.main._ensure_project_structure")
205484
@patch("fastapi_fastkit.backend.main._create_route_files")
@@ -214,15 +493,14 @@ def test_add_new_route(
214493
) -> None:
215494
"""Test add_new_route function."""
216495
# given
217-
route_name = "user"
218496
mock_ensure_structure.return_value = {
219-
"api": "/fake/api",
497+
"api_routes": "/fake/api/routes",
220498
"crud": "/fake/crud",
221499
"schemas": "/fake/schemas",
222500
}
223501

224502
# when
225-
add_new_route(str(self.project_path), route_name)
503+
add_new_route(str(self.project_path), "test_route")
226504

227505
# then
228506
mock_ensure_structure.assert_called_once()

0 commit comments

Comments
 (0)