"""Panels: assignment, pairing, and what a paired credential may reach.""" from fastapi.testclient import TestClient from app.core.config import settings PREFIX = f"{settings.API_V1_STR}/panels" DASHBOARDS = f"{settings.API_V1_STR}/dashboards" def _panels(client: TestClient, headers: dict[str, str], config: dict) -> None: response = client.put(f"{PREFIX}/", headers=headers, json=config) assert response.status_code == 200, response.text def _pair(client: TestClient, headers: dict[str, str], panel: str) -> dict[str, str]: """Walk a device through pairing and return the header it ends up with.""" started = client.post(f"{PREFIX}/pair").json() waiting = client.get( f"{PREFIX}/pair/{started['code']}", params={"secret": started["secret"]} ) assert waiting.json()["access_token"] is None approved = client.post( f"{PREFIX}/{panel}/pair", headers=headers, json={"code": started["code"]} ) assert approved.status_code == 200, approved.text collected = client.get( f"{PREFIX}/pair/{started['code']}", params={"secret": started["secret"]} ).json() assert collected["panel"] == panel return {"Authorization": f"Bearer {collected['access_token']}"} def test_panels_require_authentication(client: TestClient) -> None: assert client.get(f"{PREFIX}/").status_code == 401 assert client.put(f"{PREFIX}/", json={"panels": []}).status_code == 401 def test_assign_and_read_back( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: for name in ("hall_a", "hall_b"): client.post(f"{DASHBOARDS}/{name}", headers=superuser_token_headers) _panels( client, superuser_token_headers, { "panels": [ {"id": "hall", "title": "Hall", "dashboards": ["hall_a", "hall_b"]} ] }, ) stored = client.get(f"{PREFIX}/", headers=superuser_token_headers).json() assert stored["panels"][0]["dashboards"] == ["hall_a", "hall_b"] assert ( client.get(f"{PREFIX}/hall", headers=superuser_token_headers).json()["title"] == "Hall" ) assert ( client.get(f"{PREFIX}/nowhere", headers=superuser_token_headers).status_code == 404 ) def test_duplicate_panel_is_refused( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: response = client.put( f"{PREFIX}/", headers=superuser_token_headers, json={"panels": [{"id": "twice"}, {"id": "twice"}]}, ) assert response.status_code == 422 def test_polling_needs_the_secret(client: TestClient) -> None: started = client.post(f"{PREFIX}/pair").json() assert len(started["code"]) == 6 assert ( client.get( f"{PREFIX}/pair/{started['code']}", params={"secret": "wrong"} ).status_code == 404 ) assert ( client.get(f"{PREFIX}/pair/ZZZZZZ", params={"secret": "x"}).status_code == 404 ) def test_approving_an_unknown_code_or_panel_is_refused( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: _panels(client, superuser_token_headers, {"panels": [{"id": "hall"}]}) assert ( client.post( f"{PREFIX}/hall/pair", headers=superuser_token_headers, json={"code": "ZZZZZZ"}, ).status_code == 404 ) started = client.post(f"{PREFIX}/pair").json() assert ( client.post( f"{PREFIX}/nowhere/pair", headers=superuser_token_headers, json={"code": started["code"]}, ).status_code == 404 ) def test_paired_panel_reaches_only_what_it_shows( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: for name in ("panel_shown", "panel_hidden"): client.post(f"{DASHBOARDS}/{name}", headers=superuser_token_headers) _panels( client, superuser_token_headers, {"panels": [{"id": "hall", "dashboards": ["panel_shown"]}]}, ) panel_headers = _pair(client, superuser_token_headers, "hall") # What it was assigned, published, plus its own definition and the messages # its widgets speak. assert ( client.get(f"{DASHBOARDS}/panel_shown", headers=panel_headers).status_code == 200 ) assert client.get(f"{PREFIX}/hall", headers=panel_headers).status_code == 200 assert ( client.get( f"{settings.API_V1_STR}/messages/", headers=panel_headers ).status_code == 200 ) # The generated client spells the default out, so `?draft=false` is what a # browser actually asks with for "the published one". for spelling in ("false", "0", "off"): assert ( client.get( f"{DASHBOARDS}/panel_shown", headers=panel_headers, params={"draft": spelling}, ).status_code == 200 ), spelling # And nothing else. assert ( client.get(f"{DASHBOARDS}/panel_hidden", headers=panel_headers).status_code == 403 ) # Fail-closed: a spelling neither side recognises counts as a draft. for spelling in ("true", "1", "yes", "maybe"): assert ( client.get( f"{DASHBOARDS}/panel_shown", headers=panel_headers, params={"draft": spelling}, ).status_code == 403 ), spelling assert client.get(f"{DASHBOARDS}/", headers=panel_headers).status_code == 403 assert ( client.get(f"{settings.API_V1_STR}/flows/", headers=panel_headers).status_code == 403 ) assert ( client.delete(f"{DASHBOARDS}/panel_shown", headers=panel_headers).status_code == 403 ) assert client.get(f"{PREFIX}/", headers=panel_headers).status_code == 403 def test_removing_the_panel_revokes_its_credential( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: client.post(f"{DASHBOARDS}/panel_gone", headers=superuser_token_headers) _panels( client, superuser_token_headers, {"panels": [{"id": "workshop", "dashboards": ["panel_gone"]}]}, ) panel_headers = _pair(client, superuser_token_headers, "workshop") assert ( client.get(f"{DASHBOARDS}/panel_gone", headers=panel_headers).status_code == 200 ) _panels(client, superuser_token_headers, {"panels": []}) # 401, not 403: there is nothing left to be forbidden from, and the device # should go back to the pairing screen rather than retry. assert ( client.get(f"{DASHBOARDS}/panel_gone", headers=panel_headers).status_code == 401 )