File size: 5,759 Bytes
5b98477 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | """
Tests — Feature 2.2: JWT Authentication
"""
import pytest
class TestRegister:
async def test_register_success(self, client, db_tables):
resp = await client.post("/auth/register", json={
"email": "new@test.com",
"password": "Newpass1",
"full_name": "New User",
})
assert resp.status_code == 201
assert "successfully" in resp.json()["message"]
async def test_register_duplicate_email(self, client, seeded_db):
# First registration
await client.post("/auth/register", json={
"email": "dup@test.com", "password": "Dup12345", "full_name": "Dup"
})
# Second with same email
resp = await client.post("/auth/register", json={
"email": "dup@test.com", "password": "Dup12345", "full_name": "Dup2"
})
assert resp.status_code == 409
async def test_register_invalid_email(self, client, db_tables):
resp = await client.post("/auth/register", json={
"email": "not-an-email", "password": "Valid123", "full_name": "ValidName"
})
assert resp.status_code == 422
async def test_register_password_too_short(self, client, db_tables):
resp = await client.post("/auth/register", json={
"email": "short@test.com", "password": "Ab1", "full_name": "Short User"
})
assert resp.status_code == 422
async def test_register_password_no_digit(self, client, db_tables):
resp = await client.post("/auth/register", json={
"email": "nodigit@test.com", "password": "NoDigitPass", "full_name": "No Digit"
})
assert resp.status_code == 422
async def test_register_normalises_email_to_lowercase(self, client, db_tables):
resp = await client.post("/auth/register", json={
"email": "UPPER@TEST.COM", "password": "Upper123", "full_name": "UpperUser"
})
assert resp.status_code == 201
class TestLogin:
async def test_login_success(self, client, seeded_db):
resp = await client.post("/auth/login", json={
"email": "doctor@test.com", "password": "Doctor1234"
})
assert resp.status_code == 200
data = resp.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
assert data["expires_in"] > 0
async def test_login_sets_httponly_cookies(self, client, seeded_db):
resp = await client.post("/auth/login", json={
"email": "doctor@test.com", "password": "Doctor1234"
})
assert resp.status_code == 200
assert "access_token" in resp.cookies
assert "refresh_token" in resp.cookies
async def test_login_wrong_password(self, client, seeded_db):
resp = await client.post("/auth/login", json={
"email": "doctor@test.com", "password": "WrongPass1"
})
assert resp.status_code == 401
async def test_login_unknown_email(self, client, seeded_db):
resp = await client.post("/auth/login", json={
"email": "ghost@test.com", "password": "Ghost1234"
})
# Same error as wrong password (prevents user enumeration)
assert resp.status_code == 401
async def test_login_case_insensitive_email(self, client, seeded_db):
resp = await client.post("/auth/login", json={
"email": "DOCTOR@TEST.COM", "password": "Doctor1234"
})
assert resp.status_code == 200
class TestGetMe:
async def test_get_me_authenticated(self, client, doctor_token):
resp = await client.get(
"/auth/me",
headers={"Authorization": f"Bearer {doctor_token}"},
)
assert resp.status_code == 200
data = resp.json()
assert data["email"] == "doctor@test.com"
assert data["is_active"] is True
async def test_get_me_unauthenticated(self, client, db_tables):
resp = await client.get("/auth/me")
assert resp.status_code == 401
async def test_get_me_invalid_token(self, client, db_tables):
resp = await client.get(
"/auth/me",
headers={"Authorization": "Bearer this.is.invalid"},
)
assert resp.status_code == 401
async def test_get_me_returns_roles(self, client, admin_token):
resp = await client.get(
"/auth/me",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert resp.status_code == 200
roles = [r["name"] for r in resp.json()["roles"]]
assert "super_admin" in roles
class TestRefreshAndLogout:
async def test_refresh_issues_new_token(self, client, seeded_db):
# Log in to get cookies
login_resp = await client.post("/auth/login", json={
"email": "doctor@test.com", "password": "Doctor1234"
})
assert login_resp.status_code == 200
refresh_cookie = login_resp.cookies.get("refresh_token")
resp = await client.post(
"/auth/refresh",
cookies={"refresh_token": refresh_cookie},
)
assert resp.status_code == 200
assert "access_token" in resp.json()
async def test_refresh_without_cookie_returns_401(self, client, db_tables):
resp = await client.post("/auth/refresh")
assert resp.status_code == 401
async def test_logout_clears_cookies(self, client, seeded_db):
await client.post("/auth/login", json={
"email": "doctor@test.com", "password": "Doctor1234"
})
resp = await client.post("/auth/logout")
assert resp.status_code == 200
# Cookies should be cleared (set to empty / expired)
assert resp.json()["message"] == "Logged out successfully"
|