""" Tests for authentication, registration, password change, logout, OIDC, and MFA endpoints. """ import uuid import pytest pytestmark = pytest.mark.anyio # --------------------------------------------------------------------------- # Helper: create a disposable user, login, return (user_id, token, headers) # --------------------------------------------------------------------------- async def _make_temp_user(client, admin_headers, *, password="Test1234!"): """Register a unique user and log them in. Returns (uid, token, headers).""" uname = f"tmp_{uuid.uuid4().hex[:10]}" r = await client.post("/api/auth/register", json={ "first_name": "Tmp", "last_name": "User", "username": uname, "email": f"{uname}@test.com", "password": password, "role": "user", }, headers=admin_headers) assert r.status_code == 200, r.text uid = r.json()["id"] r2 = await client.post("/api/auth/login", json={"username": uname, "password": password}) assert r2.status_code == 200, r2.text token = r2.json()["token"] return uid, token, {"Authorization": f"Bearer {token}"} # ── Health ───────────────────────────────────────────────────────────────────── async def test_health_check(client): r = await client.get("/api/health") assert r.status_code == 200 body = r.json() assert body["status"] == "ok" assert body["db_ok"] is True # ── Login ────────────────────────────────────────────────────────────────────── async def test_login_success(client): r = await client.post("/api/auth/login", json={"username": "admin", "password": "admin123"}) assert r.status_code == 200 body = r.json() assert "token" in body assert "user" in body async def test_login_wrong_password(client): r = await client.post("/api/auth/login", json={"username": "admin", "password": "wrong"}) assert r.status_code == 401 async def test_login_nonexistent_user(client): r = await client.post("/api/auth/login", json={"username": "nobody", "password": "x"}) assert r.status_code == 401 async def test_login_returns_must_change_password(client): r = await client.post("/api/auth/login", json={"username": "admin", "password": "admin123"}) assert r.status_code == 200 body = r.json() assert "must_change_password" in body # ── Register ─────────────────────────────────────────────────────────────────── async def test_register_user_as_admin(client, admin_headers): uname = f"regtest_{uuid.uuid4().hex[:8]}" r = await client.post("/api/auth/register", json={ "first_name": "Reg", "last_name": "Test", "username": uname, "email": f"{uname}@test.com", "password": "Reg12345!", "role": "user", }, headers=admin_headers) assert r.status_code == 200 body = r.json() assert body["username"] == uname assert body["role"] == "user" async def test_register_duplicate_username(client, admin_headers): uname = f"dup_{uuid.uuid4().hex[:8]}" payload = { "first_name": "Dup", "last_name": "Test", "username": uname, "email": f"{uname}@test.com", "password": "Dup12345!", "role": "user", } r1 = await client.post("/api/auth/register", json=payload, headers=admin_headers) assert r1.status_code == 200 r2 = await client.post("/api/auth/register", json=payload, headers=admin_headers) assert r2.status_code == 400 async def test_register_as_non_admin(client, user_headers): r = await client.post("/api/auth/register", json={ "first_name": "No", "last_name": "Way", "username": "nope", "email": "nope@test.com", "password": "Nope1234!", "role": "user", }, headers=user_headers) assert r.status_code == 403 async def test_register_invalid_role(client, admin_headers): r = await client.post("/api/auth/register", json={ "first_name": "Bad", "last_name": "Role", "username": f"badrole_{uuid.uuid4().hex[:8]}", "email": "bad@test.com", "password": "Bad12345!", "role": "superadmin", }, headers=admin_headers) assert r.status_code == 400 # ── Password change ──────────────────────────────────────────────────────────── async def test_change_password_success(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "NewPass1!", }, headers=hdrs) assert r.status_code == 200 async def test_change_password_wrong_current(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "WrongOld!", "new_password": "NewPass1!", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_too_short(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "Ab1!", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_no_uppercase(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "abcdefg1!", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_no_lowercase(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "ABCDEFG1!", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_no_number(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "Abcdefgh!", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_no_special(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "Abcdefg1", }, headers=hdrs) assert r.status_code == 400 async def test_change_password_same_as_old(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/change-password", json={ "current_password": "Test1234!", "new_password": "Test1234!", }, headers=hdrs) assert r.status_code == 400 # ── Logout ───────────────────────────────────────────────────────────────────── async def test_logout(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/auth/logout", headers=hdrs) assert r.status_code == 200 assert r.json().get("ok") is True async def test_request_after_logout(client, admin_headers): _, token, hdrs = await _make_temp_user(client, admin_headers) await client.post("/api/auth/logout", headers=hdrs) r = await client.get("/api/users/me", headers={"Authorization": f"Bearer {token}"}) assert r.status_code == 401 # ── OIDC ─────────────────────────────────────────────────────────────────────── async def test_oidc_config_public(client): r = await client.get("/api/auth/oidc/config") assert r.status_code == 200 assert "auth_mode" in r.json() async def test_oidc_login_disabled(client): r = await client.get("/api/auth/oidc/login", follow_redirects=False) assert r.status_code == 400 # ── MFA ──────────────────────────────────────────────────────────────────────── async def test_mfa_setup(client, admin_headers): _, _, hdrs = await _make_temp_user(client, admin_headers) r = await client.post("/api/mfa/setup", headers=hdrs) assert r.status_code == 200 body = r.json() assert "secret" in body assert "uri" in body async def test_mfa_disable(client, admin_headers): uid, _, _ = await _make_temp_user(client, admin_headers) r = await client.post(f"/api/mfa/disable/{uid}", headers=admin_headers) assert r.status_code == 200 assert r.json().get("ok") is True