224 lines
9.1 KiB
Python
224 lines
9.1 KiB
Python
"""
|
|
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
|