"""Tests für scripts/einlesen.py (Skill sn-einlesen) – reines Python/pytest, in einem temporären Workspace.""" import subprocess import sys from datetime import datetime, timedelta, timezone from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) import einlesen # noqa: E402 ARBEITSSCHRITTE = """Prozess: Antrag prüfen S1 Start: Antrag geht ein S2 Task: Antrag erfassen S3 Ende: Antrag entschieden """ ROHTEXT = "# Wegebau abnehmen\n\nNach Fertigstellung wird der Weg begangen und die Abnahme protokolliert.\n" FESTE_ZEIT = datetime(2026, 9, 23, 14, 5, 12, tzinfo=timezone(timedelta(hours=2))) @pytest.fixture def ws(tmp_path, monkeypatch): """Leerer Workspace mit input/, entwuerfe/ und geklonter Prozessbibliothek an Stelle des echten.""" for name in ("input", "Prozessbibliothek", "entwuerfe"): (tmp_path / name).mkdir() monkeypatch.setattr(einlesen, "ROOT", tmp_path) monkeypatch.setattr(einlesen, "EINGANG", tmp_path / "input") monkeypatch.setattr(einlesen, "BIBLIOTHEK", tmp_path / "Prozessbibliothek") monkeypatch.setattr(einlesen, "ENTWUERFE", tmp_path / "entwuerfe") return tmp_path def lege_prozess_an(ws, name, text): ordner = ws / "Prozessbibliothek" / name ordner.mkdir() (ordner / f"{name}.md").write_text(text, encoding="utf-8") (ordner / "README.md").write_text(f"# {name}\n\n| | |\n|---|---|\n| Status | Entwurf |\n", encoding="utf-8") def eintrag(bericht, dateiname): return next(d for d in bericht["dateien"] if d["datei"].endswith(dateiname)) def test_fehlender_eingangsordner_wird_angelegt(ws): """Prüft, dass input/ angelegt wird, wenn es fehlt, und das im Bericht steht.""" (ws / "input").rmdir() bericht = einlesen.pruefe_eingang() assert (ws / "input").is_dir() assert bericht["angelegt"] is True assert bericht["dateien"] == [] def test_andere_dateitypen_werden_abgelehnt(ws): """Prüft, dass .docx, .pdf und .txt abgelehnt und nicht angefasst werden.""" for name in ("a.docx", "b.pdf", "c.txt"): (ws / "input" / name).write_bytes(b"inhalt") bericht = einlesen.pruefe_eingang() assert {d["status"] for d in bericht["dateien"]} == {"abgelehnt"} assert all("nur .md" in d["grund"] for d in bericht["dateien"]) assert (ws / "input" / "a.docx").read_bytes() == b"inhalt" def test_unterordner_leere_und_nicht_utf8_dateien_werden_abgelehnt(ws): """Prüft, dass Unterordner, leere .md und .md in cp1252 mit Grund abgelehnt werden.""" (ws / "input" / "unter").mkdir() (ws / "input" / "leer.md").write_text("\n", encoding="utf-8") (ws / "input" / "alt.md").write_bytes("Straße".encode("cp1252")) bericht = einlesen.pruefe_eingang() assert "Unterordner" in eintrag(bericht, "unter")["grund"] assert "leer" in eintrag(bericht, "leer.md")["grund"] assert "UTF-8" in eintrag(bericht, "alt.md")["grund"] def test_systemdateien_werden_still_uebergangen(ws): """Prüft, dass desktop.ini und versteckte Dateien gar nicht erst im Bericht auftauchen.""" (ws / "input" / "desktop.ini").write_text("x", encoding="utf-8") (ws / "input" / ".gitkeep").write_text("", encoding="utf-8") assert einlesen.pruefe_eingang()["dateien"] == [] def test_neue_md_mit_titel_namensvorschlag_und_art(ws): """Prüft, dass eine neue .md Titel, Ordnernamen-Vorschlag und die Art (Rohtext/Arbeitsschritte) bekommt.""" (ws / "input" / "roh.md").write_text(ROHTEXT, encoding="utf-8") (ws / "input" / "schritte.md").write_text(ARBEITSSCHRITTE, encoding="utf-8") bericht = einlesen.pruefe_eingang() roh, schritte = eintrag(bericht, "roh.md"), eintrag(bericht, "schritte.md") assert roh["status"] == "neu" and roh["titel"] == "Wegebau abnehmen" assert roh["vorschlag_name"] == "wegebau-abnehmen" and roh["arbeitsschritt_md"] is False assert schritte["vorschlag_name"] == "antrag-pruefen" and schritte["arbeitsschritt_md"] is True def test_gleichnamiger_prozess_wird_als_kandidat_mit_1_gemeldet(ws): """Prüft, dass ein Prozess der Bibliothek mit gleichem Ordnernamen als Kandidat mit Ähnlichkeit 1 erscheint.""" lege_prozess_an(ws, "antrag-pruefen", ARBEITSSCHRITTE) (ws / "input" / "neu.md").write_text(ARBEITSSCHRITTE.replace("erfassen", "prüfen"), encoding="utf-8") kandidaten = eintrag(einlesen.pruefe_eingang(), "neu.md")["kandidaten"] assert kandidaten[0]["pfad"] == "Prozessbibliothek/antrag-pruefen/" assert kandidaten[0]["aehnlichkeit"] == 1.0 def test_inhaltlich_aehnlicher_entwurf_wird_gefunden_fremder_nicht(ws): """Prüft, dass ein ähnlicher lokaler Entwurf als Kandidat erscheint, ein fachfremder Prozess dagegen nicht.""" (ws / "entwuerfe" / "antragsbearbeitung.md").write_text( "Prozess: Antragsbearbeitung\nS1 Start: Antrag geht ein\nS2 Task: Antrag erfassen und prüfen\nS3 Ende: Fertig\n", encoding="utf-8") lege_prozess_an(ws, "wegebau-abnehmen", "Prozess: Wegebau abnehmen\nS1 Start: Weg fertig\nS2 Ende: Protokoll\n") (ws / "input" / "antrag.md").write_text(ARBEITSSCHRITTE, encoding="utf-8") pfade = [k["pfad"] for k in eintrag(einlesen.pruefe_eingang(), "antrag.md")["kandidaten"]] assert pfade == ["entwuerfe/antragsbearbeitung.md"] def test_gitea_entwuerfe_zaehlen_nicht_zum_bestand(ws): """Prüft, dass Issue- und Kommentarentwürfe aus scripts/gitea.py nicht als Prozess gelten.""" (ws / "entwuerfe" / "20260923-134657-issue-Projektwissen.md").write_text("# Entwurf", encoding="utf-8") assert einlesen.pruefe_eingang()["bestand"] == [] def test_punktordner_der_bibliothek_zaehlen_nicht_zum_bestand(ws): """Prüft, dass .git und andere Punkt-Ordner im Bibliotheks-Klon nicht als Prozess gelten.""" (ws / "Prozessbibliothek" / ".git").mkdir() assert einlesen.pruefe_eingang()["bestand"] == [] def test_bibliothek_ausserhalb_des_workspace_wird_mit_ihrem_namen_genannt(ws, tmp_path_factory, monkeypatch): """Prüft, dass ein Prozess auch dann als 'Prozessbibliothek//' erscheint, wenn der Klon woanders liegt.""" anderswo = tmp_path_factory.mktemp("klone") / "Prozessbibliothek" anderswo.mkdir() monkeypatch.setattr(einlesen, "BIBLIOTHEK", anderswo) ordner = anderswo / "antrag-pruefen" ordner.mkdir() (ordner / "antrag-pruefen.md").write_text(ARBEITSSCHRITTE, encoding="utf-8") assert [e["pfad"] for e in einlesen.pruefe_eingang()["bestand"]] == ["Prozessbibliothek/antrag-pruefen/"] def test_vermerk_steht_oben_mit_zeitstempel_und_markiert_als_eingelesen(ws): """Prüft, dass der Vermerk als erste Zeile mit Zeitstempel geschrieben wird und die Datei danach als eingelesen gilt.""" datei = ws / "input" / "antrag.md" datei.write_text(ARBEITSSCHRITTE, encoding="utf-8") zeile = einlesen.setze_vermerk(datei, "neu", "entwuerfe/antrag-pruefen.md", jetzt=FESTE_ZEIT) inhalt = datei.read_text(encoding="utf-8") assert inhalt.splitlines()[0] == zeile assert zeile.startswith("