init
This commit is contained in:
120
tests/test_e2e.py
Normal file
120
tests/test_e2e.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Fake-E2E: kompletter Graph in Sekunden. Prüft Kernziele am DB-Endzustand:
|
||||
Coverage 100 % (Lücke über Web-Nachrecherche geschlossen), Dublette gemerged,
|
||||
Dopplung entfernt, Marker global einmalig, Resume = 0 neue Events."""
|
||||
from pathlib import Path
|
||||
|
||||
from backend import db, fakes, montage
|
||||
from .conftest import lauf_bis_ende, topic_anlegen
|
||||
|
||||
|
||||
def _pruefe_endzustand(topic: str):
|
||||
t = db.one("SELECT * FROM topics WHERE name=?", topic)
|
||||
assert t["status"] == "fertig"
|
||||
# Coverage: jeder bestätigte Soll-Punkt hat ≥1 aktives Atom mit Baustein
|
||||
luecken = db.query(
|
||||
"SELECT s.id FROM soll s WHERE s.topic=? AND s.status='bestaetigt' AND "
|
||||
"s.freispruch='' AND NOT EXISTS (SELECT 1 FROM atome a WHERE "
|
||||
"a.soll_id=s.id AND a.status='aktiv' AND a.baustein_id IS NOT NULL)", topic)
|
||||
assert luecken == []
|
||||
assert db.one("SELECT COUNT(*) c FROM soll WHERE topic=? AND "
|
||||
"status='bestaetigt'", topic)["c"] == 3
|
||||
# Dublette gemerged: HTTP-Paar existiert nur einmal aktiv
|
||||
http = db.query("SELECT titel FROM atome WHERE topic=? AND status='aktiv' AND "
|
||||
"titel IN ('HTTP', 'Hypertext Transfer Protocol')", topic)
|
||||
assert len(http) == 1
|
||||
merged = db.one("SELECT id FROM atome WHERE topic=? AND merged_into IS NOT NULL",
|
||||
topic)
|
||||
assert merged is not None
|
||||
guide = Path(montage.guide_pfad(topic)).read_text()
|
||||
# Marker global genau einmal je Atom
|
||||
aktive = db.query("SELECT id FROM atome WHERE topic=? AND status='aktiv' AND "
|
||||
"baustein_id IS NOT NULL", topic)
|
||||
for a in aktive:
|
||||
assert guide.count(f"<!-- atom: {a['id']} -->") == 1
|
||||
# Dopplung raus, Rückbezug als Prosa — KEINE Links im Guide (linear lesbar)
|
||||
assert guide.count(fakes.DUP) <= 1
|
||||
assert "Wie bereits bei den Grundlagen beschrieben" in guide
|
||||
assert "](#" not in guide
|
||||
import re as _re
|
||||
assert not _re.search(r"^\*\*[^*\n]+\*\*\s+—", guide, _re.MULTILINE)
|
||||
# Verdichtung: Splitter-Atome („Vorteil …") sind zu EINEM fusioniert
|
||||
vorteile = db.query("SELECT id FROM atome WHERE topic=? AND status='aktiv' "
|
||||
"AND titel LIKE 'Vorteil%'", topic)
|
||||
assert len(vorteile) == 1
|
||||
starts = db.query("SELECT DISTINCT start FROM anker WHERE atom_id=?",
|
||||
vorteile[0]["id"])
|
||||
assert len(starts) == 2 # beide Belegstellen geerbt (Leser-Duplikate egal)
|
||||
# Kein Format-Leak im Guide
|
||||
assert "TITEL:" not in guide and "DEFINITION:" not in guide
|
||||
assert "## Fehlerbehandlung" in guide
|
||||
# Lücken-Atom kam über Web-Nachrecherche
|
||||
assert db.one("SELECT id FROM quellen WHERE topic=? AND zweck LIKE 'luecke:%' "
|
||||
"AND status='geladen'", topic) is not None
|
||||
|
||||
|
||||
async def test_kompletter_lauf():
|
||||
topic = topic_anlegen()
|
||||
run = await lauf_bis_ende(topic)
|
||||
assert run["status"] == "done", run["grund"]
|
||||
_pruefe_endzustand(topic)
|
||||
|
||||
|
||||
async def test_resume_keine_neuen_events():
|
||||
topic = topic_anlegen()
|
||||
run1 = await lauf_bis_ende(topic)
|
||||
assert run1["status"] == "done"
|
||||
vorher = db.one("SELECT COUNT(*) c FROM events")["c"]
|
||||
run2 = await lauf_bis_ende(topic)
|
||||
assert run2["status"] == "done"
|
||||
assert db.one("SELECT COUNT(*) c FROM events")["c"] == vorher
|
||||
|
||||
|
||||
async def test_pause_und_fortsetzen():
|
||||
import asyncio
|
||||
from backend import engine
|
||||
topic = topic_anlegen()
|
||||
run_id = engine.lauf_starten(topic)
|
||||
await asyncio.sleep(0.3)
|
||||
engine.lauf_pausieren(topic)
|
||||
await asyncio.wait_for(engine._laeufe[topic], 10)
|
||||
zwischen = db.one("SELECT * FROM runs WHERE id=?", run_id)
|
||||
if zwischen["status"] == "done":
|
||||
return # Lauf war schneller als die Pause — ok
|
||||
assert zwischen["status"] == "paused"
|
||||
run = await lauf_bis_ende(topic)
|
||||
assert run["status"] == "done"
|
||||
assert run["id"] == run_id # Resume nutzt denselben Run
|
||||
_pruefe_endzustand(topic)
|
||||
# keine Task-Duplikate
|
||||
doppel = db.query("SELECT topic, knoten, item, COUNT(*) c FROM tasks "
|
||||
"GROUP BY topic, knoten, item HAVING c > 1")
|
||||
assert doppel == []
|
||||
|
||||
|
||||
async def test_reset_und_neulauf():
|
||||
from backend import config, engine
|
||||
from backend.main import _pipeline_daten_loeschen
|
||||
topic = topic_anlegen()
|
||||
run1 = await lauf_bis_ende(topic)
|
||||
assert run1["status"] == "done"
|
||||
# Reset muss auch pausierte Läufe schließen (sonst Resume mit altem Budget)
|
||||
db.update("runs", "id=?", (run1["id"],), status="paused")
|
||||
_pipeline_daten_loeschen(topic)
|
||||
db.execute("UPDATE runs SET status='stopped' WHERE topic=? AND "
|
||||
"status IN ('running','paused','budget')", topic)
|
||||
db.update("topics", "name=?", (topic,), status="neu")
|
||||
assert db.one("SELECT COUNT(*) c FROM runs WHERE topic=? AND "
|
||||
"status='paused'", topic)["c"] == 0
|
||||
assert db.one("SELECT COUNT(*) c FROM tasks WHERE topic=?", topic)["c"] == 0
|
||||
assert db.one("SELECT COUNT(*) c FROM atome WHERE topic=?", topic)["c"] == 0
|
||||
assert not (config.KORPUS_DIR / topic).exists()
|
||||
run2 = await lauf_bis_ende(topic)
|
||||
assert run2["status"] == "done"
|
||||
_pruefe_endzustand(topic)
|
||||
|
||||
|
||||
async def test_events_haben_skill_hash():
|
||||
topic = topic_anlegen()
|
||||
await lauf_bis_ende(topic)
|
||||
ohne = db.query("SELECT id FROM events WHERE status='ok' AND skill_hash=''")
|
||||
assert ohne == []
|
||||
Reference in New Issue
Block a user