@@ -204,6 +204,113 @@ def test_no_alembic_dep_skips(self, tmp_path):
204204 assert has_result (v , Result .SKIP , "Alembic config" )
205205
206206
207+ class TestTier2InitPy :
208+ def test_init_py_present_passes (self ):
209+ v = run_validator (VALID_APP , tier = 2 )
210+ assert has_result (v , Result .PASS , "app/__init__.py exists" )
211+
212+ def test_init_py_missing_warns (self , tmp_path ):
213+ (tmp_path / "app" ).mkdir ()
214+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
215+ v ._check_init_py ()
216+ assert has_result (v , Result .WARN , "app/__init__.py exists" )
217+
218+
219+ class TestTier2Dockerignore :
220+ def test_dockerignore_all_present_passes (self ):
221+ v = run_validator (VALID_APP , tier = 2 )
222+ assert has_result (v , Result .PASS , ".dockerignore exists" )
223+ assert has_result (v , Result .PASS , ".dockerignore excludes .git" )
224+ assert has_result (v , Result .PASS , ".dockerignore excludes .env" )
225+
226+ def test_dockerignore_missing_warns (self , tmp_path ):
227+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
228+ v ._check_dockerignore ()
229+ assert has_result (v , Result .WARN , ".dockerignore exists" )
230+
231+ def test_dockerignore_missing_git_warns (self , tmp_path ):
232+ (tmp_path / ".dockerignore" ).write_text (".env\n " )
233+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
234+ v ._check_dockerignore ()
235+ assert has_result (v , Result .WARN , ".dockerignore excludes .git" )
236+
237+ def test_dockerignore_missing_env_warns (self , tmp_path ):
238+ (tmp_path / ".dockerignore" ).write_text (".git\n " )
239+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
240+ v ._check_dockerignore ()
241+ assert has_result (v , Result .WARN , ".dockerignore excludes .env" )
242+
243+
244+ class TestTier2Middleware :
245+ def test_both_middleware_passes (self ):
246+ v = run_validator (VALID_APP , tier = 2 )
247+ assert has_result (v , Result .PASS , "CORS middleware referenced" )
248+ assert has_result (v , Result .PASS , "Rate limiting referenced" )
249+
250+ def test_missing_cors_warns (self , tmp_path ):
251+ (tmp_path / "app" ).mkdir ()
252+ (tmp_path / "app" / "main.py" ).write_text ("from fastapi import FastAPI\n from slowapi import Limiter\n app = FastAPI()\n " )
253+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
254+ v ._check_middleware ()
255+ assert has_result (v , Result .WARN , "CORS middleware referenced" )
256+
257+ def test_missing_rate_limiting_warns (self , tmp_path ):
258+ (tmp_path / "app" ).mkdir ()
259+ (tmp_path / "app" / "main.py" ).write_text ("from fastapi import FastAPI\n from fastapi.middleware.cors import CORSMiddleware\n app = FastAPI()\n " )
260+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
261+ v ._check_middleware ()
262+ assert has_result (v , Result .WARN , "Rate limiting referenced" )
263+
264+
265+ class TestTier2MigrationStructure :
266+ def test_migration_structure_passes (self ):
267+ v = run_validator (VALID_APP , tier = 2 )
268+ assert has_result (v , Result .PASS , "Migration versions present" )
269+
270+ def test_no_alembic_dep_skips (self , tmp_path ):
271+ (tmp_path / "requirements.txt" ).write_text ("fastapi\n uvicorn\n " )
272+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
273+ v ._check_migration_structure ()
274+ assert has_result (v , Result .SKIP , "Migration structure" )
275+
276+ def test_missing_versions_dir_warns (self , tmp_path ):
277+ (tmp_path / "requirements.txt" ).write_text ("fastapi\n alembic\n " )
278+ (tmp_path / "app" ).mkdir ()
279+ (tmp_path / "app" / "alembic" ).mkdir ()
280+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
281+ v ._check_migration_structure ()
282+ assert has_result (v , Result .WARN , "Migration versions directory" )
283+
284+ def test_empty_versions_dir_warns (self , tmp_path ):
285+ (tmp_path / "requirements.txt" ).write_text ("fastapi\n alembic\n " )
286+ (tmp_path / "app" / "alembic" / "versions" ).mkdir (parents = True )
287+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
288+ v ._check_migration_structure ()
289+ assert has_result (v , Result .WARN , "Migration versions present" )
290+
291+
292+ class TestTier2HealthEndpointCode :
293+ def test_health_in_main_passes (self ):
294+ v = run_validator (VALID_APP , tier = 2 )
295+ assert has_result (v , Result .PASS , "Health endpoint in code" )
296+
297+ def test_no_health_endpoint_warns (self , tmp_path ):
298+ (tmp_path / "app" ).mkdir ()
299+ (tmp_path / "app" / "main.py" ).write_text ("from fastapi import FastAPI\n app = FastAPI()\n " )
300+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
301+ v ._check_health_endpoint_code ()
302+ assert has_result (v , Result .WARN , "Health endpoint in code" )
303+
304+ def test_health_in_router_passes (self , tmp_path ):
305+ (tmp_path / "app" ).mkdir ()
306+ (tmp_path / "app" / "main.py" ).write_text ("from fastapi import FastAPI\n app = FastAPI()\n " )
307+ (tmp_path / "app" / "routers" ).mkdir ()
308+ (tmp_path / "app" / "routers" / "health.py" ).write_text ('@router.get("/health")\n def health(): return {"status":"ok"}\n ' )
309+ v = Validator (app_dir = str (tmp_path ), tier = 2 , strict = False )
310+ v ._check_health_endpoint_code ()
311+ assert has_result (v , Result .PASS , "Health endpoint in code" )
312+
313+
207314class TestTier3WithMocking :
208315 """Tier 3 tests using mocked subprocess.run — no Docker required."""
209316
0 commit comments