Files
TieMeasureFlow/src/frontend/flask_app/tests/test_auth.py
T
Adriano 92f130479a fix(auth): SECRET_KEY di default rifiutato, session.permanent, logout solo POST (#33)
config.py: guard_default_secret_key, verificata a ogni richiesta (prima di
create_app, TESTING/DEBUG non sono ancora impostati dai fixture di test) —
rifiuta con RuntimeError se CLIENT_SECRET_KEY è rimasto al default committato
fuori da debug/testing. auth.py: session.permanent=True al login (senza,
PERMANENT_SESSION_LIFETIME non aveva mai effetto); /auth/logout solo POST,
con CSRF. Aggiornati i tre punti che lo chiamavano: profile.html e
navbar.html (link -> form POST), auto-logout JS in base.html (fetch POST +
redirect al login).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:37:23 +02:00

139 lines
5.1 KiB
Python

"""Tests for auth blueprint (client/blueprints/auth.py).
Covers login GET/POST, logout, unauthenticated redirect, and language/theme switch.
"""
import pytest
from unittest.mock import patch
class TestLoginPage:
"""GET /auth/login tests."""
def test_login_page_renders(self, client):
"""Login page renders successfully with 200 status."""
resp = client.get("/auth/login")
assert resp.status_code == 200
assert b"login" in resp.data.lower() or b"Login" in resp.data
def test_login_page_redirects_when_already_logged_in(self, logged_in_client):
"""Already authenticated user is redirected away from login."""
resp = logged_in_client.get("/auth/login")
assert resp.status_code == 302
class TestLoginPost:
"""POST /auth/login tests."""
def test_login_post_success_redirects(self, client, mock_api_client):
"""Successful login POST sets session and redirects."""
mock_api_client.post.return_value = {
"api_key": "new-api-key-abc",
"user": {
"id": 1,
"username": "testuser",
"display_name": "Test User",
"roles": ["MeasurementTec"],
"is_admin": False,
"language_pref": "en",
"theme_pref": "light",
"active": True,
},
}
# Mock settings call made after login
mock_api_client.get.return_value = {}
resp = client.post(
"/auth/login",
data={"username": "testuser", "password": "pass123"},
follow_redirects=False,
)
assert resp.status_code == 302
mock_api_client.post.assert_called_once()
# Verify session was populated
with client.session_transaction() as sess:
assert sess.get("api_key") == "new-api-key-abc"
assert sess["user"]["username"] == "testuser"
# #33: without this, PERMANENT_SESSION_LIFETIME (8h) never applied
# and Flask used a browser-length session instead.
assert sess.permanent is True
def test_login_post_error_shows_error(self, client, mock_api_client):
"""Failed login returns the login page with error flash."""
mock_api_client.post.return_value = {
"error": True,
"detail": "Credenziali non valide",
}
resp = client.post(
"/auth/login",
data={"username": "bad", "password": "wrong"},
follow_redirects=True,
)
assert resp.status_code == 200
# Error message should appear in the response (flashed)
assert b"Credenziali non valide" in resp.data or b"login" in resp.data.lower()
def test_login_post_empty_fields(self, client, mock_api_client):
"""Login with empty username/password shows validation error."""
resp = client.post(
"/auth/login",
data={"username": "", "password": ""},
follow_redirects=True,
)
assert resp.status_code == 200
# Should render login again (not crash)
class TestLogout:
"""POST /auth/logout tests (#33: GET is refused - logout must carry CSRF)."""
def test_logout_clears_session(self, logged_in_client, mock_api_client):
"""Logout clears session and redirects to login."""
mock_api_client.post.return_value = {}
resp = logged_in_client.post("/auth/logout", follow_redirects=False)
assert resp.status_code == 302
assert "/auth/login" in resp.headers["Location"]
# Session should be cleared
with logged_in_client.session_transaction() as sess:
assert "api_key" not in sess
assert "user" not in sess
def test_logout_get_is_rejected(self, logged_in_client):
"""#33: a bare GET (link, prefetch, forged <img>) no longer logs anyone out."""
resp = logged_in_client.get("/auth/logout", follow_redirects=False)
assert resp.status_code == 405
class TestUnauthenticatedRedirect:
"""Protected routes redirect unauthenticated users."""
def test_profile_requires_login(self, client):
"""Profile page redirects to login when not authenticated."""
resp = client.get("/auth/profile", follow_redirects=False)
assert resp.status_code == 302
assert "/auth/login" in resp.headers["Location"]
class TestLanguageSwitch:
"""Language and theme switching via app routes."""
def test_set_language_stores_in_session(self, client):
"""Setting language updates session and redirects back."""
resp = client.get("/set-language/en", follow_redirects=False)
assert resp.status_code == 302
with client.session_transaction() as sess:
assert sess.get("language") == "en"
def test_set_language_invalid_ignored(self, client):
"""Setting an unsupported language does not crash and still redirects."""
resp = client.get("/set-language/xx", follow_redirects=False)
assert resp.status_code == 302
with client.session_transaction() as sess:
# Invalid language should NOT be stored
assert sess.get("language") != "xx"