"""Finalize (board_artefacts._proc_finalize) und blocks-Helfer — Judges gefaked, gegen Test-DB. Die In-Block-Konsolidierung lebt seit dem Verschmelzungs-Umbau in block_calls._verify_block und wird in tests/test_block_calls.py getestet.""" import blocks import board_artefacts as ba from kanban import Flow from pipeline import GenContext TOPIC = "finalize" def _ctx(): return GenContext(topic=TOPIC, provider="test", is_cancelled=lambda: False) def test_luecken_schnitt_cap(): l1 = [f"Aspekt-{k} fehlt" for k in ("eins", "zwei", "drei", "vier", "fünf")] assert blocks._luecken_schnitt(l1, list(l1)) == l1[:3] # Cap 3 assert blocks._luecken_schnitt(["Inline-HTML"], ["Tabellen-Syntax"]) == [] def test_neg_set_lemmatisiert(): """kein/keine/keinen falten auf einen Stamm; nicht vs. ohne bleiben verschieden.""" a = blocks._neg_set("Fehlerverhalten (kein Syntaxfehler)") b = blocks._neg_set("Fehlerverhalten (keine Syntax-Fehlermeldung)") assert a == b == frozenset({"kein"}) assert blocks._neg_set("nicht expandiert") != blocks._neg_set("ohne Expansion") assert blocks._neg_set("niemals gerendert") == blocks._neg_set("nie gerendert") async def test_finalize_purges_stale_rows(testdb, tmp_path): """Re-Run-Waisen: Finalize löscht Alt-Fragen/-Artefakte des Blocks vor dem Upsert.""" db = testdb await db.upsert_question_pattern(TOPIC, "alpha", "alt-sub", "Alpha", "Alt", "Alte Frage?") await db.put_sub_artifact(TOPIC, "alpha", "alt-sub", "flashcard", "{}", "Alpha", "Alt") await db.kanban_upsert_card(TOPIC, "artefacts", "alpha", "ablock", "finalize", {}) files = {k: tmp_path / f"{k}.json" for k in ("sub_roh", "facts", "sidecar", "question_pattern", "artefakte")} card = {"card_id": "alpha", "payload": { "title": "Alpha", "raw": {"Alpha": ["Neu"]}, "facts": {}, "sidecar": {"Alpha": [{"title": "Neu", "level": "beginner"}]}, "pattern": {"Alpha": [{"subblock": "Neu", "question": "F?"}]}, "artefacts": {"flashcard": [{"block": "Alpha", "subblock": "Neu", "front": "F", "back": "B"}]}}} flow = Flow(TOPIC, work_dir=tmp_path) await ba._proc_finalize(_ctx(), flow, files, [card]) assert {r["sub_norm"] for r in await db.list_question_pattern(TOPIC)} == {"neu"} assert {(r["sub_norm"], r["type"]) for r in await db.get_sub_artefakte(TOPIC)} == {("neu", "flashcard")} async def test_finalize_defaultet_level_nachzuegler(testdb, tmp_path): """Finalize klassifiziert level-/relevance-lose consensus-Rows (Default advanced/relevant).""" db = testdb await db.put_subblock(TOPIC, "alpha", "nachzuegler", "Alpha", "Nachzügler", status="consensus") await db.kanban_upsert_card(TOPIC, "artefacts", "alpha", "ablock", "finalize", {}) files = {k: tmp_path / f"{k}.json" for k in ("sub_roh", "facts", "sidecar", "question_pattern", "artefakte")} card = {"card_id": "alpha", "payload": {"title": "Alpha", "raw": {}, "facts": {}, "sidecar": {}, "pattern": {}, "artefacts": {}}} await ba._proc_finalize(_ctx(), Flow(TOPIC, work_dir=tmp_path), files, [card]) row = next(r for r in await db.list_subblocks(TOPIC, "alpha")) assert row["level"] == "advanced" and row["relevance"] == "relevant" async def test_finalize_loescht_stale_consensus(testdb, tmp_path): """Alt-consensus-Rows, die der Lauf-Sidecar nicht mehr trägt, fliegen raus — variant-Rows bleiben (QA liest die Status). Wurzel der 25 Board-2-losen Waisen.""" db = testdb await db.put_subblock(TOPIC, "alpha", "alt-rest", "Alpha", "Alt-Rest", status="consensus") await db.put_subblock(TOPIC, "alpha", "alte-variante", "Alpha", "Alte Variante", status="variant") await db.kanban_upsert_card(TOPIC, "artefacts", "alpha", "ablock", "finalize", {}) files = {k: tmp_path / f"{k}.json" for k in ("sub_roh", "facts", "sidecar", "question_pattern", "artefakte")} card = {"card_id": "alpha", "payload": { "title": "Alpha", "raw": {"Alpha": ["Neu"]}, "facts": {}, "sidecar": {"Alpha": [{"title": "Neu", "level": "beginner"}]}, "pattern": {}, "artefacts": {}}} await ba._proc_finalize(_ctx(), Flow(TOPIC, work_dir=tmp_path), files, [card]) rows = {r["sub_norm"]: r["status"] for r in await db.list_subblocks(TOPIC, "alpha")} assert rows == {"neu": "consensus", "alte-variante": "variant"}