2026-09-25 09:40:52 +02:00
|
|
|
|
"""Tests für scripts/pruefe_prozesse.py und für den Bestand der Prozessbibliothek – reines Python/pytest."""
|
2026-09-23 13:02:48 +02:00
|
|
|
|
import sys
|
2026-09-25 09:40:52 +02:00
|
|
|
|
import warnings
|
2026-09-23 13:02:48 +02:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
|
|
|
|
|
|
|
|
import pruefe_prozesse as pp # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
MODELL = """Prozess: Testprozess
|
|
|
|
|
|
S1 Start: Beginn
|
|
|
|
|
|
S2 Task: Etwas tun
|
|
|
|
|
|
S3 Ende: Fertig
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
STECKBRIEF = """# Testprozess
|
|
|
|
|
|
|
|
|
|
|
|
| | |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Status | {status} |
|
|
|
|
|
|
| Stand | 2026-09-23 |
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def lege_prozess_an(wurzel, name="test-prozess", status="Entwurf"):
|
|
|
|
|
|
ordner = wurzel / name
|
|
|
|
|
|
ordner.mkdir()
|
|
|
|
|
|
md = ordner / f"{name}.md"
|
|
|
|
|
|
md.write_text(MODELL, encoding="utf-8")
|
|
|
|
|
|
(ordner / f"{name}.bpmn").write_bytes(pp.erzeuge_bpmn(md))
|
|
|
|
|
|
(ordner / "README.md").write_text(STECKBRIEF.format(status=status), encoding="utf-8")
|
|
|
|
|
|
return ordner
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-25 09:40:52 +02:00
|
|
|
|
def test_bestand_der_bibliothek_und_beispiele_ist_konsistent():
|
|
|
|
|
|
"""Prüft, dass alle Prozessordner der geklonten Prozessbibliothek und die Skill-Beispiele ohne Befund sind."""
|
|
|
|
|
|
if not pp.BIBLIOTHEK.is_dir():
|
|
|
|
|
|
warnings.warn(f"Prozessbibliothek nicht geklont ({pp.BIBLIOTHEK}) – nur die Skill-Beispiele geprüft. "
|
|
|
|
|
|
"Klonen mit: python scripts/setup_workspace.py")
|
2026-09-23 13:02:48 +02:00
|
|
|
|
assert pp.pruefe_alles() == []
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-25 09:40:52 +02:00
|
|
|
|
def test_bibliothek_liegt_als_klon_neben_projektwissen():
|
|
|
|
|
|
"""Prüft, dass die Prozessbibliothek unter dem Klon-Ordner aus workspace.toml/workspace.local.toml gesucht wird."""
|
|
|
|
|
|
import setup_workspace
|
|
|
|
|
|
assert pp.BIBLIOTHEK == setup_workspace.repos_root() / "Prozessbibliothek"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_prozessordner_ohne_punktordner(tmp_path):
|
|
|
|
|
|
"""Prüft, dass .git und andere Punkt-Ordner im Bibliotheks-Klon nicht als Prozess gelten."""
|
|
|
|
|
|
lege_prozess_an(tmp_path)
|
|
|
|
|
|
(tmp_path / ".git").mkdir()
|
|
|
|
|
|
(tmp_path / ".gitea").mkdir()
|
|
|
|
|
|
assert [o.name for o in pp.prozessordner(tmp_path)] == ["test-prozess"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fremde_datei_in_der_bibliothekswurzel_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass in der Wurzel der Bibliothek nur die Repo-Dateien erlaubt sind und alles andere gemeldet wird."""
|
|
|
|
|
|
lege_prozess_an(tmp_path)
|
|
|
|
|
|
for erlaubt in ("README.md", "AGENTS.md", "CLAUDE.md", ".gitattributes", ".gitignore"):
|
|
|
|
|
|
(tmp_path / erlaubt).write_text("", encoding="utf-8")
|
|
|
|
|
|
(tmp_path / "antrag.bpmn").write_text("", encoding="utf-8")
|
|
|
|
|
|
befunde = pp.pruefe_bibliothek(tmp_path)
|
|
|
|
|
|
assert len(befunde) == 1 and "antrag.bpmn" in befunde[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fehlende_bibliothek_ist_kein_befund_aber_ein_hinweis(tmp_path):
|
|
|
|
|
|
"""Prüft, dass eine nicht geklonte Bibliothek keinen Befund, aber einen Hinweis auf setup_workspace.py ergibt."""
|
|
|
|
|
|
fehlt = tmp_path / "Prozessbibliothek"
|
|
|
|
|
|
assert pp.pruefe_bibliothek(fehlt) == []
|
|
|
|
|
|
assert "setup_workspace.py" in pp.hinweis_bibliothek(fehlt)
|
|
|
|
|
|
fehlt.mkdir()
|
|
|
|
|
|
assert pp.hinweis_bibliothek(fehlt) == ""
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-23 13:02:48 +02:00
|
|
|
|
def test_es_gibt_mindestens_ein_beispiel():
|
|
|
|
|
|
"""Prüft, dass der Abgleich .md/.bpmn wenigstens an den Skill-Beispielen tatsächlich greift."""
|
|
|
|
|
|
assert pp.beispiele(), "keine Beispiele in .agents/skills/sn-bpmn/beispiele/"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_gueltiger_prozessordner_hat_keine_befunde(tmp_path):
|
|
|
|
|
|
"""Prüft, dass ein vollständiger Ordner mit aktueller .bpmn und gültigem Status durchgeht."""
|
|
|
|
|
|
assert pp.pruefe_ordner(lege_prozess_an(tmp_path)) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_status_abgenommen_ist_gueltig(tmp_path):
|
|
|
|
|
|
"""Prüft, dass der Status 'abgenommen' akzeptiert wird."""
|
|
|
|
|
|
assert pp.pruefe_ordner(lege_prozess_an(tmp_path, status="abgenommen")) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_unbekannter_status_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass ein Status außerhalb von Entwurf/abgenommen gemeldet wird."""
|
|
|
|
|
|
befunde = pp.pruefe_ordner(lege_prozess_an(tmp_path, status="fertig"))
|
|
|
|
|
|
assert any("Status 'fertig' unbekannt" in b for b in befunde)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fehlende_statuszeile_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass ein Steckbrief ohne Statuszeile gemeldet wird."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
(ordner / "README.md").write_text("# Testprozess\n", encoding="utf-8")
|
|
|
|
|
|
assert any("Status | … |' fehlt" in b for b in pp.pruefe_ordner(ordner))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ungueltiger_ordnername_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass Großbuchstaben oder Unterstriche im Ordnernamen gemeldet werden."""
|
|
|
|
|
|
befunde = pp.pruefe_ordner(lege_prozess_an(tmp_path, name="Antrag_Pruefen"))
|
|
|
|
|
|
assert any("Ordnername" in b for b in befunde)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fehlende_bpmn_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass ein Ordner ohne erzeugte .bpmn gemeldet wird."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
(ordner / "test-prozess.bpmn").unlink()
|
|
|
|
|
|
befunde = pp.pruefe_ordner(ordner)
|
|
|
|
|
|
assert any("test-prozess.bpmn fehlt" in b for b in befunde)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fremde_datei_im_prozessordner_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass zusätzliche Dateien (etwa eine Original-PDF) im Prozessordner gemeldet werden."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
(ordner / "quelle.pdf").write_bytes(b"%PDF")
|
|
|
|
|
|
assert any("quelle.pdf gehört nicht hierher" in b for b in pp.pruefe_ordner(ordner))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_veraltete_bpmn_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass eine .bpmn gemeldet wird, die nach einer Änderung der .md nicht neu erzeugt wurde."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
md = ordner / "test-prozess.md"
|
|
|
|
|
|
md.write_text(MODELL.replace("Etwas tun", "Etwas anderes tun"), encoding="utf-8")
|
|
|
|
|
|
assert any("passt nicht zu test-prozess.md" in b for b in pp.pruefe_ordner(ordner))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_zeilenenden_der_bpmn_spielen_keine_rolle(tmp_path):
|
|
|
|
|
|
"""Prüft, dass eine unter Windows mit CRLF ausgecheckte .bpmn nicht als veraltet gilt."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
bpmn = ordner / "test-prozess.bpmn"
|
|
|
|
|
|
bpmn.write_bytes(bpmn.read_bytes().replace(b"\n", b"\r\n"))
|
|
|
|
|
|
assert pp.pruefe_ordner(ordner) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_nicht_umwandelbare_md_wird_gemeldet(tmp_path):
|
|
|
|
|
|
"""Prüft, dass eine .md mit Syntaxfehler (doppelte Schritt-ID) als Befund statt als Absturz endet."""
|
|
|
|
|
|
ordner = lege_prozess_an(tmp_path)
|
|
|
|
|
|
(ordner / "test-prozess.md").write_text(MODELL + "S2 Task: Noch einmal S2\n", encoding="utf-8")
|
|
|
|
|
|
assert any("lässt sich nicht umwandeln" in b for b in pp.pruefe_ordner(ordner))
|