# #region TestAgentChat.Api [C:3] [TYPE Module] [SEMANTICS test,agent,api,conversations] # @BRIEF Tests for AgentChat REST API — save, active gate, LLM config. # @RELATION BINDS_TO -> [AgentChat.Api.Conversations] # @TEST_EDGE: save_valid -> new conversation created # @TEST_EDGE: save_update -> existing conversation updated # @TEST_EDGE: active_gate -> always returns {active: false} # @TEST_EDGE: llm_config -> endpoint reachable # @NOTE History and delete/archive for /api/assistant prefix are handled by legacy assistant routes # (FR-020 backward compat). The new agent routes use /api/agent prefix for save/active/llm-config. import asyncio import pytest from unittest.mock import MagicMock import uuid from fastapi import HTTPException from src.api.routes import agent_conversations from src.core.database import SessionLocal from src.schemas.agent import SaveConversationRequest def _make_mock_user(user_id: str = "test-user-id"): """Create a mock authenticated user.""" mock_user = MagicMock() mock_user.id = user_id mock_user.username = "test-user" return mock_user MOCK_USER = _make_mock_user() @pytest.fixture(autouse=True) def db_session(): """Provide a real session against the pytest global SQLite database.""" db = SessionLocal() try: yield db finally: db.close() def _run(coro): return asyncio.run(coro) # #region TestAgentChat.Api.Save [C:2] [TYPE Function] [SEMANTICS test,api,save] # @BRIEF Conversation save endpoint tests — POST /api/agent/conversations/save. def test_save_conversation(db_session): """POST /api/agent/conversations/save creates a new conversation.""" conversation_id = f"test-save-{uuid.uuid4().hex}" data = _run( agent_conversations.save_conversation( SaveConversationRequest(conversation_id=conversation_id, title="Save Test", user_id="test-user-id"), db_session, ) ) assert data["saved"] is True assert data["conversation_id"] == conversation_id def test_save_conversation_updates_existing(db_session): """POST /api/agent/conversations/save updates existing conversation title.""" conversation_id = f"test-save-{uuid.uuid4().hex}" # Create _run( agent_conversations.save_conversation( SaveConversationRequest(conversation_id=conversation_id, title="Original", user_id="test-user-id"), db_session, ) ) # Update data = _run( agent_conversations.save_conversation( SaveConversationRequest(conversation_id=conversation_id, title="Updated Title", user_id="test-user-id"), db_session, ) ) assert data["saved"] is True def test_save_conversation_default_user_id(db_session): """POST /api/agent/conversations/save without user_id defaults to 'admin'.""" data = _run( agent_conversations.save_conversation( SaveConversationRequest(conversation_id=f"test-save-{uuid.uuid4().hex}", title="Default User"), db_session, ) ) assert data["saved"] is True # #endregion TestAgentChat.Api.Save # #region TestAgentChat.Api.ActiveGate [C:2] [TYPE Function] [SEMANTICS test,api,active] # @BRIEF Multi-tab active session gate tests — GET /api/agent/conversations/active. def test_check_active_session(): """GET /api/agent/conversations/active returns {active: false}.""" data = _run(agent_conversations.check_active_session()) assert data == {"active": False} # #endregion TestAgentChat.Api.ActiveGate # #region TestAgentChat.Api.LlmConfig [C:2] [TYPE Function] [SEMANTICS test,api,llm] # @BRIEF LLM config endpoint tests — GET /api/agent/llm-config. # @NOTE Requires ENCRYPTION_KEY at request time. Passes even when env var is cleared mid-suite. def test_llm_config_endpoint_reachable(db_session): """GET /api/agent/llm-config is reachable (skip if EncryptionManager unavailable).""" try: config_manager = MagicMock() config_manager.get_config.return_value.settings.llm = {} data = _run(agent_conversations.get_agent_llm_config(db_session, config_manager)) assert isinstance(data, dict) except RuntimeError as e: if "ENCRYPTION_KEY" in str(e): pytest.skip("ENCRYPTION_KEY not available at request time") raise def test_llm_config_response_shape(db_session): """LLM config response contains expected fields when 200.""" try: config_manager = MagicMock() config_manager.get_config.return_value.settings.llm = {} data = _run(agent_conversations.get_agent_llm_config(db_session, config_manager)) assert "configured" in data, "Response should have 'configured' field" except RuntimeError as e: if "ENCRYPTION_KEY" in str(e): pytest.skip("ENCRYPTION_KEY not available at request time") raise # #endregion TestAgentChat.Api.LlmConfig # #region TestAgentChat.Api.LegacyCompat [C:2] [TYPE Function] [SEMANTICS test,api,legacy] # @BRIEF Legacy assistant route backward compatibility (FR-020). def test_legacy_history_returns_empty_for_nonexistent(db_session): """GET /api/assistant/history returns 404 for nonexistent conversation.""" bad_id = f"nonexistent-{uuid.uuid4().hex}" with pytest.raises(HTTPException) as exc_info: _run(agent_conversations.get_history(bad_id, user=MOCK_USER, db=db_session)) assert exc_info.value.status_code == 404 assert exc_info.value.detail == "Conversation not found" def test_legacy_conversations_list(db_session): """GET /api/assistant/conversations returns 200 with items (legacy compat).""" data = _run(agent_conversations.list_conversations(page=1, page_size=20, search="", user=MOCK_USER, db=db_session)) assert hasattr(data, "items") def test_legacy_pagination_params(db_session): """GET /api/assistant/conversations supports page/page_size (legacy compat).""" data = _run(agent_conversations.list_conversations(page=1, page_size=5, search="", user=MOCK_USER, db=db_session)) assert hasattr(data, "items") # #endregion TestAgentChat.Api.LegacyCompat # #endregion TestAgentChat.Api