from __future__ import annotations from pathlib import Path import pytest from fastapi.testclient import TestClient from chat.app import app from chat.db.connection import open_db from chat.eventlog.log import append_event from chat.eventlog.projector import project @pytest.fixture def client(tmp_path, monkeypatch): cfg = tmp_path / "config.toml" cfg.write_text('featherless_api_key = "test"\n') monkeypatch.setenv("CHAT_CONFIG_PATH", str(cfg)) db = tmp_path / "test.db" monkeypatch.setenv("CHAT_DB_PATH", str(db)) with TestClient(app) as c: # Disable background worker (we won't drive turns). if hasattr(app.state, "background_worker"): app.state.background_worker.enabled = False yield c def _seed(db: Path) -> None: with open_db(db) as conn: append_event( conn, kind="bot_authored", payload={ "id": "bot_a", "name": "BotA", "persona": "...", "voice_samples": [], "traits": ["shy"], "backstory": "", "initial_relationship_to_you": "", "kickoff_prose": "", }, ) append_event( conn, kind="you_authored", payload={ "name": "Me", "pronouns": "they/them", "persona": "engineer", }, ) append_event( conn, kind="chat_created", payload={ "id": "chat_bot_a", "host_bot_id": "bot_a", "initial_time": "2026-04-26T20:00:00+00:00", "narrative_anchor": "Day 1", "weather": "", }, ) # Activity for both you and host bot. append_event( conn, kind="activity_change", payload={ "entity_id": "you", "posture": "sitting", "action": {"verb": "thinking"}, "attention": "the screen", }, ) append_event( conn, kind="activity_change", payload={ "entity_id": "bot_a", "posture": "standing", "action": {"verb": "looking out the window"}, }, ) # Edge host -> you. append_event( conn, kind="edge_update", payload={ "source_id": "bot_a", "target_id": "you", "chat_id": "chat_bot_a", "affinity_delta": 5, "trust_delta": 2, "knowledge_facts": ["Me likes coffee"], }, ) # A regular memory. append_event( conn, kind="memory_written", payload={ "owner_id": "bot_a", "chat_id": "chat_bot_a", "pov_summary": "Talked about her sister", "witness_you": 1, "witness_host": 1, "witness_guest": 0, "significance": 2, }, ) # A pinned memory with significance 3 (★★). append_event( conn, kind="memory_written", payload={ "owner_id": "bot_a", "chat_id": "chat_bot_a", "pov_summary": "First kiss", "witness_you": 1, "witness_host": 1, "witness_guest": 0, "significance": 3, "pinned": 1, "auto_pinned": 1, }, ) project(conn) def test_drawer_404_when_chat_missing(client): response = client.get("/chats/no_such/drawer") assert response.status_code == 404 def test_drawer_renders_scene_and_activity(client, tmp_path): _seed(tmp_path / "test.db") response = client.get("/chats/chat_bot_a/drawer") assert response.status_code == 200 body = response.text # Scene/time anchor. assert "2026-04-26" in body # Activity verbs from both entities. assert "thinking" in body assert "looking out the window" in body # Activity attention. assert "the screen" in body def test_drawer_renders_edges(client, tmp_path): _seed(tmp_path / "test.db") response = client.get("/chats/chat_bot_a/drawer") assert response.status_code == 200 body = response.text assert "BotA" in body assert "you" in body # Default affinity 50 + delta 5 = 55. assert "55" in body # Knowledge fact appears. assert "Me likes coffee" in body def test_drawer_renders_memories_with_significance_markers(client, tmp_path): _seed(tmp_path / "test.db") response = client.get("/chats/chat_bot_a/drawer") assert response.status_code == 200 body = response.text assert "Talked about her sister" in body assert "First kiss" in body # Pinned counter shows 1 / 8 (or 1/8). assert "1 / 8" in body or "1/8" in body # Significance star marker for the pinned, score-3 memory. assert "★" in body def test_drawer_handles_no_state_gracefully(client, tmp_path): db = tmp_path / "test.db" with open_db(db) as conn: # Just enough state for the chat to exist. append_event( conn, kind="bot_authored", payload={ "id": "bot_a", "name": "BotA", "persona": "...", "voice_samples": [], "traits": [], "backstory": "", "initial_relationship_to_you": "", "kickoff_prose": "", }, ) append_event( conn, kind="chat_created", payload={ "id": "chat_bot_a", "host_bot_id": "bot_a", "initial_time": "2026-04-26T20:00:00+00:00", "narrative_anchor": "Day 1", "weather": "", }, ) project(conn) response = client.get("/chats/chat_bot_a/drawer") assert response.status_code == 200 body = response.text # Drawer renders gracefully with empty placeholders. assert "No active container" in body or "Container:" not in body assert "No edges yet" in body or "Edges" in body