from __future__ import annotations import json from types import SimpleNamespace import pytest from investibook.api import agent_sessions as sessions_api from investibook.db import queries as dbq from investibook.models.requests import ( AgentSessionCreateRequest, AgentTurnCreateRequest, DiagnosisInvocationPayload, ) @pytest.fixture def session_db(tmp_path, monkeypatch): monkeypatch.setattr(dbq, "DB_PATH", tmp_path / "agent-sessions.db") dbq._db_init() user = dbq._db_get_or_create_user("session-owner@example.com") now = dbq._utc_now() dbq._db_upsert_workspace( "ws-session", user["id"], "Session workspace", "", False, now, now, ) return user @pytest.mark.asyncio async def test_agent_session_is_stable_per_workspace(session_db): body = AgentSessionCreateRequest(workspace_id="ws-session") first = await sessions_api.create_agent_session(body, session_db) second = await sessions_api.create_agent_session(body, session_db) assert first["id"].startswith("ses_") assert second["id"] == first["id"] assert second["workspace_id"] == "ws-session" @pytest.mark.asyncio async def test_submit_turn_and_resume_transcript(session_db): session = await sessions_api.create_agent_session( AgentSessionCreateRequest(workspace_id="ws-session"), session_db, ) request = SimpleNamespace(base_url="https://investibook.example/") response = await sessions_api.create_agent_turn( request=request, session_id=session["id"], body=AgentTurnCreateRequest( input="diagnose this", client_turn_id="client-turn-1", diagnosis_invocation=DiagnosisInvocationPayload( diagnosis_mode="copilot", trigger_type="human_message", supports_followup=True, ), ), wait=False, authorization="Bearer test-token", idempotency_key=None, current_user=session_db, ) turn = json.loads(response.body) assert response.status_code == 202 assert turn["session_id"] == session["id"] assert turn["status"] == "queued" stored_turn = dbq._db_get_agent_run(turn["id"]) assert stored_turn is not None assert stored_turn["diagnosis_invocation"] == { "diagnosis_mode": "copilot", "trigger_type": "human_message", "supports_followup": True, "channel": "web", "preferred_route": "", "interaction_action": "start", "selected_routes": [], "confirmed_time_range": None, } with dbq._db_lock: conn = dbq._db_connect() table = conn.execute( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'agent_turns'" ).fetchone() legacy_table = conn.execute( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'agent_runs'" ).fetchone() conn.close() assert table is not None assert legacy_table is None session_view = await sessions_api.get_agent_session_view( session["id"], history_limit=50, current_user=session_db, ) assert session_view["active_turn"]["id"] == turn["id"] assert session_view["history"][0]["input"] == "diagnose this" transcript = await sessions_api.get_agent_transcript( session["id"], current_user=session_db, ) assert [record["data"]["type"] for record in transcript["records"]] == [ "turn_started", ] assert transcript["records"][0]["turn_id"] == turn["id"] unchanged = await sessions_api.get_agent_transcript( session["id"], resume_token=transcript["resume_token"], current_user=session_db, ) assert unchanged["records"] == [] assert unchanged["reset"] is False auth_token = dbq._db_create_user_session(session_db["id"]) stream = await sessions_api.stream_agent_transcript( session_id=session["id"], token=None, resume_token=None, authorization=f"Bearer {auth_token}", last_event_id=None, ) first_chunk = await anext(stream.body_iterator) assert "event: sync" in first_chunk assert '"turn_id":"' + turn["id"] + '"' in first_chunk await stream.body_iterator.aclose()