"""Tests fuer Teil F.3 (Stufe F3, Umsetzungsauftrag_Sonnet5.md): die dauerhafte Arbeitsflaeche /workspace. Diese Stufe ist ueberwiegend Frontend (templates/workspace.html, static/js/workspace.js) -- die serverseitige Flaeche ist bewusst klein (rein statisches Markup wie /dashboard, Auth clientseitig ueber GET /auth/me, siehe app/main.py::workspace_page). Die CSP-Konformitaet (kein Inline-style/-script) wird zentral in tests/test_csp_compliance.py mitgeprueft (/workspace wurde dort in die Parametrisierung aufgenommen). Hier: die Route existiert, liefert die erwarteten Bausteine aus, und die Backend-Bausteine, auf denen workspace.js aufbaut (GET /catalog/hosts, GET /catalog/sessions), bleiben nutzbar.""" from __future__ import annotations import pytest @pytest.mark.asyncio async def test_workspace_page_renders_without_login(client): """Wie /dashboard/-admin: die Seite selbst ist statisches Markup ohne Secrets -- die eigentliche Zugriffskontrolle laeuft clientseitig ueber GET /auth/me (401 -> Redirect zu /) UND serverseitig hart auf jedem /catalog/*-API-Aufruf, den workspace.js danach macht.""" resp = await client.get("/workspace") assert resp.status_code == 200, resp.text html = resp.text assert 'id="workspace-tiles"' in html assert 'id="workspace-catalog-view"' in html assert 'id="workspace-sessions-area"' in html assert 'id="workspace-new-btn"' in html assert '/static/js/workspace.js' in html # F1-Bausteine werden mitgeladen (kein zweiter Sitzungs-Code, F.2). assert '/static/js/terminal.js' in html assert '/static/js/rdp.js' in html # F3 baut das Sitzungs-DOM erst zur Laufzeit -- kein #session-container # mit data-host-id wie bei /terminal/{id}, /rdp/{id} (die Seite haette # sonst automatisch eine Sitzung bootstrapped, siehe terminal.js/rdp.js # Bootstrap-Block). assert 'id="session-container"' not in html @pytest.mark.asyncio async def test_workspace_backend_building_blocks_reachable_after_login(client): """workspace.js ruft beim Laden GET /catalog/hosts und GET /catalog/sessions auf -- beide muessen fuer einen angemeldeten Benutzer erreichbar sein (volle Auth-Pruefung liegt bereits in test_teil_f_schritt2.py und den bestehenden Katalog-Tests; hier nur der Zusammenhang mit der neuen Seite).""" from app.db import get_db from app.security.passwords import hash_password import pyotp conn = get_db() await conn.execute( "INSERT INTO users (username, password_hash, is_admin, must_change_password) " "VALUES ('f3_user', ?, 0, 0)", (hash_password("Correct-Horse-Battery-Staple-F3"),), ) await conn.commit() resp = await client.post("/auth/login", json={"username": "f3_user", "password": "Correct-Horse-Battery-Staple-F3"}) pending = resp.json()["pending_token"] resp = await client.post("/auth/totp/enroll/start", json={"pending_token": pending}) provisioning_uri = resp.json()["provisioning_uri"] secret = dict(part.split("=") for part in provisioning_uri.split("?", 1)[1].split("&"))["secret"] code = pyotp.TOTP(secret).now() resp = await client.post("/auth/totp/enroll/confirm", json={"pending_token": pending, "code": code}) assert resp.status_code == 200, resp.text resp = await client.get("/catalog/hosts") assert resp.status_code == 200, resp.text assert isinstance(resp.json(), list) resp = await client.get("/catalog/sessions") assert resp.status_code == 200, resp.text assert resp.json() == [] # frisch angemeldeter Benutzer hat keine offenen Sitzungen