1fee29358f
api_client._handle_response: su 401 pulisce la sessione Flask e marca g.session_expired. Un solo after_request (app.py) trasforma le pagine HTML rimaste in piedi con un errore generico in un redirect al login con flash; le route JSON/AJAX che già inoltrano il dict di api_client (measure.py, statistics.py) ricevono session_expired nel corpo insieme al 401. Backend (riuso della key, bcrypt a tempo costante) resta al lotto A2. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
"""Tests for the 401 -> session-expired handling (#25, frontend half).
|
|
|
|
Backend half (auth_service key reuse, constant-time auth) is lot A2 and
|
|
lives in src/backend - not covered here.
|
|
"""
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from flask import g, session
|
|
|
|
|
|
def _mock_401_response(detail="Invalid or expired API key"):
|
|
resp = MagicMock()
|
|
resp.ok = False
|
|
resp.status_code = 401
|
|
resp.json.return_value = {"detail": detail}
|
|
resp.text = detail
|
|
return resp
|
|
|
|
|
|
class TestApiClientHandlesResponse:
|
|
"""Unit-level: services/api_client.py clears the session on 401."""
|
|
|
|
def test_401_clears_session_and_flags_g(self, flask_app):
|
|
from services.api_client import api_client
|
|
|
|
with flask_app.test_request_context("/"):
|
|
session["api_key"] = "stale-key"
|
|
session["user"] = {"id": 1, "username": "op1"}
|
|
|
|
result = api_client._handle_response(_mock_401_response())
|
|
|
|
assert result["error"] is True
|
|
assert result["status_code"] == 401
|
|
assert result["session_expired"] is True
|
|
assert "api_key" not in session
|
|
assert "user" not in session
|
|
assert g.session_expired is True
|
|
|
|
def test_non_401_error_does_not_touch_session(self, flask_app):
|
|
from services.api_client import api_client
|
|
|
|
with flask_app.test_request_context("/"):
|
|
session["api_key"] = "still-good"
|
|
|
|
resp = MagicMock(ok=False, status_code=404)
|
|
resp.json.return_value = {"detail": "not found"}
|
|
result = api_client._handle_response(resp)
|
|
|
|
assert "session_expired" not in result
|
|
assert session.get("api_key") == "still-good"
|
|
assert not getattr(g, "session_expired", False)
|
|
|
|
|
|
class TestHtmlPageRedirectsOnSessionExpired:
|
|
"""Integration: a page view that got a 401 ends up redirected to login."""
|
|
|
|
def test_recipe_list_401_redirects_to_login(self, logged_in_client):
|
|
with patch("services.api_client.requests.get", return_value=_mock_401_response()):
|
|
resp = logged_in_client.get("/maker/recipes", follow_redirects=False)
|
|
|
|
assert resp.status_code == 302
|
|
assert "/auth/login" in resp.headers["Location"]
|
|
|
|
def test_recipe_list_401_empties_the_session(self, logged_in_client):
|
|
with patch("services.api_client.requests.get", return_value=_mock_401_response()):
|
|
logged_in_client.get("/maker/recipes", follow_redirects=False)
|
|
|
|
with logged_in_client.session_transaction() as sess:
|
|
assert "api_key" not in sess
|
|
assert "user" not in sess
|
|
|
|
def test_recipe_list_401_flashes_session_expired_message(self, logged_in_client):
|
|
with patch("services.api_client.requests.get", return_value=_mock_401_response()):
|
|
resp = logged_in_client.get("/maker/recipes", follow_redirects=True)
|
|
|
|
assert "Sessione scaduta" in resp.get_data(as_text=True)
|
|
|
|
|
|
class TestJsonProxyReturns401WithFlag:
|
|
"""Integration: an AJAX proxy route surfaces session_expired in its JSON body."""
|
|
|
|
def test_save_measurement_401_returns_session_expired_flag(self, logged_in_client):
|
|
with patch("services.api_client.requests.post", return_value=_mock_401_response()):
|
|
resp = logged_in_client.post(
|
|
"/measure/save-measurement",
|
|
json={"subtask_id": 1, "version_id": 1, "value": 1.0},
|
|
)
|
|
|
|
assert resp.status_code == 401
|
|
body = resp.get_json()
|
|
assert body["error"] is True
|
|
assert body["session_expired"] is True
|
|
|
|
def test_statistics_summary_401_returns_session_expired_flag(self, logged_in_client):
|
|
with patch("services.api_client.requests.get", return_value=_mock_401_response()):
|
|
resp = logged_in_client.get("/statistics/api/summary")
|
|
|
|
assert resp.status_code == 401
|
|
body = resp.get_json()
|
|
assert body["session_expired"] is True
|