after audit

This commit is contained in:
Justin Visser 2026-03-01 18:14:22 +01:00
parent e8e20637e6
commit a5165dc3ba
80 changed files with 8489 additions and 7302 deletions

View file

@ -0,0 +1,182 @@
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.main import app
from tests.integration.helpers import (
api_bootstrap_csrf_headers,
insert_user,
login_user,
)
@pytest.mark.integration
@pytest.mark.db
@pytest.mark.asyncio
async def test_api_me_requires_authentication() -> None:
client = TestClient(app)
response = client.get("/api/v1/auth/me")
assert response.status_code == 401
payload = response.json()
assert payload["error"]["code"] == "auth_required"
assert payload["error"]["message"] == "Authentication required."
assert "request_id" in payload["meta"]
@pytest.mark.integration
@pytest.mark.db
@pytest.mark.asyncio
async def test_api_envelope_contract_for_success_and_csrf_error() -> None:
client = TestClient(app)
success_response = client.get("/api/v1/auth/csrf")
assert success_response.status_code == 200
success_payload = success_response.json()
assert set(success_payload.keys()) == {"data", "meta"}
assert isinstance(success_payload["data"]["csrf_token"], str)
assert set(success_payload["meta"].keys()) == {"request_id"}
error_response = client.post(
"/api/v1/auth/login",
json={"email": "missing-csrf@example.com", "password": "irrelevant"},
)
assert error_response.status_code == 403
error_payload = error_response.json()
assert set(error_payload.keys()) == {"error", "meta"}
assert set(error_payload["error"].keys()) == {"code", "message", "details"}
assert error_payload["error"]["code"] == "csrf_invalid"
assert error_payload["error"]["details"] is None
assert set(error_payload["meta"].keys()) == {"request_id"}
@pytest.mark.integration
@pytest.mark.db
@pytest.mark.asyncio
async def test_api_csrf_bootstrap_returns_token_for_anonymous_and_authenticated(
db_session: AsyncSession,
) -> None:
await insert_user(
db_session,
email="api-bootstrap@example.com",
password="api-password",
)
client = TestClient(app)
anonymous_response = client.get("/api/v1/auth/csrf")
assert anonymous_response.status_code == 200
anonymous_payload = anonymous_response.json()["data"]
assert isinstance(anonymous_payload["csrf_token"], str)
assert anonymous_payload["authenticated"] is False
login_user(client, email="api-bootstrap@example.com", password="api-password")
authenticated_response = client.get("/api/v1/auth/csrf")
assert authenticated_response.status_code == 200
authenticated_payload = authenticated_response.json()["data"]
assert isinstance(authenticated_payload["csrf_token"], str)
assert authenticated_payload["authenticated"] is True
@pytest.mark.integration
@pytest.mark.db
@pytest.mark.asyncio
async def test_api_me_returns_user_and_csrf_token(db_session: AsyncSession) -> None:
await insert_user(
db_session,
email="api-me@example.com",
password="api-password",
)
client = TestClient(app)
login_user(client, email="api-me@example.com", password="api-password")
response = client.get("/api/v1/auth/me")
assert response.status_code == 200
payload = response.json()
assert payload["data"]["authenticated"] is True
assert payload["data"]["user"]["email"] == "api-me@example.com"
assert isinstance(payload["data"]["csrf_token"], str)
assert payload["meta"]["request_id"]
@pytest.mark.integration
@pytest.mark.db
@pytest.mark.asyncio
async def test_api_login_and_change_password_flow(db_session: AsyncSession) -> None:
await insert_user(
db_session,
email="api-login@example.com",
password="old-password",
)
client = TestClient(app)
missing_csrf = client.post(
"/api/v1/auth/login",
json={"email": "api-login@example.com", "password": "old-password"},
)
assert missing_csrf.status_code == 403
assert missing_csrf.json()["error"]["code"] == "csrf_missing"
login_headers = api_bootstrap_csrf_headers(client)
login_response = client.post(
"/api/v1/auth/login",
json={"email": "api-login@example.com", "password": "old-password"},
headers=login_headers,
)
assert login_response.status_code == 200
login_payload = login_response.json()["data"]
assert login_payload["authenticated"] is True
assert login_payload["user"]["email"] == "api-login@example.com"
assert isinstance(login_payload["csrf_token"], str)
bad_change_response = client.post(
"/api/v1/auth/change-password",
json={
"current_password": "not-correct",
"new_password": "new-password",
"confirm_password": "new-password",
},
headers={"X-CSRF-Token": login_payload["csrf_token"]},
)
assert bad_change_response.status_code == 400
assert bad_change_response.json()["error"]["code"] == "invalid_current_password"
change_response = client.post(
"/api/v1/auth/change-password",
json={
"current_password": "old-password",
"new_password": "new-password",
"confirm_password": "new-password",
},
headers={"X-CSRF-Token": login_payload["csrf_token"]},
)
assert change_response.status_code == 200
assert change_response.json()["data"]["message"] == "Password updated successfully."
logout_response = client.post(
"/api/v1/auth/logout",
headers={"X-CSRF-Token": login_payload["csrf_token"]},
)
assert logout_response.status_code == 200
relogin_headers = api_bootstrap_csrf_headers(client)
old_password_login = client.post(
"/api/v1/auth/login",
json={"email": "api-login@example.com", "password": "old-password"},
headers=relogin_headers,
)
assert old_password_login.status_code == 401
assert old_password_login.json()["error"]["code"] == "invalid_credentials"
fresh_headers = api_bootstrap_csrf_headers(client)
new_password_login = client.post(
"/api/v1/auth/login",
json={"email": "api-login@example.com", "password": "new-password"},
headers=fresh_headers,
)
assert new_password_login.status_code == 200
assert new_password_login.json()["data"]["authenticated"] is True