Dateien
AegisSight-Monitor/tests/test_studio_struktur.py
claude-dev ffbdd5170c refactor(studio): ein Startpunkt statt sechs, Schritte werden zur Anzeige
Die Bausteinspalte mischte vier Dinge in einem Kartenlayout. Ergebnisse zum
Anschauen, Erzeuger zum Starten, ein Artefakt ohne Erzeuger und reine
Aktionen. In jeder Karte lagen Öffnen und Neurechnen wenige Pixel
auseinander, beide mit demselben Kreispfeil-Symbol. Dazu kamen sechs Stellen,
an denen ein Lauf beginnen konnte, verteilt über zwei Spalten.

Jetzt drei getrennte Bereiche. Oben wird gearbeitet, in der Mitte werden
Ergebnisse angesehen, unten stehen die Fall-Aktionen.

Gearbeitet wird über genau einen Knopf, dessen Beschriftung sich am Zustand
ausrichtet. Beim leeren Fall Lauf starten, bei einer Recherche Recherche
starten, bei veralteten Ergebnissen Jetzt aktualisieren, sonst Erneut
durchlaufen, während eines abbrechbaren Laufs Abbrechen. Darüber steht, was
gerade zu tun ist, etwa die Zahl der neuen Artikel seit dem letzten Bericht.

Darunter die vier Schritte als Kette mit Zustand. Sie starten nichts,
Aufklappen erklärt nur, was ein Schritt tut. Damit fällt auch eine
Kostenfalle weg. Ein einzeln gestartetes Sammeln buchte denselben Satz wie
der komplette Lauf, wer danach Lagebild und Faktencheck einzeln nachzog,
zahlte 69 statt 45 Credits, ohne dass die Oberfläche darauf hinwies.

Benannt wird ab jetzt das Ergebnis statt des Erzeugers. Artikel holen,
Lagebild schreiben, Fakten prüfen, Orte erkennen. Bei Recherche-Lagen heißt
der zweite Schritt Recherchebericht schreiben. Die Wörter Baustein,
Geoparsing und Analyse verschwinden aus der Oberfläche.

Weiter entfernt sind die Karte Netzwerkanalyse, solange es den Baustein im
Server nicht gibt, der Sammeln-Knopf aus der Quellenspalte und die Wahl
Nur Quellen sammeln im Startdialog.

Der Server behält seine Schnittstelle für einzelne Bausteine. Die Anzeige
eines so gestarteten Laufs bleibt erhalten, er kann aus der klassischen
Ansicht kommen. Ein solcher Lauf wird beim Abbruch auch als der Schritt
abgebrochen, der wirklich läuft, nicht pauschal als kompletter Lauf.

51 tote CSS-Regeln entfernt. Prüfungen ohne Browser, 59 neue Fälle für die
Lauf-Anzeige plus 12 in der Strukturprüfung, die festhalten, dass es im
ganzen Dokument genau ein Element gibt, das einen Lauf starten kann.

Die Anzeige der Credits am Knopf kommt später, sie ist bewusst nicht Teil
dieser Änderung.

Cache-Buster auf 20260802c.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 19:26:27 +00:00

164 Zeilen
7.0 KiB
Python

