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"