update
This commit is contained in:
532
backend/tests/test_ledger.py
Normal file
532
backend/tests/test_ledger.py
Normal file
@@ -0,0 +1,532 @@
|
||||
"""Das Gedächtnis der Inhaltsprüfungen: was gilt weiter, was wird neu gefragt.
|
||||
|
||||
Die Prüfungen 2 und 3 fragen ein Modell. Dieselbe Frage kippt bei Grenzfällen auch
|
||||
bei Temperatur 0 — vorher meldete Runde 1 einen Befund, Runde 2 keinen, Runde 3
|
||||
wieder einen. Hier steht, unter welchen Bedingungen ein Urteil stehen bleibt.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import agents
|
||||
import features
|
||||
import inhalt
|
||||
import ledger
|
||||
import projekte
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def projekt(datenordner):
|
||||
projekte.anlegen("Test")
|
||||
return "Test"
|
||||
|
||||
|
||||
def karte(projekt, titel, *funktionen):
|
||||
"""funktionen sind (name, regel) oder (name, regel, [testschritte])."""
|
||||
text = f"<!-- titel -->\n{titel}\n"
|
||||
for eintrag in funktionen:
|
||||
name, regel, schritte = (*eintrag, [])[:3]
|
||||
text += f"<!-- funktion -->\n{name}\n<!-- regeln -->\n- {regel}\n"
|
||||
if schritte:
|
||||
text += "<!-- testen -->\n" + "".join(f"- {s}\n" for s in schritte)
|
||||
return features.anlegen(projekt, text)
|
||||
|
||||
|
||||
QUELLE = {"frontend/src/App.vue": '<button @click="starte">los</button>\n'}
|
||||
|
||||
|
||||
class Zaehler:
|
||||
"""Ersetzt das Modell und zählt mit, wie oft überhaupt gefragt wurde."""
|
||||
|
||||
def __init__(self, antwort):
|
||||
self.antwort = antwort
|
||||
self.rufe = 0
|
||||
|
||||
async def __call__(self, system, nachricht, temperatur=0.0):
|
||||
self.rufe += 1
|
||||
wert = self.antwort
|
||||
return wert(nachricht, self.rufe) if callable(wert) else wert
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Datei
|
||||
|
||||
def test_leeres_gedaechtnis_hat_alle_faecher(projekt):
|
||||
stand = ledger.lade(projekt)
|
||||
assert set(stand) == set(ledger.FAECHER)
|
||||
assert all(v == {} for v in stand.values())
|
||||
|
||||
|
||||
def test_kaputte_datei_wird_wie_leer_behandelt(projekt):
|
||||
(projekte.wurzel(projekt) / ledger.DATEI).write_text("{kein json")
|
||||
assert ledger.lade(projekt)["belege"] == {}
|
||||
|
||||
|
||||
def test_gemerktes_ueberlebt_den_lauf(projekt):
|
||||
stand = ledger.lade(projekt)
|
||||
stand["belege"][ledger.schluessel("a", "Tun")] = {"abdruck": "x", "datei": "a.py",
|
||||
"beleg": "def tun():"}
|
||||
ledger.sichere(projekt, stand)
|
||||
assert ledger.lade(projekt)["belege"] == stand["belege"]
|
||||
|
||||
|
||||
def test_schluessel_bleibt_zerlegbar(projekt):
|
||||
"""Titel dürfen alles enthalten — auch das, was als Trennzeichen taugen würde."""
|
||||
fies = 'Klick auf "x", dann | und \\ und ["a","b"]'
|
||||
assert ledger.teile(ledger.schluessel("karte", fies)) == ["karte", fies]
|
||||
|
||||
|
||||
def test_gedaechtnis_ist_kein_feature(projekt):
|
||||
karte(projekt, "Eins")
|
||||
ledger.sichere(projekt, ledger.lade(projekt))
|
||||
assert [f["sektionen"]["titel"] for f in features.liste(projekt)] == ["Eins"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Prüfung 2
|
||||
|
||||
GEFUNDEN = json.dumps({"gefunden": True, "datei": "frontend/src/App.vue",
|
||||
"beleg": '<button @click="starte">los</button>'})
|
||||
|
||||
|
||||
async def test_beleg_wird_gemerkt_und_beim_zweiten_lauf_nicht_gefragt(
|
||||
projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(GEFUNDEN)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
alle = features.liste(projekt)
|
||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
||||
assert await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text) == []
|
||||
erste_runde = modell.rufe
|
||||
assert erste_runde >= len(inhalt.SUCHE_TEMPERATUREN)
|
||||
|
||||
assert await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text) == []
|
||||
assert modell.rufe == erste_runde # keine einzige neue Anfrage
|
||||
|
||||
|
||||
async def test_beleg_verfaellt_wenn_die_zeile_verschwindet(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(GEFUNDEN)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
alle = features.liste(projekt)
|
||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
||||
await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
||||
vorher = modell.rufe
|
||||
|
||||
anders = {"frontend/src/App.vue": "<div>nichts mehr</div>\n"}
|
||||
await inhalt.pruefe_feature_im_code(projekt, alle, anders, "### x\n<div>")
|
||||
assert modell.rufe > vorher # der Beleg trägt nicht mehr, also neu fragen
|
||||
|
||||
|
||||
async def test_geaenderte_funktion_wird_neu_gefragt(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(GEFUNDEN)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
||||
await inhalt.pruefe_feature_im_code(projekt, features.liste(projekt), QUELLE, text)
|
||||
vorher = modell.rufe
|
||||
|
||||
datei = projekte.wurzel(projekt) / f"{features.liste(projekt)[0]['id']}.md"
|
||||
datei.write_text(datei.read_text().replace("- Knopf", "- Ganz anderer Inhalt"))
|
||||
await inhalt.pruefe_feature_im_code(projekt, features.liste(projekt), QUELLE, text)
|
||||
assert modell.rufe > vorher
|
||||
|
||||
|
||||
async def test_fehlanzeige_gilt_nur_bei_unveraendertem_quelltext(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(json.dumps({"gefunden": False, "grund": "gibt es nicht"}))
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
alle = features.liste(projekt)
|
||||
text = "### frontend/src/App.vue\n" + QUELLE["frontend/src/App.vue"]
|
||||
erste = await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
||||
assert len(erste) == 1
|
||||
vorher = modell.rufe
|
||||
|
||||
zweite = await inhalt.pruefe_feature_im_code(projekt, alle, QUELLE, text)
|
||||
assert [p["vorschlag"] for p in zweite] == [p["vorschlag"] for p in erste]
|
||||
assert modell.rufe == vorher
|
||||
|
||||
# Neuer Quelltext: eine Löschempfehlung muss frisch belegt werden
|
||||
mehr = {**QUELLE, "backend/neu.py": "def starte():\n pass\n"}
|
||||
await inhalt.pruefe_feature_im_code(projekt, alle, mehr, text)
|
||||
assert modell.rufe > vorher
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Dubletten
|
||||
|
||||
def punkt(schluessel="bedienen:starte", beleg='<button @click="starte">los</button>'):
|
||||
return {"schluessel": schluessel, "art": "Bedienung",
|
||||
"datei": "frontend/src/App.vue", "zeile": 1, "beleg": beleg,
|
||||
"umfeld": beleg, "rumpf": ""}
|
||||
|
||||
|
||||
async def test_gleicher_beleg_macht_zwei_funktionen_zu_kandidaten(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
karte(projekt, "Zwei", ("Lauf beginnen", "Knopf"))
|
||||
monkeypatch.setattr(agents, "frage", Zaehler(json.dumps({"gegenstand_gleich": True, "ergebnis_gleich": True})))
|
||||
|
||||
alle = features.liste(projekt)
|
||||
stand = ledger.lade(projekt)
|
||||
for f in alle:
|
||||
stand["belege"][ledger.schluessel(f["id"], f["sektionen"]["funktionen"][0]["text"])] = {
|
||||
"abdruck": "egal", "datei": "frontend/src/App.vue",
|
||||
"beleg": '<button @click="starte">los</button>'}
|
||||
gefunden = await inhalt.dubletten(alle, stand)
|
||||
assert len(gefunden) == 1
|
||||
# Gleich stark: der Titel entscheidet, damit der Vorschlag nicht springt
|
||||
assert gefunden[0]["titel"] == "Etwas starten"
|
||||
assert "Lauf beginnen" in gefunden[0]["grund"]
|
||||
|
||||
|
||||
async def test_uneiniges_urteil_ist_keine_dublette_und_wird_gemerkt(
|
||||
projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
karte(projekt, "Zwei", ("Lauf beginnen", "Knopf"))
|
||||
|
||||
def wechselhaft(nachricht, ruf):
|
||||
return json.dumps({"gegenstand_gleich": True, "ergebnis_gleich": ruf == 1})
|
||||
|
||||
modell = Zaehler(wechselhaft)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
alle = features.liste(projekt)
|
||||
stand = ledger.lade(projekt)
|
||||
for f in alle:
|
||||
stand["belege"][ledger.schluessel(f["id"], f["sektionen"]["funktionen"][0]["text"])] = {
|
||||
"abdruck": "egal", "datei": "a.vue", "beleg": "x"}
|
||||
assert await inhalt.dubletten(alle, stand) == []
|
||||
vorher = modell.rufe
|
||||
assert await inhalt.dubletten(alle, stand) == []
|
||||
assert modell.rufe == vorher # Ablehnung wird gemerkt, nicht neu bezahlt
|
||||
|
||||
|
||||
def test_gegenstueck_faellt_nicht_ueber_den_titel():
|
||||
"""anlegen/entfernen und öffnen/schließen dürfen den Titelkanal nicht auslösen —
|
||||
sonst kostet jedes Gegensatzpaar drei Anfragen für nichts."""
|
||||
for a, b in (("Feature anlegen", "Feature entfernen"),
|
||||
("Panel öffnen", "Panel schließen")):
|
||||
assert inhalt._aehnlich(a, b) < inhalt.TITEL_KANAL
|
||||
|
||||
|
||||
ENTFERNEN = ["Vollmodus öffnen", "RMB auf die Funktion",
|
||||
'Klick auf "Funktion entfernen"', "Funktion entfernt"]
|
||||
|
||||
|
||||
def test_umschriebene_dublette_wird_kandidat(projekt):
|
||||
"""Anderer Titel, andere Karte, dieselbe Sache. Weder Titel noch Beleg fangen
|
||||
das — nur der Inhaltskanal."""
|
||||
karte(projekt, "Funktionen",
|
||||
("Funktion entfernen", "x-Icon", ENTFERNEN),
|
||||
("Funktion anlegen", "+-Icon", ["Vollmodus öffnen", "RMB auf die Karte",
|
||||
'Klick auf "Funktion erstellen"',
|
||||
"Funktion angelegt"]),
|
||||
("Testschritte anpassen", "Ein Schritt je Zeile",
|
||||
["Vollmodus öffnen", "LMB auf Testen", "Text ändern", "Schritte angepasst"]))
|
||||
karte(projekt, "Features", ("Funktion löschen", "x-Icon", ENTFERNEN))
|
||||
|
||||
alle = inhalt._funktionen(features.liste(projekt))
|
||||
paare = {frozenset((alle[a]["titel"], alle[b]["titel"])) for _, a, b, _ in
|
||||
inhalt._kandidaten(alle, {})}
|
||||
assert frozenset(("Funktion entfernen", "Funktion löschen")) in paare
|
||||
assert frozenset(("Funktion anlegen", "Funktion entfernen")) not in paare
|
||||
|
||||
|
||||
def test_floskeln_zaehlen_nicht_mit(projekt):
|
||||
"""Was fast überall steht, unterscheidet nichts und fliegt aus dem Vergleich."""
|
||||
schritte = ["Vollmodus öffnen", "Text ändern", "Angepasst"]
|
||||
karte(projekt, "Eins",
|
||||
*[(f"{feld} anpassen", "eine Regel", schritte)
|
||||
for feld in ("Titel", "Ziel", "Regeln", "Testen", "Badge")])
|
||||
kerne = inhalt._kerne(inhalt._funktionen(features.liste(projekt)))
|
||||
gemeinsam = set.intersection(*kerne.values())
|
||||
assert gemeinsam == set() # „vollmodus", „ändern", „angepasst" sind raus
|
||||
assert all(k for k in kerne.values()) # jede Funktion behält ihr eigenes Wort
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Prüfung 3
|
||||
|
||||
ABGEDECKT = json.dumps({"abgedeckt": True, "funktion": "Etwas starten"})
|
||||
FEHLT = json.dumps({"abgedeckt": False, "karte": "Eins", "titel": "Neu bedienen"})
|
||||
|
||||
|
||||
async def test_abdeckung_wird_gemerkt_solange_die_funktion_dasteht(
|
||||
projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(ABGEDECKT)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
alle = features.liste(projekt)
|
||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
||||
vorher = modell.rufe
|
||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
||||
assert modell.rufe == vorher
|
||||
|
||||
# Funktion weg: die Abdeckung trägt nicht mehr
|
||||
ohne = features.liste(projekt)
|
||||
ohne[0]["sektionen"]["funktionen"] = []
|
||||
await inhalt.code_im_feature(ohne, [punkt()], stand)
|
||||
assert modell.rufe > vorher
|
||||
|
||||
|
||||
async def test_geaenderte_codestelle_wird_neu_beurteilt(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(ABGEDECKT)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
alle = features.liste(projekt)
|
||||
await inhalt.code_im_feature(alle, [punkt()], stand)
|
||||
vorher = modell.rufe
|
||||
await inhalt.code_im_feature(alle, [punkt(beleg="ganz anderer Code")], stand)
|
||||
assert modell.rufe > vorher
|
||||
|
||||
|
||||
async def test_fehlmeldung_bleibt_wortgleich_bis_die_funktion_da_ist(
|
||||
projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(FEHLT)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
alle = features.liste(projekt)
|
||||
erste = await inhalt.code_im_feature(alle, [punkt()], stand)
|
||||
assert [x["titel"] for x in erste] == ["Neu bedienen"]
|
||||
vorher = modell.rufe
|
||||
|
||||
zweite = await inhalt.code_im_feature(alle, [punkt()], stand)
|
||||
assert zweite == erste
|
||||
assert modell.rufe == vorher # kein Flackern, keine neue Anfrage
|
||||
|
||||
|
||||
async def test_angenommener_vorschlag_loest_die_fehlmeldung_ab(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
modell = Zaehler(FEHLT)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand)
|
||||
vorher = modell.rufe
|
||||
|
||||
# Der Nutzer nimmt den Vorschlag an — jetzt muss neu beurteilt werden
|
||||
modell.antwort = ABGEDECKT
|
||||
karte(projekt, "Zwei", ("Neu bedienen", "Knopf"))
|
||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand) == []
|
||||
assert modell.rufe > vorher
|
||||
|
||||
|
||||
async def test_unentschiedenes_urteil_wird_festgehalten(projekt, monkeypatch):
|
||||
"""Drei zu drei ist ein Münzwurf. Der darf einmal fallen, nicht jede Runde."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
|
||||
def haelfte(nachricht, ruf):
|
||||
return ABGEDECKT if ruf % 2 else FEHLT
|
||||
|
||||
modell = Zaehler(haelfte)
|
||||
monkeypatch.setattr(agents, "frage", modell)
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
alle = features.liste(projekt)
|
||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
||||
vorher = modell.rufe
|
||||
assert await inhalt.code_im_feature(alle, [punkt()], stand) == []
|
||||
assert modell.rufe == vorher
|
||||
|
||||
|
||||
async def test_verschwundene_stelle_raeumt_ihren_eintrag_weg(projekt, monkeypatch):
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
monkeypatch.setattr(agents, "frage", Zaehler(ABGEDECKT))
|
||||
|
||||
stand = ledger.lade(projekt)
|
||||
alle = features.liste(projekt)
|
||||
await inhalt.code_im_feature(alle, [punkt()], stand)
|
||||
assert stand["abdeckung"]
|
||||
await inhalt.code_im_feature(alle, [punkt(schluessel="bedienen:anderes")], stand)
|
||||
assert "bedienen:starte" not in stand["abdeckung"]
|
||||
|
||||
|
||||
def test_fehlende_stelle_landet_auch_auf_erfundener_karte(projekt):
|
||||
"""Sechs Stimmen sagen „fehlt", nennen aber Karten, die es nicht gibt. Vorher
|
||||
fiel der Befund lautlos weg."""
|
||||
karte(projekt, "Projekte", ("Projekt anlegen", "+-Icon"))
|
||||
karten = {f["sektionen"]["titel"]: f for f in features.liste(projekt)}
|
||||
urteile = [{"abgedeckt": False, "karte": "Datenverwaltung", "titel": "x"}] * 4
|
||||
assert inhalt._zielkarte(urteile, karten) is karten["Projekte"]
|
||||
|
||||
|
||||
def test_erster_aufloesbarer_kartenname_gewinnt(projekt):
|
||||
karte(projekt, "Projekte", ("Projekt anlegen", "+-Icon"))
|
||||
karte(projekt, "Darkmode", ("Darkmode umschalten", "Icon"))
|
||||
karten = {f["sektionen"]["titel"]: f for f in features.liste(projekt)}
|
||||
urteile = ([{"abgedeckt": False, "karte": "Export", "titel": "x"}] * 3
|
||||
+ [{"abgedeckt": False, "karte": "Darkmode", "titel": "x"}] * 2)
|
||||
assert inhalt._zielkarte(urteile, karten) is karten["Darkmode"]
|
||||
|
||||
|
||||
def test_ohne_karten_kein_ziel(projekt):
|
||||
assert inhalt._zielkarte([{"abgedeckt": False, "karte": "X"}], {}) is None
|
||||
|
||||
|
||||
def test_grund_trennt_dublette_von_fehlendem_code():
|
||||
"""Beides heißt streichen — aber aus verschiedenen Gründen, und der Grund
|
||||
entscheidet, ob man zustimmt."""
|
||||
assert inhalt._wegfall_grund([{"art": "dublette"}]) == "1 Funktion doppelt"
|
||||
assert inhalt._wegfall_grund([{"art": "ohne_code"}] * 2) \
|
||||
== "2 Funktionen ohne eigenen Code"
|
||||
assert inhalt._wegfall_grund([{"art": "dublette"}, {"art": "ohne_code"}]) \
|
||||
== "1 Funktion doppelt, 1 Funktion ohne eigenen Code"
|
||||
|
||||
|
||||
def test_halbe_datei_ueberschreibt_nicht_die_ganze(projekt, monkeypatch):
|
||||
"""Absturz beim Schreiben: die alte Datei muss stehen bleiben."""
|
||||
stand = ledger.lade(projekt)
|
||||
stand["belege"]["a"] = {"abdruck": "x", "datei": "a.py", "beleg": "z"}
|
||||
ledger.sichere(projekt, stand)
|
||||
|
||||
echt = ledger.json.dumps
|
||||
|
||||
def platzt(*a, **k):
|
||||
raise MemoryError("mittendrin")
|
||||
|
||||
monkeypatch.setattr(ledger.json, "dumps", platzt)
|
||||
with pytest.raises(MemoryError):
|
||||
ledger.sichere(projekt, {"belege": {"b": {}}})
|
||||
monkeypatch.setattr(ledger.json, "dumps", echt)
|
||||
assert ledger.lade(projekt)["belege"] == stand["belege"]
|
||||
|
||||
|
||||
async def test_ausgefallene_stimmen_merken_nichts(projekt, monkeypatch):
|
||||
"""Fällt der Anbieter aus, darf keine Löschempfehlung im Gedächtnis landen."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
|
||||
async def tot(system, nachricht, temperatur=0.0):
|
||||
raise RuntimeError("429")
|
||||
|
||||
monkeypatch.setattr(agents, "frage", tot)
|
||||
stand = ledger.lade(projekt)
|
||||
gemeldet = []
|
||||
assert await inhalt.feature_im_code(features.liste(projekt), QUELLE, "x", stand,
|
||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
||||
assert gemeldet == [1] # laut gescheitert, nicht still
|
||||
assert stand["ohne_code"] == {}
|
||||
assert stand["belege"] == {}
|
||||
|
||||
|
||||
async def test_ein_ausfall_rettet_die_anderen_urteile(projekt, monkeypatch):
|
||||
"""Eine kaputte Stelle darf die bezahlten Urteile der anderen nicht wegwerfen."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
|
||||
async def launisch(system, nachricht, temperatur=0.0):
|
||||
if "bedienen:kaputt" in nachricht:
|
||||
raise RuntimeError("429")
|
||||
return ABGEDECKT
|
||||
|
||||
monkeypatch.setattr(agents, "frage", launisch)
|
||||
stand = ledger.lade(projekt)
|
||||
gemeldet = []
|
||||
await inhalt.code_im_feature(
|
||||
features.liste(projekt),
|
||||
[punkt(), punkt(schluessel="bedienen:kaputt", beleg="bedienen:kaputt")],
|
||||
stand, melde=lambda t, n=0: gemeldet.append(n))
|
||||
assert gemeldet == [1]
|
||||
assert "bedienen:starte" in stand["abdeckung"]
|
||||
assert "bedienen:kaputt" not in stand["abdeckung"]
|
||||
|
||||
|
||||
async def test_projekt_ohne_karten_stuerzt_nicht_ab(projekt, monkeypatch):
|
||||
monkeypatch.setattr(agents, "frage", Zaehler(FEHLT))
|
||||
stand = ledger.lade(projekt)
|
||||
assert await inhalt.code_im_feature([], [punkt()], stand) == []
|
||||
assert await inhalt.dubletten([], stand) == []
|
||||
assert await inhalt.feature_im_code([], QUELLE, "x", stand) == []
|
||||
|
||||
|
||||
async def test_feststehendes_urteil_braucht_keine_vollen_stimmen(projekt, monkeypatch):
|
||||
"""Vier von sechs sagen „abgedeckt": die zwei fehlenden könnten die Schwelle
|
||||
nicht mehr reißen. Daraus einen Fehlschlag zu machen wäre Verschwendung."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
ausfaelle = {"n": 0}
|
||||
|
||||
async def zwei_platzen(system, nachricht, temperatur=0.0):
|
||||
ausfaelle["n"] += 1
|
||||
if ausfaelle["n"] <= 2:
|
||||
raise RuntimeError("429")
|
||||
return ABGEDECKT
|
||||
|
||||
monkeypatch.setattr(agents, "frage", zwei_platzen)
|
||||
stand = ledger.lade(projekt)
|
||||
gemeldet = []
|
||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
||||
assert gemeldet == [] # kein Fehler: das Ergebnis stand fest
|
||||
assert "bedienen:starte" in stand["abdeckung"]
|
||||
|
||||
|
||||
async def test_offenes_urteil_bei_ausfall_wird_gemeldet(projekt, monkeypatch):
|
||||
"""Vier Stimmen sagen „fehlt", zwei fehlen — mit ihnen wäre die Schwelle
|
||||
erreicht. Das ist offen und muss laut scheitern, nicht still durchgehen."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
ausfaelle = {"n": 0}
|
||||
|
||||
async def zwei_platzen(system, nachricht, temperatur=0.0):
|
||||
ausfaelle["n"] += 1
|
||||
if ausfaelle["n"] <= 2:
|
||||
raise RuntimeError("429")
|
||||
return FEHLT
|
||||
|
||||
monkeypatch.setattr(agents, "frage", zwei_platzen)
|
||||
stand = ledger.lade(projekt)
|
||||
gemeldet = []
|
||||
assert await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
||||
melde=lambda t, n=0: gemeldet.append(n)) == []
|
||||
assert gemeldet == [1]
|
||||
assert stand["abdeckung"] == {} and stand["ohne_karte"] == {}
|
||||
|
||||
|
||||
async def test_zwei_ueberlebende_stimmen_urteilen_nicht(projekt, monkeypatch):
|
||||
"""Rechnerisch könnten zwei Stimmen die Schwelle unerreichbar machen. Als
|
||||
dauerhaft gemerktes Urteil ist das zu wenig."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
ausfaelle = {"n": 0}
|
||||
|
||||
async def vier_platzen(system, nachricht, temperatur=0.0):
|
||||
ausfaelle["n"] += 1
|
||||
if ausfaelle["n"] <= 4:
|
||||
raise RuntimeError("429")
|
||||
return ABGEDECKT
|
||||
|
||||
monkeypatch.setattr(agents, "frage", vier_platzen)
|
||||
stand = ledger.lade(projekt)
|
||||
gemeldet = []
|
||||
await inhalt.code_im_feature(features.liste(projekt), [punkt()], stand,
|
||||
melde=lambda t, n=0: gemeldet.append(n))
|
||||
assert gemeldet == [1]
|
||||
assert stand["abdeckung"] == {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Vision
|
||||
|
||||
VISION = "<!-- titel -->\nVision\n<!-- art -->\nvision\n<!-- ziel -->\nSo soll es werden\n"
|
||||
|
||||
|
||||
def test_vision_ist_eine_eigene_kartenart(projekt):
|
||||
f = features.anlegen(projekt, VISION)
|
||||
assert f["sektionen"]["art"] == "vision"
|
||||
assert f["sektionen"]["ziel"] == "So soll es werden"
|
||||
|
||||
|
||||
def test_unbekannte_art_bleibt_kern(projekt):
|
||||
f = features.anlegen(projekt, "<!-- titel -->\nX\n<!-- art -->\nwichtig\n")
|
||||
assert f["sektionen"]["art"] == "kern"
|
||||
|
||||
|
||||
def test_keine_pruefung_fasst_die_vision_an(projekt):
|
||||
"""Die Vision ist die Vorlage, nicht der Prüfling."""
|
||||
karte(projekt, "Eins", ("Etwas starten", "Knopf"))
|
||||
features.anlegen(projekt, VISION)
|
||||
assert len(features.liste(projekt)) == 2
|
||||
titel = [f["sektionen"]["titel"] for f in features.pruefbar(projekt)]
|
||||
assert titel == ["Eins"]
|
||||
Reference in New Issue
Block a user