"""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"]*\bsrc=", html) assert "' in einer Beschriftung nicht als Ende des Skriptblocks wirkt.""" html = vorschau.baue_html('', "x.bpmn") assert "alert(1)" in html assert "";', encoding="utf-8") monkeypatch.setattr(vorschau, "VORSCHAU", kopie) monkeypatch.setattr(vorschau, "VORLAGE", kopie / "vorschau.html") with pytest.raises(RuntimeError, match="") 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("") 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