""" Security regression tests for authenticated / removed routes (no Postgres required). Run: cd be0 && python -m unittest tests.test_security_routes -v """ from __future__ import annotations import os import unittest from unittest.mock import patch from tests.security_token_fixture import mint_bearer_token class SecurityRoutesTests(unittest.TestCase): def _client(self): from fastapi.testclient import TestClient from main import app return TestClient(app) def test_removed_upload_document_returns_404(self) -> None: client = self._client() r = client.post("/upload_document", files={"file": ("x.pdf", b"%PDF", "application/pdf")}) self.assertEqual(r.status_code, 404) def test_removed_get_page_returns_404(self) -> None: client = self._client() r = client.post("/get_page", data={"new_page_number": "1"}) self.assertEqual(r.status_code, 404) def test_list_applications_requires_auth(self) -> None: client = self._client() r = client.get("/api/applications") self.assertEqual(r.status_code, 401) def test_list_applications_rejects_viewer(self) -> None: client = self._client() headers = {"Authorization": mint_bearer_token(roles=("viewer",))} with patch("src.initiative_db.engine.is_postgres_enabled", return_value=False): r = client.get("/api/applications", headers=headers) self.assertEqual(r.status_code, 403) def test_list_applications_allows_staff_without_db(self) -> None: client = self._client() headers = {"Authorization": mint_bearer_token(roles=("admin",))} with patch("src.initiative_db.engine.is_postgres_enabled", return_value=False): with patch("main._load_submitted_items", return_value=[]): r = client.get("/api/applications", headers=headers) self.assertEqual(r.status_code, 200, r.text) self.assertIn("data", r.json()) def test_get_application_requires_auth(self) -> None: client = self._client() r = client.get("/api/applications/sub-deadbeefdeadbeef") self.assertEqual(r.status_code, 401) def test_get_application_rejects_viewer_without_row(self) -> None: client = self._client() headers = {"Authorization": mint_bearer_token(roles=("viewer",), email="viewer@ump.edu.vn")} with patch("src.initiative_db.engine.is_postgres_enabled", return_value=False): with patch("main._get_application_from_file_index", return_value=None): r = client.get("/api/applications/sub-deadbeefdeadbeef", headers=headers) self.assertEqual(r.status_code, 404) def test_review_documents_list_requires_auth(self) -> None: client = self._client() r = client.get("/api/v1/review-documents", params={"caseId": "CASE-1"}) self.assertEqual(r.status_code, 401) def test_review_documents_create_requires_auth(self) -> None: client = self._client() r = client.post( "/api/v1/review-documents", json={"caseId": "CASE-1", "officialBieuMau": {}}, ) self.assertEqual(r.status_code, 401) def test_chat_requires_auth(self) -> None: client = self._client() r = client.post("/api/v1/chat", json={"message": "hello"}) self.assertEqual(r.status_code, 401) def test_analyze_compliance_requires_auth(self) -> None: client = self._client() r = client.post( "/analyze_compliance", json={"external_requirements": ["ext"], "internal_requirements": ["int"]}, ) self.assertEqual(r.status_code, 401) def test_test_ollama_requires_admin(self) -> None: client = self._client() viewer = {"Authorization": mint_bearer_token(roles=("viewer",))} r_viewer = client.post("/test_ollama", json={"prompt": "hi"}, headers=viewer) self.assertEqual(r_viewer.status_code, 403) admin = {"Authorization": mint_bearer_token(roles=("admin",))} with patch( "main.ollama.chat", return_value={"message": {"content": "ok"}}, ): r_admin = client.post("/test_ollama", json={"prompt": "hi"}, headers=admin) self.assertEqual(r_admin.status_code, 200, r_admin.text) def test_ideas_post_requires_admin(self) -> None: client = self._client() headers = {"Authorization": mint_bearer_token(roles=("viewer",))} r = client.post( "/api/v1/ideas", json={"title": "t", "description": "d"}, headers=headers, ) self.assertEqual(r.status_code, 403) def test_security_headers_on_health(self) -> None: client = self._client() r = client.get("/health") self.assertEqual(r.status_code, 200) self.assertEqual(r.headers.get("x-content-type-options"), "nosniff") self.assertEqual(r.headers.get("x-frame-options"), "DENY") self.assertIn("referrer-policy", r.headers) class JwtSecretTests(unittest.TestCase): def test_production_requires_secret(self) -> None: from src.auth_jwt import jwt_secret env = {k: v for k, v in os.environ.items() if k not in ("JWT_SECRET", "ENVIRONMENT")} env["ENVIRONMENT"] = "production" with patch.dict(os.environ, env, clear=True): with self.assertRaises(RuntimeError): jwt_secret() def test_development_allows_dev_fallback(self) -> None: from src.auth_jwt import jwt_secret with patch.dict(os.environ, {"ENVIRONMENT": "development"}, clear=False): os.environ.pop("JWT_SECRET", None) secret = jwt_secret() self.assertGreaterEqual(len(secret), 32) class LoginRateLimitTests(unittest.TestCase): def test_login_rate_limit_blocks_after_threshold(self) -> None: from src.auth_rate_limit import allow_login email = "ratelimit-test@ump.edu.vn" ip = "203.0.113.50" for _ in range(5): self.assertTrue(allow_login(email, ip)) self.assertFalse(allow_login(email, ip)) if __name__ == "__main__": unittest.main()