"""Prueft die Struktur der Oberflaechen ohne Browser.
Faengt genau die Fehler, die eine Syntaxpruefung durchlaesst. Ein onclick, das
auf eine nicht vorhandene Methode zeigt. Ein Feld, das das Skript sucht, das
im Dokument aber fehlt. Eine ID, die zweimal vergeben ist. Ein vergessener
Cache-Buster.
Aufruf aus dem Projektstamm:
python3 tests/test_studio_struktur.py
"""
import os
import re
import sys
STAMM = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC = os.path.join(STAMM, "src", "static")
ok = 0
fehl = 0
def pruefe(name, bedingung, extra=None):
global ok, fehl
if bedingung:
ok += 1
print(f" OK {name}")
else:
fehl += 1
print(f" FEHL {name}" + (f" {extra}" if extra is not None else ""))
def lies(*teile):
with open(os.path.join(STATIC, *teile), encoding="utf-8") as f:
return f.read()
studio_html = lies("studio.html")
studio_js = lies("js", "studio.js")
dash_html = lies("dashboard.html")
app_js = lies("js", "app.js")
comp_js = lies("js", "components.js")
style = lies("css", "style.css")
studio_css = lies("css", "studio.css")
def ids(html):
return re.findall(r'\bid="([^"]+)"', html)
def doppelte(liste):
gesehen, doppelt = set(), []
for x in liste:
if x in gesehen:
doppelt.append(x)
gesehen.add(x)
return doppelt
print("\nA) IDs sind eindeutig")
for name, html in [("studio.html", studio_html), ("dashboard.html", dash_html)]:
d = doppelte(ids(html))
pruefe(f"A keine doppelte ID in {name}", not d, d)
print("\nB) Der Einstellungen-Reiter ist vollstaendig verdrahtet")
# Alle Felder, die die Logik anfasst, muessen im Dokument stehen.
gesucht = sorted(set(re.findall(r"_setEl\('([^']+)'\)", studio_js)))
vorhanden = set(ids(studio_html))
fehlend = [f for f in gesucht if f not in vorhanden]
pruefe("B1 jedes angesprochene Feld existiert", not fehlend, fehlend)
pruefe("B2 es sind auch wirklich welche geprueft", len(gesucht) >= 15, len(gesucht))
# Panel und Navigationsknopf haengen am selben Schluessel.
pruefe("B3 Navigationsknopf vorhanden",
'data-art="settings" onclick="Studio.openTab(\'settings\')"' in studio_html)
pruefe("B4 Panel vorhanden", '<div class="tab-panel" data-art="settings">' in studio_html)
pruefe("B5 activateTab kennt den Reiter",
"if (key === 'settings') this.loadSettings();" in studio_js)
print("\nC) Jedes onclick zeigt auf eine vorhandene Methode")
for name, html, js, praefix in [
("studio", studio_html, studio_js, "Studio"),
("dashboard", dash_html, app_js + comp_js, "App"),
]:
aufgerufen = sorted(set(re.findall(praefix + r"\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", html)))
fehlend = []
for m in aufgerufen:
# Methoden stehen als " name(" oder " async name(" im Objektliteral
if not re.search(r"^\s+(async\s+)?" + re.escape(m) + r"\s*\(", js, re.M):
fehlend.append(m)
pruefe(f"C {name}.html ruft nur vorhandene Methoden ({len(aufgerufen)} geprueft)",
not fehlend, fehlend)
print("\nD) Der KI-Weg ist in beiden Ansichten gleich angeboten")
for name, html, feld in [("studio.html", studio_html, "set-ai-backend"),
("studio.html anlegen", studio_html, "inc-ai-backend"),
("dashboard.html", dash_html, "inc-ai-backend")]:
block = re.search(r'<select id="' + feld + r'".*?</select>', html, re.S)
pruefe(f"D {name} bietet drei Werte an",
bool(block) and block.group(0).count("<option") == 3)
if block:
werte = re.findall(r'value="([^"]*)"', block.group(0))
pruefe(f"D {name} genau die erlaubten Werte", werte == ["", "cli", "bedrock"], werte)
print("\nE) Cache-Buster sind einheitlich")
for name, html in [("studio.html", studio_html), ("dashboard.html", dash_html)]:
stempel = set(re.findall(r"/static/(?:js|css)/[a-z0-9_.-]+\?v=([0-9a-z]+)", html))
# Fremde Bausteine (i18n, api, ws) haben eigene Staende, das ist in Ordnung.
# Geprueft wird nur, dass die in diesem Zug geaenderten Dateien mitgezogen sind.
eigene = re.findall(r"/static/(?:js|css)/(studio|app|style|components|studio)\.(?:js|css)\?v=([0-9a-z]+)", html)
pruefe(f"E {name} nennt Stempel", bool(stempel), stempel)
# Kein fester Wert. Geprueft wird, dass die in einem Zug geaenderten
# Dateien denselben Stempel tragen, sonst muesste der Test bei jedem
# Deploy mitgepflegt werden.
stempel_eigen = {v for _, v in eigene}
pruefe(f"E {name} alle eigenen Dateien tragen denselben Stempel",
len(stempel_eigen) == 1, sorted(stempel_eigen))
print("\nE2) Die Bausteinspalte hat genau einen Startpunkt")
_i = studio_html.index('id="studio-menu"')
menue = studio_html[_i:studio_html.index("</section>", _i)]
starts = re.findall(r'data-stage="([a-z]+)"', menue)
pruefe("E2a genau ein Element startet einen Lauf", len(starts) == 1, starts)
pruefe("E2b und zwar der komplette Lauf", starts == ["full"], starts)
pruefe("E2c auch im ganzen Dokument nur dieses eine",
len(re.findall(r"data-stage=", studio_html)) == 1,
len(re.findall(r"data-stage=", studio_html)))
pruefe("E2d die Ansichten starten nichts", "Studio.runStage" not in studio_html)
for tot in ["art-card", "art-run", "art-note", "run-all", "collect-btn", "art-foot"]:
pruefe(f"E2 {tot} ist restlos weg",
tot not in studio_html and tot not in studio_js and tot not in studio_css)
pruefe("E2e die Schritte tragen Ergebnisnamen",
"Artikel holen" in studio_js and "Orte erkennen" in studio_js
and "Fakten prüfen" in studio_js)
pruefe("E2f Geoparsing steht nicht mehr in der Oberflaeche",
"Geoparsing" not in studio_html)
print("\nF) Tags im neuen Panel sind ausgeglichen")
i = studio_html.index('<div class="tab-panel" data-art="settings">')
j = studio_html.index('<div class="tab-panel" data-art="export">')
teil = studio_html[i:j]
for tag in ["div", "select", "label", "textarea"]:
auf = len(re.findall(r"<" + tag + r"[\s>]", teil))
zu = len(re.findall(r"</" + tag + r">", teil))
pruefe(f"F {tag} ausgeglichen ({auf})", auf == zu, (auf, zu))
print("\nG) Das Kennzeichen nutzt vorhandene Stile")
pruefe("G1 incident-ai-badge ist definiert", ".incident-ai-badge" in style)
pruefe("G2 case-ai-tag ist definiert", ".case-ai-tag" in studio_css)
pruefe("G3 studio.html bindet style.css ein", "/static/css/style.css" in studio_html)
pruefe("G4 Kennzeichenplatz in der Kopfzeile", 'id="studio-ai-badge"' in studio_html)
print("\nH) Keine unbekannten Design-Tokens")
# var(--x, wert) ist auch ohne Definition gueltig, der Fallback greift.
# Geprueft wird deshalb nur, was ohne Fallback benutzt wird.
ohne_fallback = set(re.findall(r"var\((--[a-z0-9-]+)\s*\)", studio_css))
definiert = set(re.findall(r"^\s*(--[a-z0-9-]+)\s*:", style, re.M))
definiert |= set(re.findall(r"^\s*(--[a-z0-9-]+)\s*:", studio_css, re.M))
unbekannt = sorted(ohne_fallback - definiert)
pruefe("H1 jedes Token ohne Fallback ist definiert", not unbekannt, unbekannt)
pruefe("H2 es sind auch wirklich welche geprueft", len(ohne_fallback) >= 10, len(ohne_fallback))
print(f"\nErgebnis: {ok} bestanden, {fehl} fehlgeschlagen")
sys.exit(1 if fehl else 0)