"""#59/#51/#57: PIN nella gestione utenti (proxy admin, pannello) e link Monitor nel menu.""" from unittest.mock import MagicMock, patch import pytest from tests.conftest import api_get_router USER = { "id": 2, "username": "mario", "display_name": "Mario", "email": None, "roles": ["MeasurementTec"], "is_admin": False, "language_pref": "it", "theme_pref": "light", "active": True, "has_pin": True, } @pytest.fixture def mock_admin_api(): mock = MagicMock() mock.get.side_effect = api_get_router({ "/api/users": [USER], "/api/settings": {"auto_logout_minutes": "30"}, }) with patch("blueprints.admin.api_client", mock): yield mock def test_pin_proxy_forwards_four_digits(logged_in_client, mock_admin_api): mock_admin_api.put.return_value = {"message": "ok"} resp = logged_in_client.put("/admin/api/users/2/pin", json={"pin": "1234"}) assert resp.status_code == 200 mock_admin_api.put.assert_called_once_with("/api/users/2/pin", data={"pin": "1234"}) def test_pin_proxy_forwards_null_to_remove(logged_in_client, mock_admin_api): mock_admin_api.put.return_value = {"message": "ok"} resp = logged_in_client.put("/admin/api/users/2/pin", json={"pin": None}) assert resp.status_code == 200 mock_admin_api.put.assert_called_once_with("/api/users/2/pin", data={"pin": None}) def test_pin_proxy_passes_422_detail(logged_in_client, mock_admin_api): mock_admin_api.put.return_value = { "error": True, "status_code": 422, "detail": "Gli admin non hanno il PIN", } resp = logged_in_client.put("/admin/api/users/1/pin", json={"pin": "1234"}) assert resp.status_code == 422 assert resp.get_json()["detail"] == "Gli admin non hanno il PIN" def test_pin_proxy_requires_admin(client, mock_admin_api): with client.session_transaction() as sess: sess["api_key"] = "k" sess["user"] = {"id": 3, "username": "op", "roles": ["MeasurementTec"], "is_admin": False, "language_pref": "it", "theme_pref": "light"} resp = client.put("/admin/api/users/2/pin", json={"pin": "1234"}) assert resp.status_code != 200 mock_admin_api.put.assert_not_called() def test_users_panel_has_pin_controls(logged_in_client, mock_admin_api): html = logged_in_client.get("/admin/settings?tab=utenti").data.decode("utf-8") assert "openPin(user)" in html # pulsante Imposta/Reset PIN assert "user.has_pin === true" in html # indicatore PIN impostato assert "removePin()" in html # Togli PIN assert "form.pin" in html # campo PIN alla creazione assert "!user.is_admin" in html # niente PIN per gli admin def _session_client(client, roles, is_admin): with client.session_transaction() as sess: sess["api_key"] = "k" sess["user"] = {"id": 5, "username": "u", "display_name": "U", "roles": roles, "is_admin": is_admin, "language_pref": "it", "theme_pref": "light"} return client @pytest.fixture def mock_monitor_api(): mock = MagicMock() mock.get.return_value = [] with patch("blueprints.monitor.api_client", mock), \ patch("blueprints.measure.api_client", mock): yield mock def test_monitor_link_for_admin(logged_in_client, mock_monitor_api): html = logged_in_client.get("/monitor").data.decode("utf-8") assert 'href="/monitor"' in html def test_monitor_link_for_supervisor(client, mock_monitor_api): _session_client(client, ["Supervisor"], False) html = client.get("/monitor").data.decode("utf-8") assert 'href="/monitor"' in html def test_monitor_link_absent_for_operator(client, mock_api_client, monkeypatch): # Stessa preparazione di test_measure.py: la pagina vuole STATION_CODE a config ricaricata monkeypatch.setenv("STATION_CODE", "ST-TEST") import importlib import config importlib.reload(config) import blueprints.measure importlib.reload(blueprints.measure) mock_api_client.get_station_recipes.return_value = [] _session_client(client, ["MeasurementTec"], False) resp = client.get("/measure/select") assert resp.status_code == 200 html = resp.data.decode("utf-8") assert "/measure/select" in html or "Misure" in html or "Measure" in html # menu reso assert 'href="/monitor"' not in html