Files
pv-agent/tests/test_audit.py
T

122 lines
4.1 KiB
Python

"""Tests für Index-Bootstrap sowie Interaktions- und Bewertungsprotokoll."""
from __future__ import annotations
import dataclasses
import pytest
from agent.audit import AuditStore
from agent.bootstrap import ensure_index, index_is_ready
from agent.config import Config
def interaction_payload() -> dict:
return {
"api_version": "v1",
"request_id": "test-request-1",
"status": "answered",
"question": "Was gilt?",
"answer": "Belegte Antwort [lb-min-01].",
"refused": False,
"verified": True,
"citations": ["lb-min-01"],
"sources": [{"id": "lb-min-01", "title": "Quelle"}],
"conflicts": [],
"planned_queries": [{"text": "Was gilt?"}],
"model": "test-model",
"latency_ms": 42,
"n_context": 1,
"regenerations": 0,
}
def test_audit_persists_interaction_and_rating(tmp_path):
cfg = Config(
audit_enabled=True,
audit_db_path=str(tmp_path / "audit.db"),
audit_retention_days=30,
)
store = AuditStore(cfg)
try:
store.record_interaction(interaction_payload())
store.record_rating("test-request-1", "down", "Quelle war nicht passend")
first_comment_id = store.record_comment(
"test-request-1", "Bitte mit einer anderen Quelle prüfen."
)
second_comment_id = store.record_comment(
"test-request-1", "Der Stand ist für mich besonders wichtig."
)
# Retry mit derselben Request-ID aktualisiert die Interaktion, ohne ihre
# bereits gespeicherten Bewertungen oder Kommentare zu löschen.
store.record_interaction(interaction_payload())
rows = store.recent()
finally:
store.close()
assert len(rows) == 1
assert rows[0]["question"] == "Was gilt?"
assert rows[0]["answer"].startswith("Belegte Antwort")
assert rows[0]["citations"] == ["lb-min-01"]
assert rows[0]["rating"] == "down"
assert rows[0]["feedback"] == "Quelle war nicht passend"
assert [item["comment_id"] for item in rows[0]["comments"]] == [
first_comment_id,
second_comment_id,
]
assert rows[0]["comments"][0]["comment"].startswith("Bitte mit")
def test_audit_can_omit_free_text_and_still_log_metadata(tmp_path, capsys):
cfg = Config(
audit_enabled=True,
audit_db_path=str(tmp_path / "audit.db"),
audit_log_content=False,
audit_stdout=True,
)
store = AuditStore(cfg)
try:
store.record_interaction(interaction_payload())
store.record_rating("test-request-1", "up", "soll nicht gespeichert werden")
store.record_comment("test-request-1", "auch dieser Kommentar ist privat")
row = store.recent()[0]
finally:
store.close()
output = capsys.readouterr().out
assert row["question"] is None
assert row["answer"] is None
assert row["feedback"] is None
assert row["comments"][0]["comment"] is None
assert "Was gilt?" not in output
assert "soll nicht gespeichert werden" not in output
assert "auch dieser Kommentar ist privat" not in output
assert '"event": "agent_interaction"' in output
assert '"event": "agent_rating"' in output
assert '"event": "agent_comment"' in output
def test_rating_requires_existing_interaction(tmp_path):
store = AuditStore(Config(audit_db_path=str(tmp_path / "audit.db")))
try:
with pytest.raises(KeyError):
store.record_rating("missing", "up", None)
with pytest.raises(KeyError):
store.record_comment("missing", "Kommentar")
finally:
store.close()
def test_bootstrap_recognizes_existing_mini_index(mini_index):
assert index_is_ready(mini_index) is True
dense_cfg = dataclasses.replace(mini_index, embed_off=False)
assert index_is_ready(dense_cfg) is False
def test_bootstrap_builds_missing_index(mini_cfg, tmp_path):
cfg = dataclasses.replace(mini_cfg, db_path=str(tmp_path / "new-index.db"))
assert index_is_ready(cfg) is False
result = ensure_index(cfg)
assert result["event"] == "index_bootstrap_completed"
assert index_is_ready(cfg) is True
assert ensure_index(cfg)["event"] == "index_ready"