Files
Projektwissen/tests/test_vorschau.py
T
DanielandClaude Opus 5.5 19947a845f BPMN-Vorschau im Browser, offline, auf Basis von bpmn-js
- werkzeuge/vorschau/vorschau.html: Anzeige per Drag & Drop oder
  Dateiauswahl, Zoom, Einpassen, Fehlermeldungen; funktioniert ohne
  Python direkt aus dem Dateisystem.
- scripts/vorschau.py: erzeugt aus .bpmn oder Arbeitsschritt-.md eine
  in sich geschlossene Seite und öffnet sie; --stdin als Filter, etwa
  für einen Gitea-Renderer. Diagramm wird mit maskiertem '<' eingebettet,
  die Bibliothek unverändert und nur auf <script/</script geprüft.
- bpmn-js 18.29.1 (navigated viewer + CSS + LICENSE) lokal eingebettet,
  npm-Integrität geprüft; lädt nichts aus dem Netz. Wasserzeichen bleibt
  laut Lizenzauflage sichtbar.
- tests/test_vorschau.py: 11 Tests; Skill sn-bpmn, Anleitung, AGENTS.md,
  README, Leitfäden, Fallstricke und STATUS ergänzt.
- Tests: python -m pytest -q -> 55 bestanden. Darstellung mit Edge
  headless geprüft (beide Beispiele, leere Seite, kaputte Datei).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 13:32:14 +02:00

