Files
ai-podcast/tests/test_call_start_lineup_guard.py
T
lukeandClaude Opus 5 b85e1c6ea7 Fix caller lineup never loading and add Wellspring cult callers
The lineup fix and the new characters both land in main.py, so they share a
commit rather than being split artificially.

Lineup fix — populate_backgrounds() was reachable only through
POST /api/session/reset, so taking calls without hitting reset left
session.caller_backgrounds empty for a whole show. Every caller got a hollow
prompt, collapsed into generic relationship filler, and Silas lost his lore.
Now start_call populates on demand, get_caller_prompt raises
EmptyCallerBackgroundError instead of emitting an empty prompt, /api/callers
reports lineup readiness to a header badge, and cross-episode topic dedup
widened from 2 shows to 10.

Wellspring callers — Doyle as the anchor defector plus a six-member pool,
grouped by new frontmatter fields (group, group_lead, group_weight, register,
explicitness). A faction is capped at one caller per show, with a 15% roll for
two pairing the lead with one other member. group_weight lets an anchor carrying
an arc get slots faster than texture characters.

Also detects model refusals, which arrive as ordinary 200s and previously
reached air as broken-character text or dead silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 03:33:08 -05:00

73 lines
2.8 KiB
Python

"""Regression guard: a show once ran with session.caller_backgrounds empty because
populate_backgrounds() was reachable only via POST /api/session/reset. Every caller
got a hollow prompt and collapsed into generic relationship filler.
"""
import asyncio
import pytest
import backend.main as m
@pytest.fixture
def quiet_call(monkeypatch):
monkeypatch.setattr(m.audio_service, "stop_caller_audio", lambda *a, **k: None)
monkeypatch.setattr(m, "_maybe_generate_callback", lambda: None)
monkeypatch.setattr(m, "_enrich_background_async", lambda key: asyncio.sleep(0))
monkeypatch.setattr(m.session, "intern_monitoring", False)
m.session.current_caller_key = None
yield
# asyncio.run() closes the loop it creates and clears the thread's current
# loop. tests/test_caller_service.py still calls asyncio.get_event_loop(),
# which then raises — and this module sorts ahead of it. Leave a usable loop.
asyncio.set_event_loop(asyncio.new_event_loop())
def test_start_call_populates_empty_lineup(monkeypatch, quiet_call):
key = next(iter(m.CALLER_BASES))
m.session.caller_backgrounds = {}
called = []
async def fake_populate():
called.append(True)
m.session.caller_backgrounds = {
key: {"name": "Silas", "voice": "Sebastian", "identity": "commune founder",
"situation": "the convoy stopped", "specific_details": []}
}
monkeypatch.setattr(m.session, "populate_backgrounds", fake_populate)
asyncio.run(m.start_call(key))
assert called, "start_call must populate backgrounds when the lineup is empty"
assert m.session.caller_backgrounds, "lineup should be populated after the call starts"
def test_start_call_does_not_repopulate_existing_lineup(monkeypatch, quiet_call):
key = next(iter(m.CALLER_BASES))
m.session.caller_backgrounds = {
key: {"name": "Silas", "voice": "Sebastian", "identity": "commune founder",
"situation": "the convoy stopped", "specific_details": []}
}
called = []
async def fake_populate():
called.append(True)
monkeypatch.setattr(m.session, "populate_backgrounds", fake_populate)
asyncio.run(m.start_call(key))
assert not called, "an existing lineup must not be regenerated mid-show"
def test_prompt_from_populated_background_is_not_hollow():
prompt = m.get_caller_prompt({
"name": "Silas", "identity": "founder of The Wellspring",
"situation": "the convoy stopped on the shoulder of 118",
"reason_calling": "Priscilla will not come out of the trailer",
"secret_want": "to hear he is not the reason she broke",
"specific_details": ["livestock trailer", "nine years in"],
})
assert "Silas" in prompt
assert "Wellspring" in prompt
assert "You are . " not in prompt