@@ -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\n api_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