125 lines
5.4 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.
"""Tests für scripts/vorschau.py und die eingebettete bpmn-js-Vorschau – reines Python/pytest, ohne Browser."""
import json
import re
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import vorschau # noqa: E402
BEISPIEL_MD = ROOT / ".agents" / "skills" / "sn-bpmn" / "beispiele" / "beispiel_prozess.md"
BEISPIEL_BPMN = BEISPIEL_MD.with_suffix(".bpmn")
def test_vorlage_verweist_nur_auf_vorhandene_lokale_dateien():
"""Prüft, dass vorschau.html nur Dateien unter werkzeuge/vorschau/ einbindet, die es gibt, und nichts aus dem Netz."""
html = vorschau.VORLAGE.read_text(encoding="utf-8")
verweise = re.findall(r'(?:src|href)="([^"]+)"', html)
assert verweise, "keine eingebundenen Dateien gefunden"
for verweis in verweise:
assert not re.match(r"^[a-z]+:", verweis), f"externer Verweis: {verweis}"
assert (vorschau.VORSCHAU / verweis).is_file(), f"fehlt: {verweis}"
def test_lizenz_von_bpmn_js_liegt_bei():
"""Prüft, dass die Lizenz von bpmn-js neben der eingebetteten Bibliothek liegt (MIT-Auflage)."""
lizenz = (vorschau.VORSCHAU / "bpmn-js" / "LICENSE").read_text(encoding="utf-8")
assert "watermark" in lizenz
def test_erzeugte_seite_ist_in_sich_geschlossen():
"""Prüft, dass die erzeugte Seite Bibliothek und Stile eingebettet hat und keine Datei mehr nachlädt."""
html = vorschau.baue_html(BEISPIEL_BPMN.read_text(encoding="utf-8"), BEISPIEL_BPMN.name)
assert not re.search(r"<script[^>]*\bsrc=", html)
assert "<link" not in html
assert "BpmnJS" in html
assert "const EINGEBETTET = null;" not in html
assert '"beispiel_prozess.bpmn"' in html
def test_diagramm_wird_als_javascript_zeichenkette_eingebettet():
"""Prüft, dass der Inhalt der .bpmn verlustfrei als JSON-Zeichenkette mit maskiertem '<' in der Seite landet."""
xml = '<definitions id="x">Ä&amp;</definitions>'
html = vorschau.baue_html(xml, "x.bpmn")
wert = re.search(r"const EINGEBETTET = (.*);", html).group(1)
assert "<" not in wert
assert json.loads(wert) == xml
def test_script_ende_im_diagramm_bricht_die_seite_nicht_auf():
"""Prüft, dass '</script>' in einer Beschriftung nicht als Ende des Skriptblocks wirkt."""
html = vorschau.baue_html('<task name="</script><script>alert(1)</script>"/>', "x.bpmn")
assert "alert(1)" in html
assert "<script>alert(1)" not in html
assert html.lower().count("</script") == html.lower().count("<script")
def test_bibliothek_mit_script_ende_wird_abgelehnt(tmp_path, monkeypatch):
"""Prüft, dass eine (etwa nach einem Update) nicht sicher einbettbare Bibliothek einen Fehler auslöst."""
kopie = tmp_path / "vorschau"
shutil.copytree(vorschau.VORSCHAU, kopie)
js = kopie / "bpmn-js" / "bpmn-navigated-viewer.production.min.js"
js.write_text(js.read_text(encoding="utf-8") + '\nvar x = "</script>";', encoding="utf-8")
monkeypatch.setattr(vorschau, "VORSCHAU", kopie)
monkeypatch.setattr(vorschau, "VORLAGE", kopie / "vorschau.html")
with pytest.raises(RuntimeError, match="</script"):
vorschau.baue_html("<x/>")
def test_md_wird_vor_der_vorschau_umgewandelt():
"""Prüft, dass eine Arbeitsschritt-.md dasselbe XML liefert wie ihre erzeugte .bpmn."""
erwartet = BEISPIEL_BPMN.read_text(encoding="utf-8").replace("\r\n", "\n")
assert vorschau.lies_diagramm(BEISPIEL_MD).replace("\r\n", "\n") == erwartet
def test_ausgabe_in_datei_ohne_browser(tmp_path):
"""Prüft, dass --ausgabe mit --nicht-oeffnen eine Seite schreibt und Exit-Code 0 liefert."""
ziel = tmp_path / "v.html"
ergebnis = subprocess.run(
[sys.executable, str(ROOT / "scripts" / "vorschau.py"), str(BEISPIEL_MD),
"--ausgabe", str(ziel), "--nicht-oeffnen"],
capture_output=True, text=True, encoding="utf-8",
)
assert ergebnis.returncode == 0, ergebnis.stdout + ergebnis.stderr
assert "BpmnJS" in ziel.read_text(encoding="utf-8")
def test_stdin_modus_liest_xml_und_schreibt_html():
"""Prüft, dass --stdin als Filter arbeitet (Grundlage für einen Gitea-Renderer)."""
ergebnis = subprocess.run(
[sys.executable, str(ROOT / "scripts" / "vorschau.py"), "--stdin"],
input=BEISPIEL_BPMN.read_bytes(), capture_output=True,
)
assert ergebnis.returncode == 0, ergebnis.stderr.decode("utf-8", "replace")
html = ergebnis.stdout.decode("utf-8")
assert html.startswith("<!DOCTYPE html>") and "Antrag erfassen" in html
def test_fehlende_datei_gibt_exit_code_1(tmp_path):
"""Prüft, dass eine nicht vorhandene Eingabedatei sauber mit Exit-Code 1 endet."""
ergebnis = subprocess.run(
[sys.executable, str(ROOT / "scripts" / "vorschau.py"), str(tmp_path / "gibt-es-nicht.bpmn"),
"--nicht-oeffnen"],
capture_output=True, text=True, encoding="utf-8",
)
assert ergebnis.returncode == 1
assert "nicht gefunden" in ergebnis.stdout
def test_nicht_umwandelbare_md_gibt_exit_code_1(tmp_path):
"""Prüft, dass eine .md mit Syntaxfehler als Meldung statt als Absturz endet."""
md = tmp_path / "kaputt.md"
md.write_text("S1 Start: A\nS1 Ende: B\n", encoding="utf-8")
ergebnis = subprocess.run(
[sys.executable, str(ROOT / "scripts" / "vorschau.py"), str(md), "--nicht-oeffnen"],
capture_output=True, text=True, encoding="utf-8",
)
assert ergebnis.returncode == 1
assert "lässt sich nicht umwandeln" in ergebnis.stdout