shinkan-jinkendo/backend/tests/test_planning_catalog_prompt_snippets.py
Lars 53f2b027cc
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m20s
Enhance Planning Catalog Context and Prompt Slot Management
- Introduced new catalog context handling in planning prompt functions, allowing for improved integration of planning variables.
- Added optional catalog context parameters in various functions to streamline the merging of planning prompt variables.
- Updated frontend components to include CatalogPromptSlotsEditor for managing prompt slots across different catalog types.
- Enhanced API utilities to support fetching and updating catalog prompt slots, improving backend functionality for catalog management.
- Incremented version numbers and updated changelog to reflect the new features and improvements.
2026-06-15 12:13:15 +02:00

167 lines
6.0 KiB
Python

"""Tests Katalog-Prompt-Slots (H2)."""
from unittest.mock import MagicMock
from catalog_prompt_slots import (
build_catalog_guidance_for_prompt,
pick_active_catalog_item,
placeholder_key,
resolve_catalog_prompt_variables,
)
from planning_catalog_context import PlanningCatalogContextItem, ProgressionPlanningCatalogContext
from planning_prompt_variables import merge_planning_prompt_variables
def _mock_cur(
rows_by_table=None,
slots_by_kind_id=None,
slot_types_ready=True,
):
rows_by_table = rows_by_table or {}
slots_by_kind_id = slots_by_kind_id or {}
cur = MagicMock()
def execute(sql, params=None):
sql_l = (sql or "").lower()
if "to_regclass" in sql_l:
cur.fetchone.return_value = {"t": "catalog_prompt_slot_types" if slot_types_ready else None}
return
if "from catalog_prompt_slot_types" in sql_l:
cur.fetchall.return_value = []
return
if "from catalog_prompt_slots" in sql_l:
kind, cid = params[0], int(params[1])
slot_map = slots_by_kind_id.get((kind, cid), {})
cur.fetchall.return_value = [
{"slot_key": k, "content": v} for k, v in slot_map.items()
]
return
for table, rows in rows_by_table.items():
if f"from {table}" in sql_l:
item_id = int(params[0])
raw = rows.get(item_id)
if raw is None:
cur.fetchone.return_value = None
elif isinstance(raw, dict):
cur.fetchone.return_value = {
"id": item_id,
"name": raw.get("name", ""),
"description": raw.get("description", ""),
}
else:
cur.fetchone.return_value = {
"id": item_id,
"name": str(raw),
"description": "",
}
return
cur.fetchone.return_value = None
cur.fetchall.return_value = []
cur.execute.side_effect = execute
return cur
def test_pick_active_catalog_item_primary_wins():
items = [
PlanningCatalogContextItem(id=1, is_primary=False, weight=0.9),
PlanningCatalogContextItem(id=2, is_primary=True, weight=0.5),
]
assert pick_active_catalog_item(items).id == 2
def test_granular_placeholder_focus_area_hints_on_path_qa():
cur = _mock_cur(
rows_by_table={"focus_areas": {4: {"name": "Gewaltschutz"}}},
slots_by_kind_id={
("focus_area", 4): {
"description": "Planung zielt auf Prävention und Deeskalation.",
"hints_on_path_qa": "Lücken sind fehlende Deeskalations-Stufen.",
"anti_patterns": "Nicht nach Kumite-Tiefe bewerten.",
}
},
)
catalog = ProgressionPlanningCatalogContext(
focus_areas=[PlanningCatalogContextItem(id=4, is_primary=True)],
)
resolved = resolve_catalog_prompt_variables(cur, catalog, slug="planning_exercise_path_qa")
assert "Deeskalation" in resolved[placeholder_key("focus_area", "hints_on_path_qa")]
assert "Deeskalation" in resolved["catalog_guidance_block"]
assert resolved["has_catalog_guidance"] == "true"
def test_description_fallback_from_stammdaten():
cur = _mock_cur(
rows_by_table={
"focus_areas": {
4: {
"name": "Gewaltschutz",
"description": "Gewaltprävention und Deeskalation",
}
}
},
slots_by_kind_id={("focus_area", 4): {}},
)
catalog = ProgressionPlanningCatalogContext(
focus_areas=[PlanningCatalogContextItem(id=4, is_primary=True)],
)
resolved = resolve_catalog_prompt_variables(cur, catalog)
assert resolved[placeholder_key("focus_area", "description")] == "Gewaltprävention und Deeskalation"
def test_empty_without_catalog():
cur = MagicMock()
out = build_catalog_guidance_for_prompt(cur, None)
assert out["has_catalog_guidance"] is False
assert out["catalog_guidance_block"] == ""
def test_unknown_entry_no_guidance_block():
cur = _mock_cur(rows_by_table={"focus_areas": {99: {"name": "Unbekannter Fokus XYZ"}}})
catalog = ProgressionPlanningCatalogContext(
focus_areas=[PlanningCatalogContextItem(id=99, is_primary=True)],
)
out = build_catalog_guidance_for_prompt(cur, catalog)
assert out["has_catalog_guidance"] is False
assert out["catalog_guidance_block"] == ""
assert "Unbekannter Fokus XYZ" in out["catalog_context_json"]
def test_merge_planning_prompt_variables_granular_keys():
cur = _mock_cur(
rows_by_table={"focus_areas": {4: {"name": "Gewaltschutz"}}},
slots_by_kind_id={
("focus_area", 4): {"hints_on_path_qa": "Deeskalation und Grenzen."}
},
)
catalog = ProgressionPlanningCatalogContext(
focus_areas=[PlanningCatalogContextItem(id=4, is_primary=True)],
)
merged = merge_planning_prompt_variables(
cur,
{"goal_query": "Deeskalation Kinder"},
catalog=catalog,
slug="planning_exercise_path_qa",
)
assert merged[placeholder_key("focus_area", "hints_on_path_qa")].startswith("Deeskalation")
assert merged["has_catalog_guidance"] == "true"
def test_priority_order_in_guidance_block():
cur = _mock_cur(
rows_by_table={
"focus_areas": {1: {"name": "Gewaltschutz"}},
"training_types": {2: {"name": "Breitensport"}},
},
slots_by_kind_id={
("focus_area", 1): {"description": "Fokus-Text"},
("training_type", 2): {"description": "Stil-Text"},
},
)
catalog = ProgressionPlanningCatalogContext(
focus_areas=[PlanningCatalogContextItem(id=1, is_primary=True)],
training_types=[PlanningCatalogContextItem(id=2, is_primary=True)],
)
block = build_catalog_guidance_for_prompt(cur, catalog)["catalog_guidance_block"]
assert block.index("Primärfokus") < block.index("Trainingsstil")