"""Viewer-Service: Parser, Drift, Doku-Ordnung, Projektliste.""" from pathlib import Path import config import projekte import viewer MINI = Path(__file__).parent / "fixtures" / "mini_repo" def test_lese_einheiten_mit_entscheidungen(): text = """# Einheiten: a.py stand: abc ## a.py::eins beschreibung: Macht eins. input: x output: y entscheidungen: - Erste Entscheidung. beleg: "code hier" - Zweite mit Umbruch und Fortsetzung. kanten: - ruft-auf: a.py::zwei ## b.py beschreibung: Datei-Einheit. """ e = viewer.lese_einheiten(text) eins = e["a.py::eins"] assert eins["entscheidungen"] == ["Erste Entscheidung.", "Zweite mit Umbruch und Fortsetzung."] assert eins["kanten"] == [{"typ": "ruft-auf", "ziel": "a.py::zwei"}] assert (eins["input"], eins["output"]) == ("x", "y") assert e["b.py"]["beschreibung"] == "Datei-Einheit." def test_lese_kern_und_pruefung(): kern = viewer.lese_kern("# Kern: demo\nZwei Sätze.\n\ntragende-einheiten: a.py::x, b.py::y\n") assert kern["titel"] == "demo" and kern["tragende_einheiten"] == ["a.py::x", "b.py::y"] pr = viewer.lese_pruefung("# Prüfung: demo\ntests: make test\nlint: (keins vorhanden)\n" "start: make dev\nhinweise:\n- Vorsicht.\n") assert pr["tests"] == "make test" and pr["hinweise"] == ["Vorsicht."] def test_lese_features_architektur_flows(): b = viewer.lese_features("# Bereich: rechnen\nbeschreibung: Rechnet.\n\n" "## Feature: Addieren [kern]\nbeschreibung: Summen.\n\n" "### Kann addieren\neinheiten: a.py::eins\n") assert b["features"][0]["flag"] == "kern" assert b["features"][0]["teilfeatures"][0]["einheiten"] == ["a.py::eins"] a = viewer.lese_architektur("# Architektur: demo\n\n## Komponente: kernstück\n" "beschreibung: Trägt.\neinheiten: a.py::eins\n" "beziehungen:\n- nutzt: anzeige\n") assert a[0]["beziehungen"] == [{"typ": "nutzt", "ziel": "anzeige"}] f = viewer.lese_flows("# Flows: demo\n\n## Flow: ablauf\n1. Schritt eins — a.py::eins\n" "2. Schritt ohne Referenz\n") assert f[0]["schritte"][0]["einheiten"] == ["a.py::eins"] assert f[0]["schritte"][1]["einheiten"] == [] def test_code_extrahieren(): assert "return a + b" in viewer.code_extrahieren(MINI, "rechner.py::addiere") assert viewer.code_extrahieren(MINI, "rechner.py::BASIS").startswith("BASIS = 10") assert viewer.code_extrahieren(MINI, "rechner.py::gibtsnicht") == "" assert viewer.code_extrahieren(MINI, "wegdatei.py::x") == "" assert "def merke" in viewer.code_extrahieren(MINI, "rechner.py::Speicher.merke") async def test_lade_projekt_und_drift(gescanntes_repo): wurzel, git = gescanntes_repo projekt = viewer.lade_projekt(wurzel) assert "rechner.py::addiere" in projekt["einheiten"] assert projekt["kern"]["titel"] == "mini" assert projekt["staende"]["rechner.py"] assert viewer.drift(wurzel, projekt["staende"])["veraltet"] == [] (wurzel / "rechner.py").write_text((wurzel / "rechner.py").read_text() + "\nX2 = 2\n") git("add", "-A") git("commit", "-m", "aenderung") d = viewer.drift(wurzel, projekt["staende"]) assert d["veraltet"] == ["rechner.py"] # anzeige.py unverändert def test_doku_ordnung_mit_zyklus(): projekt = { "bereiche": [ {"name": "oben", "features": [{"teilfeatures": [{"einheiten": ["a.py::x"]}]}]}, {"name": "unten", "features": [{"teilfeatures": [{"einheiten": ["b.py::y"]}]}]}, ], "einheiten": { "a.py::x": {"kanten": [{"typ": "ruft-auf", "ziel": "b.py::y"}]}, "b.py::y": {"kanten": []}, }, } assert viewer.doku_ordnung(projekt) == ["unten", "oben"] # Fundament zuerst # Zyklus: beide nutzen einander → deterministisch, beide enthalten projekt["einheiten"]["b.py::y"]["kanten"] = [{"typ": "nutzt", "ziel": "a.py::x"}] ordnung = viewer.doku_ordnung(projekt) assert sorted(ordnung) == ["oben", "unten"] and len(ordnung) == 2 def test_projektliste(tmp_path, monkeypatch): echt = tmp_path / "echt" echt.mkdir() (tmp_path / "kopie").mkdir() datei = tmp_path / "projekte.txt" datei.write_text(f"# Kommentar\n{echt}\n{tmp_path}/weg\n{tmp_path}/kopie\n{echt}\n") monkeypatch.setattr(config, "PROJEKTE_DATEI", datei) liste = projekte.lade() assert [p.get("fehler") is None for p in liste] == [True, False, True, False] assert "existiert nicht" in liste[1]["fehler"] assert "Namenskollision" in liste[3]["fehler"] assert projekte.finde("echt") == echt and projekte.finde("weg") is None