test_auth_routes.py (4144B)
1 """T015 — Tests for auth routes.""" 2 import os 3 import tempfile 4 5 import pytest 6 from httpx import ASGITransport, AsyncClient 7 from app.main import app 8 9 @pytest.fixture(autouse=True) 10 def _isolate_auth_config(tmp_path): 11 """Each test gets a clean config file.""" 12 config_path = str(tmp_path / "config.json") 13 os.environ["THEO_CONFIG_PATH"] = config_path 14 # Patch the module-level config path in routes/auth 15 import app.routes.auth as auth_module 16 old = auth_module._CONFIG_PATH 17 auth_module._CONFIG_PATH = config_path 18 yield 19 auth_module._CONFIG_PATH = old 20 os.environ.pop("THEO_CONFIG_PATH", None) 21 22 23 @pytest.fixture 24 async def client() -> AsyncClient: 25 """Fresh async client for each test.""" 26 async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: 27 yield c 28 29 30 class TestAuthStatus: 31 """GET /api/auth/status""" 32 33 async def test_status_unconfigured(self, client: AsyncClient) -> None: 34 """Returns configured: false when no auth config exists.""" 35 resp = await client.get("/api/auth/status") 36 assert resp.status_code == 200 37 body = resp.json() 38 assert "configured" in body 39 assert body["configured"] is False 40 assert isinstance(body["configured"], bool) 41 42 43 class TestAuthSetup: 44 """POST /api/auth/setup""" 45 46 async def test_setup_creates_password_and_returns_jwt(self, client: AsyncClient) -> None: 47 """First-run setup creates credentials and returns a JWT.""" 48 resp = await client.post("/api/auth/setup", json={ 49 "password": "testpass123", 50 "api_key": "sk-test-key", 51 }) 52 assert resp.status_code == 200 53 body = resp.json() 54 assert "token" in body 55 assert isinstance(body["token"], str) 56 assert len(body["token"]) > 0 57 58 async def test_setup_returns_400_if_already_configured(self, client: AsyncClient) -> None: 59 """Cannot setup twice.""" 60 await client.post("/api/auth/setup", json={ 61 "password": "pass1", 62 "api_key": "key1", 63 }) 64 resp = await client.post("/api/auth/setup", json={ 65 "password": "pass2", 66 "api_key": "key2", 67 }) 68 assert resp.status_code == 400 69 body = resp.json() 70 assert "detail" in body 71 assert "Already configured" in body["detail"] 72 73 74 class TestAuthLogin: 75 """POST /api/auth/login""" 76 77 async def test_login_correct_password_returns_jwt(self, client: AsyncClient) -> None: 78 """Valid login returns a JWT.""" 79 await client.post("/api/auth/setup", json={ 80 "password": "mypassword", 81 "api_key": "sk-key", 82 }) 83 resp = await client.post("/api/auth/login", json={"password": "mypassword"}) 84 assert resp.status_code == 200 85 body = resp.json() 86 assert "token" in body 87 assert isinstance(body["token"], str) 88 assert len(body["token"]) > 0 89 90 async def test_login_wrong_password_returns_401(self, client: AsyncClient) -> None: 91 """Invalid password returns 401.""" 92 await client.post("/api/auth/setup", json={ 93 "password": "correct", 94 "api_key": "sk-key", 95 }) 96 resp = await client.post("/api/auth/login", json={"password": "wrong"}) 97 assert resp.status_code == 401 98 body = resp.json() 99 assert "detail" in body 100 assert "Invalid password" in body["detail"] 101 102 async def test_login_not_configured_returns_401(self, client: AsyncClient) -> None: 103 """Login before setup returns 401.""" 104 resp = await client.post("/api/auth/login", json={"password": "anything"}) 105 assert resp.status_code == 401 106 body = resp.json() 107 assert "detail" in body 108 assert "Not configured" in body["detail"] 109 110 111 class TestAuthLogout: 112 """POST /api/auth/logout""" 113 114 async def test_logout_clears_cookie(self, client: AsyncClient) -> None: 115 """Logout clears the JWT cookie.""" 116 resp = await client.post("/api/auth/logout") 117 assert resp.status_code == 200 118 body = resp.json() 119 assert body == {"ok": True}