194 lines
7.3 KiB
Python
194 lines
7.3 KiB
Python
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}"
|