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.world # registers handlers from chat.state.world import get_activity, get_chat def _chat_payload(**overrides): payload = { "id": "chat_bot_a", "host_bot_id": "bot_a", "guest_bot_id": None, "initial_time": "2026-04-26T20:00:00+00:00", "narrative_anchor": "Day 1 evening", "weather": "clear", } payload.update(overrides) return payload def _container_payload(**overrides): payload = { "chat_id": "chat_bot_a", "name": "office", "type": "workplace", "properties": { "public": True, "moving": False, "audible_range": "normal", "slots": [], }, "parent_id": None, } payload.update(overrides) return payload def _activity_payload(**overrides): payload = { "entity_id": "bot_a", "container_id": 1, "slot": "desk_chair", "posture": "sitting", "action": {"verb": "writing email"}, "attention": "the screen", "holding": ["pen"], "status": {"hungry": False}, } payload.update(overrides) return payload def _seed_events(conn): """Append seed events but do NOT project — caller appends more then projects once.""" append_event(conn, kind="chat_created", payload=_chat_payload()) append_event(conn, kind="container_created", payload=_container_payload()) append_event(conn, kind="activity_change", payload=_activity_payload()) def test_elision_advances_chat_clock_only(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: _seed_events(conn) append_event(conn, kind="time_skip_elision", payload={ "chat_id": "chat_bot_a", "new_time": "2026-04-26T20:30:00+00:00", }) project(conn) chat = get_chat(conn, "chat_bot_a") assert chat["time"] == "2026-04-26T20:30:00+00:00" # Activity row preserved with the same fields it was seeded with. a = get_activity(conn, "bot_a") assert a is not None assert a["entity_id"] == "bot_a" assert a["container_id"] == 1 assert a["slot"] == "desk_chair" assert a["posture"] == "sitting" assert a["action"] == {"verb": "writing email"} assert a["attention"] == "the screen" assert a["holding"] == ["pen"] assert a["status"] == {"hungry": False} def test_jump_with_reset_clears_activity(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: _seed_events(conn) append_event(conn, kind="time_skip_jump", payload={ "chat_id": "chat_bot_a", "new_time": "2026-04-27T08:00:00+00:00", "reset_activity": True, }) project(conn) chat = get_chat(conn, "chat_bot_a") assert chat["time"] == "2026-04-27T08:00:00+00:00" count = conn.execute( "SELECT COUNT(*) FROM activity " "WHERE container_id IN (SELECT id FROM containers WHERE chat_id = ?)", ("chat_bot_a",), ).fetchone()[0] assert count == 0 assert get_activity(conn, "bot_a") is None def test_jump_without_reset_preserves_activity(tmp_path): db = tmp_path / "t.db" apply_migrations(db) with open_db(db) as conn: _seed_events(conn) append_event(conn, kind="time_skip_jump", payload={ "chat_id": "chat_bot_a", "new_time": "2026-04-27T08:00:00+00:00", "reset_activity": False, }) project(conn) chat = get_chat(conn, "chat_bot_a") assert chat["time"] == "2026-04-27T08:00:00+00:00" a = get_activity(conn, "bot_a") assert a is not None assert a["posture"] == "sitting" assert a["action"]["verb"] == "writing email"