from __future__ import annotations from chat.db.connection import open_db from chat.db.migrate import apply_migrations from chat.eventlog.log import append_and_apply, append_event from chat.eventlog.projector import project import chat.state.entities # registers handlers import chat.state.world # registers handlers import chat.state.threads # registers handlers from chat.state.threads import get_thread, list_open_threads def _bot_payload(bot_id: str, name: str) -> dict: return { "id": bot_id, "name": name, "persona": "thoughtful, observant", "voice_samples": [], "traits": [], "backstory": "", "initial_relationship_to_you": "coworker", "kickoff_prose": "", } def _chat_payload(chat_id: str = "chat_bot_a") -> dict: return { "id": chat_id, "host_bot_id": "bot_a", "guest_bot_id": "bot_b", "initial_time": "2026-04-26T20:00:00+00:00", "narrative_anchor": "Day 1 evening", "weather": "clear", } def test_thread_opened_creates_row(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: append_event(conn, kind="bot_authored", payload=_bot_payload("bot_a", "BotA")) append_event(conn, kind="chat_created", payload=_chat_payload()) append_event( conn, kind="thread_opened", payload={ "thread_id": "thr_abc", "chat_id": "chat_bot_a", "title": "Maya's job hunt", "summary": "Maya is looking for a new job", }, ) project(conn) t = get_thread(conn, "thr_abc") assert t is not None assert t["thread_id"] == "thr_abc" assert t["chat_id"] == "chat_bot_a" assert t["title"] == "Maya's job hunt" assert t["summary"] == "Maya is looking for a new job" assert t["status"] == "open" assert t["closed_at"] is None assert t["last_referenced_scene_id"] is None def test_thread_updated_changes_summary_and_last_referenced(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: append_event(conn, kind="bot_authored", payload=_bot_payload("bot_a", "BotA")) append_event(conn, kind="chat_created", payload=_chat_payload()) append_event( conn, kind="thread_opened", payload={ "thread_id": "thr_abc", "chat_id": "chat_bot_a", "title": "Maya's job hunt", "summary": "Maya is looking for a new job", }, ) append_event( conn, kind="thread_updated", payload={ "thread_id": "thr_abc", "summary": "Maya landed an interview at a startup", "last_referenced_scene_id": 42, }, ) project(conn) t = get_thread(conn, "thr_abc") assert t is not None assert t["summary"] == "Maya landed an interview at a startup" assert t["last_referenced_scene_id"] == 42 assert t["status"] == "open" def test_thread_closed_terminal(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: append_event(conn, kind="bot_authored", payload=_bot_payload("bot_a", "BotA")) append_event(conn, kind="chat_created", payload=_chat_payload()) append_event( conn, kind="thread_opened", payload={ "thread_id": "thr_abc", "chat_id": "chat_bot_a", "title": "Maya's job hunt", "summary": "Maya is looking for a new job", }, ) append_event( conn, kind="thread_closed", payload={ "thread_id": "thr_abc", "closed_at": "2026-04-26T21:00:00+00:00", }, ) project(conn) t = get_thread(conn, "thr_abc") assert t is not None assert t["status"] == "closed" assert t["closed_at"] == "2026-04-26T21:00:00+00:00" # Subsequent updates to a closed thread are no-ops. append_and_apply( conn, kind="thread_updated", payload={ "thread_id": "thr_abc", "summary": "should not be applied", }, ) t2 = get_thread(conn, "thr_abc") assert t2 is not None assert t2["summary"] == "Maya is looking for a new job" assert t2["status"] == "closed" def test_list_open_threads_filters_to_open_only(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: append_event(conn, kind="bot_authored", payload=_bot_payload("bot_a", "BotA")) append_event(conn, kind="chat_created", payload=_chat_payload()) for tid, title in [ ("thr_1", "Arc 1"), ("thr_2", "Arc 2"), ("thr_3", "Arc 3"), ]: append_event( conn, kind="thread_opened", payload={ "thread_id": tid, "chat_id": "chat_bot_a", "title": title, "summary": "", }, ) append_event( conn, kind="thread_closed", payload={ "thread_id": "thr_3", "closed_at": "2026-04-26T21:00:00+00:00", }, ) project(conn) open_threads = list_open_threads(conn, "chat_bot_a") assert len(open_threads) == 2 ids = {t["thread_id"] for t in open_threads} assert ids == {"thr_1", "thr_2"}