Skip to content

Commit 715119a

Browse files
authored
Create test_revocation.py
1 parent b4cadb0 commit 715119a

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

‎tests/test_revocation.py‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Token revocation via token_version: logout and password change."""
2+
3+
from __future__ import annotations
4+
5+
from fastapi.testclient import TestClient
6+
7+
8+
def _register(client: TestClient, email: str = "rev@example.com"):
9+
tokens = client.post("/auth/register", json={"email": email, "password": "password-1"}).json()
10+
return tokens["access_token"], tokens["refresh_token"]
11+
12+
13+
class TestLogoutRevocation:
14+
def test_logout_invalidates_access_and_refresh(self, client: TestClient) -> None:
15+
access, refresh = _register(client)
16+
h = {"Authorization": f"Bearer {access}"}
17+
assert client.get("/auth/me", headers=h).status_code == 200
18+
19+
assert client.post("/auth/logout", headers=h).status_code == 200
20+
21+
# both the access token and the refresh token are now dead
22+
assert client.get("/auth/me", headers=h).status_code == 401
23+
assert client.post("/auth/refresh", json={"refresh_token": refresh}).status_code == 401
24+
25+
def test_fresh_login_after_logout_works(self, client: TestClient) -> None:
26+
access, _ = _register(client, "relog@example.com")
27+
client.post("/auth/logout", headers={"Authorization": f"Bearer {access}"})
28+
# a new login mints tokens at the new version
29+
res = client.post(
30+
"/auth/login", json={"email": "relog@example.com", "password": "password-1"}
31+
)
32+
assert res.status_code == 200
33+
new = res.json()["access_token"]
34+
assert client.get("/auth/me", headers={"Authorization": f"Bearer {new}"}).status_code == 200
35+
36+
37+
class TestPasswordChangeRevocation:
38+
def test_change_password_revokes_old_tokens(self, client: TestClient) -> None:
39+
access, refresh = _register(client, "pw@example.com")
40+
h = {"Authorization": f"Bearer {access}"}
41+
res = client.post(
42+
"/auth/password",
43+
headers=h,
44+
json={"old_password": "password-1", "new_password": "password-2"},
45+
)
46+
assert res.status_code == 200
47+
# old sessions are gone
48+
assert client.get("/auth/me", headers=h).status_code == 401
49+
assert client.post("/auth/refresh", json={"refresh_token": refresh}).status_code == 401
50+
# new password works, old doesn't
51+
assert (
52+
client.post(
53+
"/auth/login", json={"email": "pw@example.com", "password": "password-2"}
54+
).status_code
55+
== 200
56+
)
57+
assert (
58+
client.post(
59+
"/auth/login", json={"email": "pw@example.com", "password": "password-1"}
60+
).status_code
61+
== 401
62+
)
63+
64+
def test_wrong_current_password_rejected(self, client: TestClient) -> None:
65+
access, _ = _register(client, "pw2@example.com")
66+
res = client.post(
67+
"/auth/password",
68+
headers={"Authorization": f"Bearer {access}"},
69+
json={"old_password": "wrong-one", "new_password": "password-2"},
70+
)
71+
assert res.status_code == 401
72+
# unchanged: original password still works
73+
assert (
74+
client.post(
75+
"/auth/login", json={"email": "pw2@example.com", "password": "password-1"}
76+
).status_code
77+
== 200
78+
)
79+
80+
81+
class TestRefreshStillWorksNormally:
82+
def test_refresh_rotates_without_revocation(self, client: TestClient) -> None:
83+
_, refresh = _register(client, "norm@example.com")
84+
res = client.post("/auth/refresh", json={"refresh_token": refresh})
85+
assert res.status_code == 200
86+
new_access = res.json()["access_token"]
87+
assert (
88+
client.get("/auth/me", headers={"Authorization": f"Bearer {new_access}"}).status_code
89+
== 200
90+
)

0 commit comments

Comments
 (0)