update
This commit is contained in:
193
backend/tests/test_recherche.py
Normal file
193
backend/tests/test_recherche.py
Normal file
@@ -0,0 +1,193 @@
|
||||
import pytest
|
||||
|
||||
import agents
|
||||
import projekte
|
||||
import recherche
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def projekt(datenordner):
|
||||
projekte.anlegen("P")
|
||||
return "P"
|
||||
|
||||
|
||||
def test_leeres_projekt_hat_keine_faeden(projekt):
|
||||
assert recherche.liste(projekt) == []
|
||||
|
||||
|
||||
def test_anlegen_gibt_faden_mit_id(projekt):
|
||||
faden = recherche.anlegen(projekt)
|
||||
assert faden["id"] and faden["verlauf"] == []
|
||||
assert [f["id"] for f in recherche.liste(projekt)] == [faden["id"]]
|
||||
|
||||
|
||||
def test_faeden_haben_eigene_ids(projekt):
|
||||
a, b = recherche.anlegen(projekt), recherche.anlegen(projekt)
|
||||
assert a["id"] != b["id"]
|
||||
assert len(recherche.liste(projekt)) == 2
|
||||
|
||||
|
||||
def test_loeschen_trifft_nur_den_einen(projekt):
|
||||
a, b = recherche.anlegen(projekt), recherche.anlegen(projekt)
|
||||
recherche.loeschen(projekt, a["id"])
|
||||
assert [f["id"] for f in recherche.liste(projekt)] == [b["id"]]
|
||||
|
||||
|
||||
def test_loeschen_ohne_faden_ist_404(projekt):
|
||||
with pytest.raises(FileNotFoundError):
|
||||
recherche.loeschen(projekt, "gibtsnicht")
|
||||
|
||||
|
||||
def test_faeden_liegen_im_projektstand_nicht_in_der_planung(projekt):
|
||||
"""Nachgeschlagenes überlebt einen Planungsabbruch — es gehört zum Projekt."""
|
||||
recherche.anlegen(projekt)
|
||||
(projekte.stand(projekt) / projekte.PLANUNG).mkdir()
|
||||
assert len(recherche.liste(projekt)) == 1
|
||||
assert (projekte.stand(projekt) / recherche.DATEI).is_file()
|
||||
|
||||
|
||||
def _antwort(satz="Laut Quelle X: ja."):
|
||||
"""Eine Modellantwort — Markdown, nicht JSON."""
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
return satz
|
||||
return antworte
|
||||
|
||||
|
||||
async def test_frage_haengt_beide_zeilen_an(projekt, monkeypatch):
|
||||
faden = recherche.anlegen(projekt)
|
||||
monkeypatch.setattr(agents, "frage_lokal", _antwort(" Laut Quelle X: ja. "))
|
||||
erg = await recherche.frage(projekt, faden["id"], "Wie geht das?")
|
||||
assert [(e["rolle"], e["text"]) for e in erg["verlauf"]] == [
|
||||
("person", "Wie geht das?"), ("ki", "Laut Quelle X: ja.")]
|
||||
|
||||
|
||||
async def test_erste_frage_benennt_den_faden(projekt, monkeypatch):
|
||||
faden = recherche.anlegen(projekt)
|
||||
monkeypatch.setattr(agents, "frage_lokal", _antwort("ok"))
|
||||
erg = await recherche.frage(projekt, faden["id"], "Wie lädt man\nzu S3 hoch?")
|
||||
assert erg["titel"] == "Wie lädt man zu S3 hoch?"
|
||||
# Die zweite Frage lässt den Namen stehen: der Reiter soll nicht wandern
|
||||
erg = await recherche.frage(projekt, faden["id"], "Und die Grenzen?")
|
||||
assert erg["titel"] == "Wie lädt man zu S3 hoch?"
|
||||
|
||||
|
||||
async def test_langer_titel_wird_gekuerzt(projekt, monkeypatch):
|
||||
faden = recherche.anlegen(projekt)
|
||||
monkeypatch.setattr(agents, "frage_lokal", _antwort("ok"))
|
||||
erg = await recherche.frage(projekt, faden["id"], "w" * 80)
|
||||
assert len(erg["titel"]) == recherche.TITELLAENGE + 1
|
||||
assert erg["titel"].endswith("…")
|
||||
|
||||
|
||||
async def test_recherche_bekommt_die_websuche(projekt, monkeypatch):
|
||||
"""Der Werkzeugsatz ist der ganze Unterschied zum Planungsgespräch."""
|
||||
faden = recherche.anlegen(projekt)
|
||||
gesehen = {}
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
gesehen.update(kommando=kommando, inhalt=inhalt)
|
||||
return "ok"
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
await recherche.frage(projekt, faden["id"], "Was gilt draußen?")
|
||||
assert gesehen["kommando"] == agents.RECHERCHE_KOMMANDO
|
||||
|
||||
|
||||
async def test_die_karten_liegen_im_prompt(projekt, monkeypatch):
|
||||
"""Ohne Bestand ist „was fehlt noch" nicht zu beantworten — dann rät das Modell."""
|
||||
(projekte.stand(projekt) / "gold.md").write_text(
|
||||
"<!-- titel -->\nGold\n<!-- art -->\nkern\n"
|
||||
"<!-- ziel -->\nSpieler verdient Gold pro Runde.\n"
|
||||
"<!-- funktion -->\nZinsen erhalten\n<!-- stand -->\noffen\n"
|
||||
"<!-- testen -->\n- Runde spielen\n")
|
||||
faden = recherche.anlegen(projekt)
|
||||
gesehen = {}
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
gesehen["system"] = system
|
||||
return "ok"
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
await recherche.frage(projekt, faden["id"], "Was fehlt noch?")
|
||||
assert "Zinsen erhalten" in gesehen["system"]
|
||||
# Der Stand ist der Punkt: er unterscheidet Gebautes von Bestelltem
|
||||
assert '"stand": "offen"' in gesehen["system"]
|
||||
# Testabläufe blähen den Prompt und tragen keine Recherche
|
||||
assert "Runde spielen" not in gesehen["system"]
|
||||
|
||||
|
||||
async def test_vision_kommt_mit(projekt, monkeypatch):
|
||||
(projekte.stand(projekt) / "vision.md").write_text(
|
||||
"<!-- titel -->\nVision\n<!-- art -->\nvision\n"
|
||||
"<!-- ziel -->\nTFT im Browser nachbauen.\n")
|
||||
faden = recherche.anlegen(projekt)
|
||||
gesehen = {}
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
gesehen["system"] = system
|
||||
return "ok"
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
await recherche.frage(projekt, faden["id"], "Und nun?")
|
||||
assert "TFT im Browser nachbauen." in gesehen["system"]
|
||||
|
||||
|
||||
async def test_markdown_bleibt_wie_es_kam(projekt, monkeypatch):
|
||||
"""Die Antwort wird als Markdown angezeigt — der Server fasst sie nicht an."""
|
||||
roh = "Kurz gesagt.\n\n## Festgelegt\n- **5 Gold** pro Runde\n\n## Quellen\n- [Wiki](http://x)"
|
||||
faden = recherche.anlegen(projekt)
|
||||
monkeypatch.setattr(agents, "frage_lokal", _antwort(f"\n{roh}\n"))
|
||||
erg = await recherche.frage(projekt, faden["id"], "Wie geht Gold?")
|
||||
assert erg["verlauf"][-1]["text"] == roh
|
||||
|
||||
|
||||
async def test_leere_frage_wird_abgelehnt(projekt):
|
||||
faden = recherche.anlegen(projekt)
|
||||
with pytest.raises(ValueError):
|
||||
await recherche.frage(projekt, faden["id"], " ")
|
||||
|
||||
|
||||
async def test_geschlossener_faden_verwirft_die_antwort(projekt, monkeypatch):
|
||||
"""Antworten dauern Minuten. Wer inzwischen schließt, will das Ergebnis nicht."""
|
||||
faden = recherche.anlegen(projekt)
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
recherche.loeschen(projekt, faden["id"])
|
||||
return "zu spät"
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
with pytest.raises(FileNotFoundError):
|
||||
await recherche.frage(projekt, faden["id"], "Was gilt?")
|
||||
assert recherche.liste(projekt) == []
|
||||
|
||||
|
||||
async def test_paralleler_faden_bleibt_erhalten(projekt, monkeypatch):
|
||||
"""Der zweite Faden entsteht, während der erste noch wartet."""
|
||||
a = recherche.anlegen(projekt)
|
||||
neu = {}
|
||||
|
||||
async def antworte(system, inhalt, kommando=None, ordner=None):
|
||||
neu["b"] = recherche.anlegen(projekt)
|
||||
return "fertig"
|
||||
|
||||
monkeypatch.setattr(agents, "frage_lokal", antworte)
|
||||
await recherche.frage(projekt, a["id"], "Frage")
|
||||
faeden = recherche.liste(projekt)
|
||||
assert [f["id"] for f in faeden] == [a["id"], neu["b"]["id"]]
|
||||
# Was der eine Faden erfährt, bleibt beim einen
|
||||
assert len(faeden[0]["verlauf"]) == 2 and faeden[1]["verlauf"] == []
|
||||
|
||||
|
||||
def test_kaputte_datei_gilt_als_leer(projekt):
|
||||
(projekte.stand(projekt) / recherche.DATEI).write_text("{kein json")
|
||||
assert recherche.liste(projekt) == []
|
||||
|
||||
|
||||
async def test_tiefe_haelt_den_faden_kurz(projekt, monkeypatch):
|
||||
faden = recherche.anlegen(projekt)
|
||||
monkeypatch.setattr(agents, "frage_lokal", _antwort("ok"))
|
||||
for i in range(recherche.TIEFE):
|
||||
await recherche.frage(projekt, faden["id"], f"Frage {i}")
|
||||
verlauf = recherche.liste(projekt)[0]["verlauf"]
|
||||
assert len(verlauf) == recherche.TIEFE
|
||||
assert verlauf[-2]["text"] == f"Frage {recherche.TIEFE - 1}"
|
||||
Reference in New Issue
Block a user