"""Sanity tests for :mod:`tests.fixtures` — the structured CannedQueue builder for ``MockLLMClient`` (T116). The builder is a thin shaping layer over JSON dicts; these tests pin the JSON shapes and the ``MockLLMClient`` round-trip so nothing silently regresses if a default field name or shape gets renamed. """ from __future__ import annotations import json import pytest from chat.llm.mock import MockLLMClient from tests.fixtures import CannedQueue def test_canned_queue_build_emits_expected_shapes(): """Each builder method emits the JSON shape its classifier consumer expects. The narrative slot is a bare string (stream). """ canned = ( CannedQueue() .parse_turn(segments=[{"kind": "dialogue", "text": "hello"}]) .detect_addressee(addressee_id="bot_a", reason="host") .narrative("Hi there.") .state_update() .state_update(affinity_delta=1, trust_delta=2) .detect_interjection(should_interject=False, reason="calm") .detect_event_transitions( [{"event_id": "evt_1", "new_status": "active", "reason": "they arrived"}] ) .detect_scene_close(should_close=False, reason="no signal") .summarize_scene_pov(summary="BotA noticed the day winding down.") .detect_threads( [ { "action": "open", "title": "Maya's job hunt", "summary": "Maya is looking for a new job", "existing_thread_id": None, } ] ) .build() ) # All slots are strings (the MockLLMClient pops strings). assert all(isinstance(slot, str) for slot in canned) assert len(canned) == 10 # Slot 0: parse_turn — defaults intent="narrative". parse = json.loads(canned[0]) assert parse["segments"] == [{"kind": "dialogue", "text": "hello"}] assert parse["intent"] == "narrative" assert parse["landing_state_hint"] == "" # Slot 1: detect_addressee. addr = json.loads(canned[1]) assert addr["addressee_id"] == "bot_a" assert addr["confidence"] == "medium" assert addr["reason"] == "host" # Slot 2: narrative — bare string, NOT JSON. assert canned[2] == "Hi there." with pytest.raises(json.JSONDecodeError): json.loads(canned[2]) # Slot 3: state_update with all defaults — zero deltas, no facts. su0 = json.loads(canned[3]) assert su0 == {"affinity_delta": 0, "trust_delta": 0, "knowledge_facts": []} # Slot 4: state_update with custom deltas. su1 = json.loads(canned[4]) assert su1["affinity_delta"] == 1 assert su1["trust_delta"] == 2 assert su1["knowledge_facts"] == [] # Slot 5: detect_interjection. interj = json.loads(canned[5]) assert interj == {"should_interject": False, "reason": "calm"} # Slot 6: detect_event_transitions. transitions = json.loads(canned[6]) assert transitions["transitions"][0]["event_id"] == "evt_1" assert transitions["transitions"][0]["new_status"] == "active" # Slot 7: detect_scene_close. close = json.loads(canned[7]) assert close == {"should_close": False, "reason": "no signal"} # Slot 8: summarize_scene_pov. pov = json.loads(canned[8]) assert pov["summary"] == "BotA noticed the day winding down." assert pov["knowledge_facts"] == [] assert pov["relationship_summary"] == "" # Slot 9: detect_threads. threads = json.loads(canned[9]) assert threads["candidates"][0]["action"] == "open" assert threads["candidates"][0]["title"] == "Maya's job hunt" @pytest.mark.asyncio async def test_canned_queue_round_trips_through_mock_llm_client(): """Building a queue and feeding it to ``MockLLMClient`` produces the same items back via ``generate`` (in order). This is the contract every migrated test relies on. """ canned = ( CannedQueue() .parse_turn(segments=[{"kind": "dialogue", "text": "hi"}]) .narrative("Hello back.") .state_update() .build() ) mock = MockLLMClient(canned=canned) # generate() pops from the front. parse_str = await mock.generate([], model="x") assert json.loads(parse_str)["segments"] == [ {"kind": "dialogue", "text": "hi"} ] # The narrative slot is a raw string — generate returns it as-is. narr_str = await mock.generate([], model="x") assert narr_str == "Hello back." # The state_update slot has zero-delta defaults. su_str = await mock.generate([], model="x") assert json.loads(su_str) == { "affinity_delta": 0, "trust_delta": 0, "knowledge_facts": [], } # Queue fully drained. with pytest.raises(IndexError): await mock.generate([], model="x")