update
This commit is contained in:
174
backend/tests/test_events.py
Normal file
174
backend/tests/test_events.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""Event-Tracking (events-Tabelle) + Agenten-Labels."""
|
||||
|
||||
import asyncio
|
||||
|
||||
import agents
|
||||
from pipeline import GenContext
|
||||
|
||||
TOPIC = "t"
|
||||
|
||||
|
||||
async def _events(db, kind=None):
|
||||
conn = await db.get_db()
|
||||
q = "SELECT topic, kind, key, label, status, dur_ms, wait_ms FROM events WHERE topic = ?"
|
||||
args = [TOPIC]
|
||||
if kind:
|
||||
q += " AND kind = ?"
|
||||
args.append(kind)
|
||||
cur = await conn.execute(q, args)
|
||||
return [dict(zip(("topic", "kind", "key", "label", "status", "dur_ms", "wait_ms"), r))
|
||||
for r in await cur.fetchall()]
|
||||
|
||||
|
||||
async def test_advance_many_writes_stage_events(testdb):
|
||||
db = testdb
|
||||
await db.kanban_upsert_card(TOPIC, "inventory", "a", "block", "s1")
|
||||
await db.kanban_upsert_card(TOPIC, "inventory", "b", "block", "s1")
|
||||
await db.kanban_advance_many(TOPIC, "inventory", [("a", "s2"), ("b", "s2")])
|
||||
evs = await _events(db, "stage")
|
||||
assert {(e["key"], e["status"]) for e in evs} == {("inventory:a", "s2"), ("inventory:b", "s2")}
|
||||
|
||||
|
||||
async def test_fail_card_events_retry_then_dead(testdb):
|
||||
db = testdb
|
||||
await db.kanban_upsert_card(TOPIC, "inventory", "a", "block", "s1")
|
||||
assert await db.kanban_fail_card(TOPIC, "inventory", "a", "boom", max_retries=2) is False
|
||||
assert await db.kanban_fail_card(TOPIC, "inventory", "a", "boom", max_retries=2) is True
|
||||
evs = await _events(db, "fail")
|
||||
assert [e["status"] for e in evs] == ["retry1", "dead"]
|
||||
|
||||
|
||||
async def test_guide_stage_event(testdb):
|
||||
db = testdb
|
||||
await db.upsert_guide_card(TOPIC, "Guide", "alpha", "Alpha")
|
||||
await db.set_guide_card(TOPIC, "Guide", "alpha", stage="writer")
|
||||
evs = await _events(db, "stage")
|
||||
assert evs and evs[-1]["key"] == "guide:Guide:alpha" and evs[-1]["status"] == "writer"
|
||||
|
||||
|
||||
async def test_run_agent_emits_event_and_survives_broken_sink(testdb, monkeypatch):
|
||||
recorded = []
|
||||
|
||||
async def sink(**kw):
|
||||
recorded.append(kw)
|
||||
|
||||
async def fake_cli(agent_key, prompt, timeout, model, capabilities, label=""):
|
||||
return 0, "out", ""
|
||||
|
||||
monkeypatch.setattr(agents, "on_event", sink)
|
||||
monkeypatch.setattr(agents, "_run_claude_cli", fake_cli)
|
||||
monkeypatch.setattr(agents.shutil, "which", lambda c: "/bin/true")
|
||||
monkeypatch.setattr(agents, "resolve_role", lambda p, r: ("claude", "test-model"))
|
||||
rc, out, err = await agents.run_agent("blocks-t-x", "p", 5, provider="claude",
|
||||
role="judge", scope=TOPIC, label="Alpha · Judge")
|
||||
assert rc == 0
|
||||
assert recorded and recorded[0]["kind"] == "agent"
|
||||
assert recorded[0]["label"] == "Alpha · Judge" and recorded[0]["status"] == "ok"
|
||||
assert isinstance(recorded[0]["wait_ms"], int) and isinstance(recorded[0]["dur_ms"], int)
|
||||
|
||||
# broken sink never breaks the call; interactive/scope-less calls don't log
|
||||
async def broken(**kw):
|
||||
raise RuntimeError("sink down")
|
||||
monkeypatch.setattr(agents, "on_event", broken)
|
||||
rc, _, _ = await agents.run_agent("blocks-t-y", "p", 5, provider="claude", scope=TOPIC)
|
||||
assert rc == 0
|
||||
monkeypatch.setattr(agents, "on_event", sink)
|
||||
recorded.clear()
|
||||
await agents.run_agent("chat-1", "p", 5, provider="claude", lane="interactive")
|
||||
assert recorded == []
|
||||
|
||||
|
||||
async def test_active_agents_carry_labels():
|
||||
async def run(key, label):
|
||||
return await agents._communicate(key, ["sleep", "0.4"], None, 5, label=label)
|
||||
|
||||
t1 = asyncio.create_task(run("blocks-t-x", "Alpha · Facts 1"))
|
||||
t2 = asyncio.create_task(run("blocks-t-x", "Alpha · Facts 2")) # key collision → ~2
|
||||
await asyncio.sleep(0.15)
|
||||
agents_now = agents.active_agents("blocks-t-")
|
||||
assert sorted(a["label"] for a in agents_now) == ["Alpha · Facts 1", "Alpha · Facts 2"]
|
||||
assert {a["key"] for a in agents_now} == {"blocks-t-x", "blocks-t-x~2"}
|
||||
await asyncio.gather(t1, t2)
|
||||
assert agents.active_agents("blocks-t-") == []
|
||||
|
||||
async def test_pull_prefers_bigger_blocks(testdb):
|
||||
"""LPT: Karten mit größerem subs_n werden zuerst gezogen; ohne Feld bleibt FIFO."""
|
||||
db = testdb
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "klein", "ablock", "facts", {"subs_n": 5})
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "gross", "ablock", "facts", {"subs_n": 40})
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", "mittel", "ablock", "facts", {"subs_n": 15})
|
||||
pulled = await db.kanban_pull(TOPIC, "artefacts", "facts", 10)
|
||||
assert [c["card_id"] for c in pulled] == ["gross", "mittel", "klein"]
|
||||
# ohne subs_n: FIFO nach updated_at
|
||||
await db.kanban_upsert_card(TOPIC, "inventory", "a", "block", "s1")
|
||||
await db.kanban_upsert_card(TOPIC, "inventory", "b", "block", "s1")
|
||||
pulled = await db.kanban_pull(TOPIC, "inventory", "s1", 10)
|
||||
assert [c["card_id"] for c in pulled] == ["a", "b"]
|
||||
|
||||
|
||||
async def test_learnstate_smoke(testdb):
|
||||
"""Regression: P5-Ausbau hatte die _LEVEL_CASE-Konstante mitgerissen —
|
||||
load_learnstate (Guide-Start-Pfad) muss ohne NameError laufen."""
|
||||
from rules import load_learnstate
|
||||
guides, progress, levels = await load_learnstate()
|
||||
assert isinstance(levels, dict)
|
||||
|
||||
|
||||
async def test_guide_error_event(testdb):
|
||||
db = testdb
|
||||
await db.upsert_guide_card(TOPIC, "Guide", "alpha", "Alpha")
|
||||
await db.set_guide_card(TOPIC, "Guide", "alpha", status="error", gate_info="Writer ohne Ergebnis")
|
||||
evs = await _events(db, "fail")
|
||||
assert evs and evs[-1]["key"] == "guide:Guide:alpha" and "Writer" in evs[-1]["status"]
|
||||
|
||||
|
||||
def test_timeout_calibration_smoke():
|
||||
from pipeline import _timeout
|
||||
assert _timeout("subblock", 10) == 400 + 150
|
||||
assert _timeout("content", 10) == 450 + 300
|
||||
|
||||
|
||||
def test_env_file_wins(tmp_path, monkeypatch):
|
||||
"""Regression: geerbte (veraltete) Env-Werte dürfen die .env nicht mehr überstimmen."""
|
||||
import config
|
||||
monkeypatch.setenv("X_CREATOR_TESTKEY", "alt")
|
||||
p = tmp_path / ".env"
|
||||
p.write_text("X_CREATOR_TESTKEY=neu\n", encoding="utf-8")
|
||||
config._load_env(p)
|
||||
import os
|
||||
assert os.environ["X_CREATOR_TESTKEY"] == "neu"
|
||||
|
||||
|
||||
async def test_restart_artefact_card_wipes_only_that_block(testdb):
|
||||
import board_inventory as bi
|
||||
db = testdb
|
||||
for norm in ("alpha", "beta"):
|
||||
await db.kanban_upsert_card(TOPIC, "artefacts", norm, "ablock", "done_artefact",
|
||||
{"title": norm.title(), "raw": {norm: ["S"]}, "facts": {}})
|
||||
await db.upsert_subblock(TOPIC, norm, "s1", norm.title(), "Sub Eins")
|
||||
await db.upsert_question_pattern(TOPIC, norm, "s1", norm.title(), "Sub Eins", "Frage?")
|
||||
await db.put_sub_artifact(TOPIC, norm, "s1", "flashcard", norm.title(), "Sub Eins", "{}")
|
||||
assert await bi.restart_artefact_card(TOPIC, "alpha") is True
|
||||
assert (await db.kanban_get_card(TOPIC, "artefacts", "alpha"))["stage"] == "subblocks"
|
||||
assert await db.list_subblocks(TOPIC, "alpha") == []
|
||||
assert len(await db.list_subblocks(TOPIC, "beta")) == 1 # untouched
|
||||
assert await bi.restart_artefact_card(TOPIC, "gibtsnicht") is False
|
||||
|
||||
|
||||
async def test_guide_reset_card_single(testdb):
|
||||
import guide_board as gb
|
||||
db = testdb
|
||||
for n in ("alpha", "beta"):
|
||||
await db.upsert_guide_card(TOPIC, "Guide", n, n.title())
|
||||
await db.set_guide_card(TOPIC, "Guide", n, stage="done", status="ok",
|
||||
writer_rounds=2, md="# SECTION Text", gate_info="x")
|
||||
await db.put_lernziel(TOPIC, n, "z1", "Ziel eins")
|
||||
assert await gb.reset_card(TOPIC, "Guide", "alpha", 0) is True
|
||||
cards = {c["block_norm"]: c for c in await db.list_guide_cards(TOPIC, "Guide")}
|
||||
assert cards["alpha"]["stage"] == "lernziele" and cards["alpha"]["md"] == "" and cards["alpha"]["writer_rounds"] == 0
|
||||
assert cards["beta"]["stage"] == "done" and cards["beta"]["md"] # untouched
|
||||
assert await db.list_lernziele(TOPIC) and all(z["block_norm"] != "alpha" for z in await db.list_lernziele(TOPIC))
|
||||
# ab_stage 3 (fakten_gate) behält md
|
||||
assert await gb.reset_card(TOPIC, "Guide", "beta", 3) is True
|
||||
cards = {c["block_norm"]: c for c in await db.list_guide_cards(TOPIC, "Guide")}
|
||||
assert cards["beta"]["stage"] == "fakten_gate" and cards["beta"]["md"]
|
||||
Reference in New Issue
Block a user