8fc3d9adda
- New test_existing_endpoints_require_auth.py: spot-check that existing /api/* endpoints now require auth (gated via Depends(matrix_gate)) when AUTH_DISABLED is False. Health remains public. - conftest.py: flip AUTH_DISABLED=True for the suite so the legacy pre-auth tests keep passing without login. Auth tests flip it back off via their own autouse fixture (now patched to use monkeypatch for cleanup). Verified: 53 auth tests pass; 222 pre-existing non-auth failures are unchanged.
116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
"""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
|