Skip to content

Commit d5a739f

Browse files
committed
[TEST] coverage update to 83%, update test workflow to run at only when PR merged
1 parent 1ba6149 commit d5a739f

File tree

6 files changed

+946
-22
lines changed

6 files changed

+946
-22
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
name: Test and Coverage
22

33
on:
4-
push:
5-
branches:
6-
- main
74
pull_request:
85
types:
96
- opened

docs/contributing/development-setup.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,21 +161,6 @@ Success: no issues found in 12 source files
161161

162162
</div>
163163

164-
### Security Scanning
165-
166-
**bandit** - Security issue detection:
167-
168-
<div class="termy">
169-
170-
```console
171-
$ bandit -r src/
172-
[main] INFO profile include tests: None
173-
[main] INFO profile exclude tests: None
174-
No issues identified.
175-
```
176-
177-
</div>
178-
179164
## Available Make Commands
180165

181166
The project Makefile provides convenient commands for common development tasks:

tests/test_backends/test_main.py

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,358 @@ def test_add_new_route(
507507
mock_create_route.assert_called_once()
508508
mock_handle_api.assert_called_once()
509509
mock_update_main.assert_called_once()
510+
511+
def test_ensure_project_structure_success(self) -> None:
512+
"""Test _ensure_project_structure function with successful structure creation."""
513+
# given
514+
src_dir = self.project_path / "src"
515+
src_dir.mkdir()
516+
517+
# when
518+
from fastapi_fastkit.backend.main import _ensure_project_structure
519+
520+
result = _ensure_project_structure(str(src_dir))
521+
522+
# then
523+
assert "api" in result
524+
assert "api_routes" in result
525+
assert "crud" in result
526+
assert "schemas" in result
527+
528+
# Check directories were created
529+
assert (src_dir / "api").exists()
530+
assert (src_dir / "api" / "routes").exists()
531+
assert (src_dir / "crud").exists()
532+
assert (src_dir / "schemas").exists()
533+
534+
# Check __init__.py files were created
535+
assert (src_dir / "api" / "__init__.py").exists()
536+
assert (src_dir / "api" / "routes" / "__init__.py").exists()
537+
assert (src_dir / "crud" / "__init__.py").exists()
538+
assert (src_dir / "schemas" / "__init__.py").exists()
539+
540+
def test_ensure_project_structure_missing_src_dir(self) -> None:
541+
"""Test _ensure_project_structure function when src directory doesn't exist."""
542+
# given
543+
nonexistent_dir = str(self.project_path / "nonexistent")
544+
545+
# when & then
546+
from fastapi_fastkit.backend.main import _ensure_project_structure
547+
548+
with pytest.raises(BackendExceptions, match="Source directory not found"):
549+
_ensure_project_structure(nonexistent_dir)
550+
551+
def test_ensure_project_structure_existing_directories(self) -> None:
552+
"""Test _ensure_project_structure function when directories already exist."""
553+
# given
554+
src_dir = self.project_path / "src"
555+
src_dir.mkdir()
556+
api_dir = src_dir / "api"
557+
api_dir.mkdir()
558+
(api_dir / "__init__.py").write_text("# existing")
559+
560+
# when
561+
from fastapi_fastkit.backend.main import _ensure_project_structure
562+
563+
result = _ensure_project_structure(str(src_dir))
564+
565+
# then
566+
assert result["api"] == str(api_dir)
567+
# Should preserve existing __init__.py content
568+
assert (api_dir / "__init__.py").read_text() == "# existing"
569+
570+
@patch("fastapi_fastkit.backend.main.copy_and_convert_template_file")
571+
def test_create_route_files_success(self, mock_copy: MagicMock) -> None:
572+
"""Test _create_route_files function with successful file creation."""
573+
# given
574+
modules_dir = str(self.project_path / "modules")
575+
target_dirs = {
576+
"api_routes": str(self.project_path / "api" / "routes"),
577+
"crud": str(self.project_path / "crud"),
578+
"schemas": str(self.project_path / "schemas"),
579+
}
580+
route_name = "test_route"
581+
mock_copy.return_value = True
582+
583+
# Create target directories
584+
for dir_path in target_dirs.values():
585+
os.makedirs(dir_path, exist_ok=True)
586+
587+
# when
588+
from fastapi_fastkit.backend.main import _create_route_files
589+
590+
_create_route_files(modules_dir, target_dirs, route_name)
591+
592+
# then
593+
assert mock_copy.call_count == 3 # api/routes, crud, schemas
594+
595+
@patch("fastapi_fastkit.backend.main.copy_and_convert_template_file")
596+
def test_create_route_files_existing_file(self, mock_copy: MagicMock) -> None:
597+
"""Test _create_route_files function when target file already exists."""
598+
# given
599+
modules_dir = str(self.project_path / "modules")
600+
target_dirs = {
601+
"api_routes": str(self.project_path / "api" / "routes"),
602+
"crud": str(self.project_path / "crud"),
603+
"schemas": str(self.project_path / "schemas"),
604+
}
605+
route_name = "test_route"
606+
607+
# Create target directories and existing file
608+
os.makedirs(target_dirs["api_routes"], exist_ok=True)
609+
os.makedirs(target_dirs["crud"], exist_ok=True)
610+
os.makedirs(target_dirs["schemas"], exist_ok=True)
611+
612+
existing_file = Path(target_dirs["api_routes"]) / f"{route_name}.py"
613+
existing_file.write_text("# existing")
614+
615+
# when
616+
from fastapi_fastkit.backend.main import _create_route_files
617+
618+
_create_route_files(modules_dir, target_dirs, route_name)
619+
620+
# then
621+
# Only crud and schemas should be called (api_routes file exists)
622+
assert mock_copy.call_count == 2 # Only crud and schemas, not api_routes
623+
624+
@patch("fastapi_fastkit.backend.main.copy_and_convert_template_file")
625+
def test_handle_api_router_file_no_existing_file(
626+
self, mock_copy: MagicMock
627+
) -> None:
628+
"""Test _handle_api_router_file function when no api.py exists."""
629+
# given
630+
target_dirs = {"api": str(self.project_path / "api")}
631+
modules_dir = str(self.project_path / "modules")
632+
route_name = "test_route"
633+
mock_copy.return_value = True
634+
635+
os.makedirs(target_dirs["api"], exist_ok=True)
636+
637+
# Create the source template file
638+
os.makedirs(os.path.join(modules_dir, "api"), exist_ok=True)
639+
source_file = Path(modules_dir) / "api" / "__init__.py-tpl"
640+
source_file.write_text(
641+
"from fastapi import APIRouter\napi_router = APIRouter()"
642+
)
643+
644+
# when
645+
from fastapi_fastkit.backend.main import _handle_api_router_file
646+
647+
_handle_api_router_file(target_dirs, modules_dir, route_name)
648+
649+
# then
650+
# Should be called to create api.py file
651+
mock_copy.assert_called()
652+
653+
def test_handle_api_router_file_existing_file(self) -> None:
654+
"""Test _handle_api_router_file function when api.py already exists."""
655+
# given
656+
api_dir = self.project_path / "api"
657+
api_dir.mkdir()
658+
api_file = api_dir / "api.py"
659+
existing_content = """
660+
from fastapi import APIRouter
661+
662+
api_router = APIRouter()
663+
664+
@api_router.get("/items")
665+
def get_items():
666+
return {"items": []}
667+
"""
668+
api_file.write_text(existing_content)
669+
670+
target_dirs = {"api": str(api_dir)}
671+
modules_dir = str(self.project_path / "modules")
672+
route_name = "users"
673+
674+
# when
675+
from fastapi_fastkit.backend.main import _handle_api_router_file
676+
677+
_handle_api_router_file(target_dirs, modules_dir, route_name)
678+
679+
# then
680+
updated_content = api_file.read_text()
681+
# Check for the actual import pattern used in _update_api_router
682+
assert "from .routes import users" in updated_content
683+
assert (
684+
'api_router.include_router(users.router, prefix="/users", tags=["users"])'
685+
in updated_content
686+
)
687+
688+
@patch("fastapi_fastkit.backend.main.copy_and_convert_template_file")
689+
def test_process_init_files_success(self, mock_copy: MagicMock) -> None:
690+
"""Test _process_init_files function."""
691+
# given
692+
modules_dir = str(self.project_path / "modules")
693+
# _process_init_files looks for module_base in target_dirs, not exact module_type
694+
target_dirs = {
695+
"api": str(self.project_path / "api"), # api/routes -> api
696+
"crud": str(self.project_path / "crud"),
697+
"schemas": str(self.project_path / "schemas"),
698+
}
699+
module_types = ["api/routes", "crud", "schemas"]
700+
mock_copy.return_value = True
701+
702+
# Create target directories
703+
for dir_path in target_dirs.values():
704+
os.makedirs(dir_path, exist_ok=True)
705+
706+
# Create source template files for each module_base (not module_type)
707+
for module_type in module_types:
708+
module_base = module_type.split("/")[0] # api/routes -> api
709+
source_dir = Path(modules_dir) / module_base
710+
source_dir.mkdir(parents=True, exist_ok=True)
711+
source_file = source_dir / "__init__.py-tpl"
712+
source_file.write_text("# init file")
713+
714+
# when
715+
from fastapi_fastkit.backend.main import _process_init_files
716+
717+
_process_init_files(modules_dir, target_dirs, module_types)
718+
719+
# then
720+
# Only unique module_bases will be processed: api, crud, schemas = 3 calls
721+
assert mock_copy.call_count == 3
722+
723+
def test_update_main_app_success(self) -> None:
724+
"""Test _update_main_app function with successful update."""
725+
# given
726+
src_dir = self.project_path / "src"
727+
src_dir.mkdir()
728+
main_py = src_dir / "main.py"
729+
main_content = """
730+
from fastapi import FastAPI
731+
732+
app = FastAPI()
733+
734+
@app.get("/")
735+
def read_root():
736+
return {"Hello": "World"}
737+
"""
738+
main_py.write_text(main_content)
739+
740+
# when
741+
from fastapi_fastkit.backend.main import _update_main_app
742+
743+
_update_main_app(str(src_dir), "test_route")
744+
745+
# then
746+
updated_content = main_py.read_text()
747+
assert "from src.api.api import api_router" in updated_content
748+
assert "app.include_router(api_router)" in updated_content
749+
750+
def test_update_main_app_no_main_file(self) -> None:
751+
"""Test _update_main_app function when main.py doesn't exist."""
752+
# given
753+
src_dir = self.project_path / "src"
754+
src_dir.mkdir()
755+
756+
# when
757+
from fastapi_fastkit.backend.main import _update_main_app
758+
759+
_update_main_app(str(src_dir), "test_route")
760+
761+
# then
762+
# Should complete without error (warning logged)
763+
pass
764+
765+
def test_update_main_app_no_fastapi_app(self) -> None:
766+
"""Test _update_main_app function when FastAPI app is not found."""
767+
# given
768+
src_dir = self.project_path / "src"
769+
src_dir.mkdir()
770+
main_py = src_dir / "main.py"
771+
main_py.write_text("print('Hello World')")
772+
773+
# when
774+
from fastapi_fastkit.backend.main import _update_main_app
775+
776+
_update_main_app(str(src_dir), "test_route")
777+
778+
# then
779+
# Should complete without error (warning logged)
780+
content = main_py.read_text()
781+
assert "app = FastAPI" not in content
782+
783+
def test_update_main_app_already_configured(self) -> None:
784+
"""Test _update_main_app function when router is already configured."""
785+
# given
786+
src_dir = self.project_path / "src"
787+
src_dir.mkdir()
788+
main_py = src_dir / "main.py"
789+
main_content = """
790+
from fastapi import FastAPI
791+
from src.api.api import api_router
792+
793+
app = FastAPI()
794+
app.include_router(api_router)
795+
796+
@app.get("/")
797+
def read_root():
798+
return {"Hello": "World"}
799+
"""
800+
main_py.write_text(main_content)
801+
original_content = main_content
802+
803+
# when
804+
from fastapi_fastkit.backend.main import _update_main_app
805+
806+
_update_main_app(str(src_dir), "test_route")
807+
808+
# then
809+
# Should not modify the file
810+
assert main_py.read_text() == original_content
811+
812+
def test_update_main_app_file_read_error(self) -> None:
813+
"""Test _update_main_app function with file read error."""
814+
# given
815+
src_dir = self.project_path / "src"
816+
src_dir.mkdir()
817+
main_py = src_dir / "main.py"
818+
main_py.write_text("content")
819+
820+
# when & then
821+
with patch("builtins.open", side_effect=OSError("Permission denied")):
822+
from fastapi_fastkit.backend.main import _update_main_app
823+
824+
# Should complete without raising exception
825+
_update_main_app(str(src_dir), "test_route")
826+
827+
def test_add_new_route_with_exception(self) -> None:
828+
"""Test add_new_route function with OSError."""
829+
# given
830+
project_dir = str(self.project_path)
831+
832+
# when & then
833+
with patch(
834+
"fastapi_fastkit.backend.main._ensure_project_structure",
835+
side_effect=OSError("Permission denied"),
836+
):
837+
with pytest.raises(BackendExceptions, match="Failed to add new route"):
838+
add_new_route(project_dir, "test_route")
839+
840+
def test_add_new_route_with_backend_exception(self) -> None:
841+
"""Test add_new_route function with BackendExceptions."""
842+
# given
843+
project_dir = str(self.project_path)
844+
845+
# when & then
846+
with patch(
847+
"fastapi_fastkit.backend.main._ensure_project_structure",
848+
side_effect=BackendExceptions("Backend error"),
849+
):
850+
with pytest.raises(BackendExceptions, match="Backend error"):
851+
add_new_route(project_dir, "test_route")
852+
853+
def test_add_new_route_with_unexpected_exception(self) -> None:
854+
"""Test add_new_route function with unexpected exception."""
855+
# given
856+
project_dir = str(self.project_path)
857+
858+
# when & then
859+
with patch(
860+
"fastapi_fastkit.backend.main._ensure_project_structure",
861+
side_effect=ValueError("Unexpected error"),
862+
):
863+
with pytest.raises(BackendExceptions, match="Failed to add new route"):
864+
add_new_route(project_dir, "test_route")

0 commit comments

Comments
 (0)