An expired or orphaned session left the app on a half-rendered page instead of the login screen: a properly signed token naming a user who no longer exists answered 404, which the client does not treat as an authentication failure. All failures in get_current_user are 401 now, and the client stops retrying them, so a dead session goes straight back to the login screen. - Clicking an edge names the two nodes it runs between, not just the message. - A node with a problem shows one badge carrying the whole explanation, rather than a corner badge and a status dot saying the same thing twice. The dot is back to what it is good at: whether the node ran. - The sidebar's collapse control sits in the sidebar, where a phone still finds one in the chrome because there is no sidebar on screen to hold it.
46 lines
1.4 KiB
Python
46 lines
1.4 KiB
Python
"""A session the server will not accept must say so as an auth failure.
|
|
|
|
The client clears the stored token and returns to the login screen on 401. Any
|
|
other status leaves the browser holding a token that can never work, stuck on a
|
|
page that will not load.
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import timedelta
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.core import security
|
|
from app.core.config import settings
|
|
|
|
PROTECTED = [
|
|
f"{settings.API_V1_STR}/users/me",
|
|
f"{settings.API_V1_STR}/flows/",
|
|
]
|
|
|
|
|
|
def token_for(subject: str) -> str:
|
|
return security.create_access_token(subject, timedelta(days=1))
|
|
|
|
|
|
def test_no_token_is_unauthorised(client: TestClient) -> None:
|
|
for path in PROTECTED:
|
|
assert client.get(path).status_code == 401
|
|
|
|
|
|
def test_a_token_we_did_not_sign_is_unauthorised(client: TestClient) -> None:
|
|
headers = {"Authorization": "Bearer not.a.real.token"}
|
|
for path in PROTECTED:
|
|
assert client.get(path, headers=headers).status_code == 401
|
|
|
|
|
|
def test_a_token_for_a_user_who_is_gone_is_unauthorised(
|
|
client: TestClient,
|
|
) -> None:
|
|
# Properly signed, but the user it names no longer exists — which is what a
|
|
# browser holds after the database is reset.
|
|
headers = {"Authorization": f"Bearer {token_for(str(uuid.uuid4()))}"}
|
|
for path in PROTECTED:
|
|
response = client.get(path, headers=headers)
|
|
assert response.status_code == 401, path
|