"""Unit tests for agent slot providers.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from types import SimpleNamespace from steering.agent_slots.guardrail_review import resolve_guardrail_review_slots from steering.agent_slots.recurring_due import resolve_recurring_due_slots from steering.agent_slots.review_due import resolve_review_due_slots from steering.agent_slots.registry import compute_agent_slots from steering.kernel.models import HorizonMarker def _ctx(**kwargs): defaults = { "initiative_id": "init-1", "horizon": HorizonMarker(kind="none"), "operating_context": { "data_slices": ["reviews", "recurring", "roadmap"], "steering_elements": ["gate_fulfillment", "recurring_rhythm"], "agent_slot_config": {}, }, "_reviews": [], "_recurring_elements": [], "_actions": [], } defaults.update(kwargs) class FakeCtx: initiative_id = defaults["initiative_id"] horizon = defaults["horizon"] operating_context = defaults["operating_context"] @property def steering_elements(self): return frozenset(self.operating_context.get("steering_elements") or []) @property def data_slices(self): return list(self.operating_context.get("data_slices") or []) @property def agent_slot_config(self): return dict(self.operating_context.get("agent_slot_config") or {}) @property def reviews(self): return defaults["_reviews"] @property def recurring_elements(self): return defaults["_recurring_elements"] @property def actions(self): return defaults["_actions"] return FakeCtx() def test_review_due_slot_for_overdue_planned_review(): past = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat() ctx = _ctx( _reviews=[ { "id": "rev-1", "title": "Architektur-Review", "status": "planned", "due_at": past, } ] ) slots = resolve_review_due_slots(ctx) assert len(slots) == 1 assert slots[0]["slot_key"] == "review.due" assert slots[0]["scope_id"] == "rev-1" assert slots[0]["actor_role_hint"] == "reviewer" def test_recurring_due_slot(): past = (datetime.now(timezone.utc) - timedelta(hours=2)).isoformat() ctx = _ctx( _recurring_elements=[ { "id": "rec-1", "title": "Sprint-Retro", "status": "active", "next_due_at": past, } ] ) slots = resolve_recurring_due_slots(ctx) assert len(slots) == 1 assert slots[0]["slot_key"] == "recurring.due" def test_guardrail_slot_when_gate_has_planning_debt(): ctx = _ctx( horizon=HorizonMarker( kind="gate", gate_roadmap_item_id="gate-1", gate_title="G3 Architektur", ), _reviews=[], ) read_models = { "planning_debt": [{"roadmap_item_id": "gate-1", "title": "G3"}], } slots = resolve_guardrail_review_slots(ctx, read_models) assert len(slots) == 1 assert slots[0]["slot_key"] == "review.guardrail" assert slots[0]["guardrail_pack"] == "default_v0" def test_compute_agent_slots_respects_enabled_slots_filter(): past = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat() ctx = _ctx( operating_context={ "data_slices": ["reviews", "recurring", "roadmap"], "steering_elements": ["gate_fulfillment", "recurring_rhythm"], "agent_slot_config": {"enabled_slots": ["review.due"]}, }, _reviews=[ {"id": "rev-1", "title": "Review", "status": "planned", "due_at": past} ], _recurring_elements=[ { "id": "rec-1", "title": "Retro", "status": "active", "next_due_at": past, } ], ) slots = compute_agent_slots(ctx, read_models={}, proposals={}) assert len(slots) == 1 assert slots[0]["slot_key"] == "review.due"