Files
Trademind/tests/test_dashboard.py
T
Tobias Zimmermann 4551339632 Dashboard reparieren: Escape-Sequenz zerlegte das eingebettete Skript
Das Dashboard blieb bei "lädt ..." stehen und zeigte weder Kacheln noch die
Steuerungs-Abschnitte.

Ursache: _DASHBOARD ist ein Python-String. Der in d081edf ergänzte
prompt()-Dialog für die Live-Bestätigung enthält \n, das Python zu echten
Zeilenumbrüchen aufgelöst hat. Das dadurch offene JavaScript-String-Literal ist
ein Syntaxfehler, der den gesamten <script>-Block mitreißt - im Browser waren
refresh und $ schlicht undefined, es lief kein einziger Ausdruck. Behoben durch
ein r-Präfix am Template, mit Kommentar an Ort und Stelle.

Warum das durchgerutscht ist: Die bisherige Prüfung hat nur kontrolliert, ob die
Bedienelemente im HTML stehen und ob die HTTP-Endpunkte antworten. Beides war
grün, während die Seite tot war. Markup-Präsenz ersetzt nicht das Ausführen der
Seite.

Neu: tests/test_dashboard.py
- Mini-Lexer über das ausgelieferte Skript, der String-Literale findet, die vor
  dem Zeilenende nicht geschlossen werden (Backtick-Template-Literale dürfen
  mehrzeilig sein). Gegenprobe im Test enthalten; gegen den echten Defekt
  geprüft, er wird erkannt.
- Ausgeglichene Klammern, jede im Skript referenzierte Element-ID existiert im
  HTML, und \n kommt als zwei Zeichen beim Browser an.

186 Tests (12 neue), ruff sauber. Im Browser durchgeklickt: Skript lädt, 10
Kacheln, Handel pausieren/starten und der Lernschalter ändern den Serverzustand
und die Anzeige, historisches Training läuft durch bis "Fertig in 0.8s: +146
Beobachtungen, gespeichert", null Loop-Fehler.
2026-08-23 12:59:39 +02:00

123 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
r"""Das eingebettete Dashboard-Skript muss syntaktisch heil beim Browser ankommen.
Hintergrund: ``_DASHBOARD`` ist ein Python-String. Fehlt das r-Präfix, macht Python aus
einem ``\n``, das eigentlich dem JavaScript gehört, einen echten Zeilenumbruch. Das offene
String-Literal reißt dann das gesamte ``<script>`` mit die Seite lädt, zeigt aber nur
"lädt …", weil kein einziger Ausdruck ausgeführt wird. Genau das ist schon passiert.
"""
from __future__ import annotations
import re
import pytest
from trademind.server import _DASHBOARD
SCRIPT_RE = re.compile(r"<script>(.*?)</script>", re.DOTALL)
def script_body() -> str:
match = SCRIPT_RE.search(_DASHBOARD)
assert match is not None, "Das Dashboard enthält keinen <script>-Block"
return match.group(1)
def unterminated_string(js: str) -> str | None:
"""Findet ein String-Literal, das vor dem Zeilenende nicht geschlossen wird.
Nur Template-Literale (Backticks) dürfen über mehrere Zeilen gehen. Gibt eine
Beschreibung der Fundstelle zurück oder ``None``, wenn alles sauber ist.
"""
state: str | None = None # None | " | ' | ` | // | /*
opened_at = 0
line = 1
i = 0
while i < len(js):
char = js[i]
nxt = js[i + 1 : i + 2]
if state is None:
if char == "/" and nxt == "/":
state, i = "//", i + 1
elif char == "/" and nxt == "*":
state, i = "/*", i + 1
elif char in "\"'`":
state, opened_at = char, line
elif char == "\n":
line += 1
elif state == "//":
if char == "\n":
state, line = None, line + 1
elif state == "/*":
if char == "*" and nxt == "/":
state, i = None, i + 1
elif char == "\n":
line += 1
else: # innerhalb eines String-Literals
if char == "\\":
i += 1 # nächstes Zeichen ist escaped
elif char == state:
state = None
elif char == "\n":
if state != "`":
return f"String mit {state} in Zeile {opened_at} endet nicht auf derselben Zeile"
line += 1
i += 1
if state in ("\"", "'", "`"):
return f"String mit {state} ab Zeile {opened_at} wird nie geschlossen"
return None
def test_script_has_no_unterminated_string_literal():
assert unterminated_string(script_body()) is None
def test_javascript_escapes_survive_python():
r"""Ein \n im Quelltext muss als zwei Zeichen beim Browser ankommen."""
assert "\\n" in _DASHBOARD, "Escape-Sequenz wurde von Python aufgelöst r-String fehlt?"
def test_braces_are_balanced():
js = script_body()
depth = 0
for char in js:
depth += char == "{"
depth -= char == "}"
assert depth >= 0, "Mehr schließende als öffnende Klammern"
assert depth == 0, f"Klammern unausgeglichen (Differenz {depth})"
def test_every_referenced_element_exists():
"""Die Skript-Initialisierung greift auf feste IDs zu fehlt eine, bricht alles ab."""
js = script_body()
referenced = set(re.findall(r"""\$\(["']([\w-]+)["']\)""", js))
referenced |= set(re.findall(r"""getElementById\(["']([\w-]+)["']\)""", js))
assert referenced, "Keine Element-Referenzen gefunden Test greift ins Leere"
for element_id in sorted(referenced):
assert f'id="{element_id}"' in _DASHBOARD, f"Element #{element_id} fehlt im HTML"
@pytest.mark.parametrize(
"needle",
[
"Automatisierter Handel",
"Training starten",
"Kontinuierliches Lernen",
"control/trading",
"control/train/history",
"control/train/live",
"X-TradeMind-Token",
],
)
def test_dashboard_wires_up_the_controls(needle: str):
assert needle in _DASHBOARD
def test_broken_example_is_detected():
"""Gegenprobe: Der Prüfer muss den ursprünglichen Fehler erkennen."""
broken = 'const a = prompt("erste Zeile\nzweite Zeile");'
assert unterminated_string(broken) is not None
assert unterminated_string('const a = prompt("erste Zeile\\nzweite Zeile");') is None
assert unterminated_string("const a = `mehrere\nZeilen sind hier erlaubt`;") is None