"""End-to-End-Test des Login-Flows (Passwort -> TOTP-Enrollment -> Session) gegen die echte FastAPI-App mit einer temporaeren SQLite-DB. Die `client`-Fixture liegt in conftest.py (von mehreren Testdateien genutzt, u.a. tests/test_pentest_security.py).""" import pyotp import pytest @pytest.mark.asyncio async def test_full_login_flow_with_totp_enrollment(client, monkeypatch): from app.db import get_db from app.security.passwords import hash_password conn = get_db() await conn.execute( "INSERT INTO users (username, password_hash, must_change_password) VALUES (?, ?, 0)", ("alice", hash_password("Correct-Horse-Battery-Staple-1")), ) await conn.commit() resp = await client.post( "/auth/login", json={"username": "alice", "password": "Correct-Horse-Battery-Staple-1"} ) assert resp.status_code == 200, resp.text body = resp.json() assert body["totp_enrolled"] is False pending_token = body["pending_token"] resp = await client.post("/auth/totp/enroll/start", json={"pending_token": pending_token}) assert resp.status_code == 200, resp.text provisioning_uri = resp.json()["provisioning_uri"] secret = dict(part.split("=") for part in provisioning_uri.split("?", 1)[1].split("&"))["secret"] code = pyotp.TOTP(secret).now() resp = await client.post( "/auth/totp/enroll/confirm", json={"pending_token": pending_token, "code": code} ) assert resp.status_code == 200, resp.text assert len(resp.json()["recovery_codes"]) == 10 assert "jh_session" in resp.cookies resp = await client.get("/auth/me") assert resp.status_code == 200, resp.text assert resp.json()["username"] == "alice" @pytest.mark.asyncio async def test_wrong_password_rejected(client): from app.db import get_db from app.security.passwords import hash_password conn = get_db() await conn.execute( "INSERT INTO users (username, password_hash, must_change_password) VALUES (?, ?, 0)", ("bob", hash_password("Correct-Horse-Battery-Staple-2")), ) await conn.commit() resp = await client.post("/auth/login", json={"username": "bob", "password": "wrong"}) assert resp.status_code == 401 @pytest.mark.asyncio async def test_unauthenticated_admin_access_denied(client): resp = await client.get("/admin/users") assert resp.status_code == 401