"""Gates gegen das mini_repo-Fixture: gültiges Fragment passiert, jede Manipulation wird mit genau dem erwarteten Fehler gefangen.""" from pathlib import Path import gates MINI = Path(__file__).parent / "fixtures" / "mini_repo" GUT = '''# Einheiten: rechner.py stand: UNBEKANNT ## rechner.py::addiere beschreibung: Addiert zwei Zahlen. input: zwei Zahlen a, b output: die Summe entscheidungen: - Reine Funktion ohne Zustand. beleg: "return a + b" kanten: - wird-genutzt-von: anzeige.py::zeige ## rechner.py::Speicher beschreibung: Merkt sich das letzte Ergebnis. input: Werte über merke() output: letzter Wert in self.letztes entscheidungen: - Startet leer. beleg: "self.letztes = None" kanten: - nutzt: rechner.py::addiere ''' def test_gutes_fragment_passiert(): fehler, warnungen, einheiten = gates.pruefe_fragment(GUT, MINI, "rechner.py") assert fehler == [] assert [e["id"] for e in einheiten] == ["rechner.py::addiere", "rechner.py::Speicher"] def test_falsches_zitat(): kaputt = GUT.replace('beleg: "return a + b"', 'beleg: "return a - b"') fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py") assert any("Zitat nicht wörtlich" in f for f in fehler) def test_erfundener_anker(): kaputt = GUT.replace("## rechner.py::addiere", "## rechner.py::subtrahiere") fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py") assert any("Symbol 'subtrahiere' nicht in" in f for f in fehler) def test_kaputte_kante(): kaputt = GUT.replace("- nutzt: rechner.py::addiere", "- nutzt: rechner.py::addiere (wichtig!)") fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py") assert any("Kante unlesbar" in f for f in fehler) def test_unbekannter_kanten_typ(): kaputt = GUT.replace("- nutzt:", "- verwendet:") fehler, _, _ = gates.pruefe_fragment(kaputt, MINI, "rechner.py") assert any("unbekannter Kanten-Typ" in f for f in fehler) def test_fremde_datei_abgelehnt(): fehler, _, _ = gates.pruefe_fragment(GUT, MINI, "anzeige.py") assert any("gehört nicht zur gescannten Datei" in f for f in fehler) def test_leere_antwort(): fehler, _, _ = gates.pruefe_fragment("", MINI, "rechner.py") assert any("keine Einheit geparst" in f for f in fehler) def test_escaping_normalisierung(): frag = GUT.replace('beleg: "return a + b"', 'beleg: "return f\\"Ergebnis: {addiere(a, b)}\\""') frag = frag.replace("## rechner.py::addiere", "## anzeige.py::zeige").replace( "# Einheiten: rechner.py", "# Einheiten: anzeige.py") # nur den umgebauten zeige-Block prüfen block = frag.split("\n\n")[1] fehler, _, _ = gates.pruefe_fragment(block, MINI, "anzeige.py") assert not any("Zitat" in f for f in fehler) def test_fehlende_symbole(): _, _, einheiten = gates.pruefe_fragment(GUT, MINI, "rechner.py") fehlt = gates.fehlende_symbole(["addiere", "skaliere", "Speicher", "BASIS"], einheiten, []) assert fehlt == ["skaliere", "BASIS"] fehlt2 = gates.fehlende_symbole(["skaliere"], einheiten, ["rechner.py::skaliere"]) assert fehlt2 == [] def test_repariere_kanten(): """Erfundene Kanten-Ziele werden gestrichen (Datei erfunden, Symbol erfunden, Prefix fehlt) — gültige Kanten und alle anderen Zeilen bleiben unangetastet.""" kaputt = GUT.replace( "- nutzt: rechner.py::addiere", "- nutzt: rechner.py::addiere\n- ruft-auf: erfunden.py::x\n" "- nutzt: rechner.py::gibtsnicht\n- löst-aus: nur_symbol") bereinigt, gestrichen = gates.repariere_kanten(kaputt, MINI, "rechner.py") assert len(gestrichen) == 3 assert "- nutzt: rechner.py::addiere" in bereinigt assert "erfunden.py" not in bereinigt and "gibtsnicht" not in bereinigt fehler, _, _ = gates.pruefe_fragment(bereinigt, MINI, "rechner.py") assert fehler == [] def test_repariere_belege_schnappt_fast_richtige_zitate(): """Ein-Zeichen-Abweichung (`=` statt Original) wird auf die echte Quellzeile geschnappt; frei erfundene Zitate bleiben stehen und fallen im Gate durch.""" kaputt = GUT.replace('beleg: "return a + b"', 'beleg: "return a+b"') repariert, korrigiert = gates.repariere_belege(kaputt, MINI, "rechner.py") assert len(korrigiert) == 1 fehler, _, _ = gates.pruefe_fragment(repariert, MINI, "rechner.py") assert fehler == [] erfunden = GUT.replace('beleg: "return a + b"', 'beleg: "raise TotalErfunden(xyz)"') repariert2, korrigiert2 = gates.repariere_belege(erfunden, MINI, "rechner.py") assert korrigiert2 == [] fehler2, _, _ = gates.pruefe_fragment(repariert2, MINI, "rechner.py") assert any("Zitat nicht wörtlich" in f for f in fehler2) def test_qualifizierte_methode_als_symbol(): frag = GUT.replace("## rechner.py::Speicher", "## rechner.py::Speicher.merke") fehler, _, _ = gates.pruefe_fragment(frag, MINI, "rechner.py") assert not any("Speicher.merke" in f for f in fehler) def test_beleg_mit_anfuehrungszeichen(): """Belege mit eingebetteten Quotes/Regex (Befund 17) parsen und passieren.""" frag = ('## anzeige.py::zeige\nbeschreibung: Formatiert.\ninput: a, b\noutput: Text\n' 'entscheidungen:\n- Nutzt einen f-String.\n' ' beleg: "return f"Ergebnis: {addiere(a, b)}""\nkanten:\n') fehler, _, _ = gates.pruefe_fragment(frag, MINI, "anzeige.py") assert fehler == [] def test_datei_einheit_ohne_symbole(tmp_path): """Skript ohne def/class: Datei-Einheit `## pfad` (Befund 18).""" (tmp_path / "skript.py").write_text('import sys\nprint(sys.argv)\n') frag = ('## skript.py\nbeschreibung: Gibt Argumente aus.\ninput: argv\noutput: stdout\n' 'entscheidungen:\n- Direkter Druck ohne Formatierung.\n beleg: "print(sys.argv)"\nkanten:\n') fehler, _, einheiten = gates.pruefe_fragment(frag, tmp_path, "skript.py") assert fehler == [] and einheiten[0]["id"] == "skript.py" def test_referenz_korrektur_falscher_prefix(): """Symbol existiert eindeutig unter anderer Datei → ID wird korrigiert (Befund 19).""" sicht = "### Kann reparieren\neinheiten: falsch.py::_fix, kaputt.py::gibtsnirgends\n" text, gestrichen = gates.repariere_referenzen(sicht, {"richtig.py::_fix", "a.py::x"}) assert "richtig.py::_fix" in text assert gestrichen == ["kaputt.py::gibtsnirgends"] def test_referenz_integritaet(): sicht = "### Kann rechnen\neinheiten: rechner.py::addiere, rechner.py::gibtsnicht\n" fehler = gates.pruefe_referenzen(sicht, {"rechner.py::addiere"}) assert fehler == ["unbekannte Einheiten-Referenz: rechner.py::gibtsnicht"]