diff --git a/chat/state/world.py b/chat/state/world.py index 5966924..8a9ba89 100644 --- a/chat/state/world.py +++ b/chat/state/world.py @@ -29,6 +29,24 @@ def _apply_chat_created(conn: Connection, e: Event) -> None: ) +@on("guest_added") +def _apply_guest_added(conn: Connection, e: Event) -> None: + p = e.payload + conn.execute( + "UPDATE chats SET guest_bot_id = ? WHERE id = ?", + (p["guest_bot_id"], p["chat_id"]), + ) + + +@on("guest_removed") +def _apply_guest_removed(conn: Connection, e: Event) -> None: + p = e.payload + conn.execute( + "UPDATE chats SET guest_bot_id = NULL WHERE id = ?", + (p["chat_id"],), + ) + + @on("container_created") def _apply_container_created(conn: Connection, e: Event) -> None: p = e.payload diff --git a/tests/test_guest_events.py b/tests/test_guest_events.py new file mode 100644 index 0000000..6d6c7e2 --- /dev/null +++ b/tests/test_guest_events.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from chat.db.connection import open_db +from chat.db.migrate import apply_migrations +from chat.eventlog.log import append_event +from chat.eventlog.projector import project +import chat.state.entities # registers bot_authored handler +import chat.state.world # registers chat_created / guest_added / guest_removed +from chat.state.world import get_chat + + +def _bot_payload(bot_id: str, name: str) -> dict: + return { + "id": bot_id, + "name": name, + "persona": "...", + "voice_samples": ["sample"], + "traits": ["shy"], + "backstory": "...", + "initial_relationship_to_you": "coworker", + "kickoff_prose": "you stay late", + } + + +def _chat_payload(**overrides) -> dict: + payload = { + "id": "chat_bot_a", + "host_bot_id": "bot_a", + "initial_time": "2026-04-26T20:00:00+00:00", + "narrative_anchor": "Day 1 evening", + "weather": "clear", + } + payload.update(overrides) + return payload + + +def test_guest_added_sets_guest_bot_id(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="bot_authored", payload=_bot_payload("bot_b", "BotB")) + append_event(conn, kind="chat_created", payload=_chat_payload()) + append_event(conn, kind="guest_added", payload={ + "chat_id": "chat_bot_a", + "guest_bot_id": "bot_b", + }) + project(conn) + + chat = get_chat(conn, "chat_bot_a") + assert chat is not None + assert chat["guest_bot_id"] == "bot_b" + + +def test_guest_removed_clears_guest_bot_id(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="bot_authored", payload=_bot_payload("bot_b", "BotB")) + append_event(conn, kind="chat_created", payload=_chat_payload()) + append_event(conn, kind="guest_added", payload={ + "chat_id": "chat_bot_a", + "guest_bot_id": "bot_b", + }) + append_event(conn, kind="guest_removed", payload={ + "chat_id": "chat_bot_a", + }) + project(conn) + + chat = get_chat(conn, "chat_bot_a") + assert chat is not None + assert chat["guest_bot_id"] is None + + +def test_guest_added_idempotent_overwrite(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="bot_authored", payload=_bot_payload("bot_b", "BotB")) + append_event(conn, kind="bot_authored", payload=_bot_payload("bot_c", "BotC")) + append_event(conn, kind="chat_created", payload=_chat_payload()) + append_event(conn, kind="guest_added", payload={ + "chat_id": "chat_bot_a", + "guest_bot_id": "bot_b", + }) + append_event(conn, kind="guest_added", payload={ + "chat_id": "chat_bot_a", + "guest_bot_id": "bot_c", + }) + project(conn) + + chat = get_chat(conn, "chat_bot_a") + assert chat is not None + assert chat["guest_bot_id"] == "bot_c"