"""API tests for /api/auth/login, /api/auth/logout, /api/auth/me.""" from __future__ import annotations import pytest from fastapi.testclient import TestClient from sqlalchemy import delete from cyclone.api import app from cyclone.auth import users from cyclone.db import Session as DbSession from cyclone.db import SessionLocal, User @pytest.fixture(autouse=True) def _clear(monkeypatch): # conftest sets AUTH_DISABLED=True so pre-auth tests keep passing # without a login. The auth-route tests need the real auth path # exercised, so flip it back off here. monkeypatch.setattr("cyclone.auth.deps.AUTH_DISABLED", False) with SessionLocal()() as db: db.execute(delete(DbSession)) db.execute(delete(User)) db.commit() yield with SessionLocal()() as db: db.execute(delete(DbSession)) db.execute(delete(User)) db.commit() @pytest.fixture def client(): return TestClient(app) @pytest.fixture def seeded_admin(client): with SessionLocal()() as db: users.create(db, username="admin", password="adminpassword1", role="admin") return client def test_login_success_returns_user_and_cookie(client): with SessionLocal()() as db: users.create(db, username="alice", password="hunter2hunter2", role="user") resp = client.post( "/api/auth/login", json={"username": "alice", "password": "hunter2hunter2"}, ) assert resp.status_code == 200 body = resp.json() assert body["username"] == "alice" assert body["role"] == "user" assert "password_hash" not in body assert "cyclone_session" in resp.cookies def test_login_bad_password_returns_401(client): with SessionLocal()() as db: users.create(db, username="bob", password="hunter2hunter2", role="user") resp = client.post( "/api/auth/login", json={"username": "bob", "password": "WRONG"}, ) assert resp.status_code == 401 assert resp.json()["error"] == "invalid_credentials" def test_login_unknown_user_returns_401(client): resp = client.post( "/api/auth/login", json={"username": "ghost", "password": "whatever"}, ) assert resp.status_code == 401 assert resp.json()["error"] == "invalid_credentials" def test_login_disabled_user_returns_403(client): with SessionLocal()() as db: u = users.create(db, username="carol", password="hunter2hunter2", role="user") users.disable(db, u.id) resp = client.post( "/api/auth/login", json={"username": "carol", "password": "hunter2hunter2"}, ) assert resp.status_code == 403 assert resp.json()["error"] == "account_disabled" def test_logout_clears_session_and_cookie(seeded_admin): login = seeded_admin.post( "/api/auth/login", json={"username": "admin", "password": "adminpassword1"}, ) cookie = login.cookies.get("cyclone_session") assert cookie resp = seeded_admin.post("/api/auth/logout", cookies={"cyclone_session": cookie}) assert resp.status_code == 204 def test_me_returns_current_user(seeded_admin): login = seeded_admin.post( "/api/auth/login", json={"username": "admin", "password": "adminpassword1"}, ) cookie = login.cookies.get("cyclone_session") resp = seeded_admin.get("/api/auth/me", cookies={"cyclone_session": cookie}) assert resp.status_code == 200 assert resp.json()["username"] == "admin" def test_me_without_cookie_returns_401(seeded_admin): resp = seeded_admin.get("/api/auth/me") assert resp.status_code == 401