# #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 os import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient from src.app import app from src.dependencies import get_current_user 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 override_deps(): """Override FastAPI dependencies with mock user for all tests.""" app.dependency_overrides[get_current_user] = lambda: MOCK_USER yield app.dependency_overrides.clear() client = TestClient(app) # #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(): """POST /api/agent/conversations/save creates a new conversation.""" response = client.post( "/api/agent/conversations/save", json={"conversation_id": "test-save-1", "title": "Save Test", "user_id": "test-user-id"}, ) assert response.status_code == 200 data = response.json() assert data["saved"] is True assert data["conversation_id"] == "test-save-1" def test_save_conversation_updates_existing(): """POST /api/agent/conversations/save updates existing conversation title.""" # Create client.post( "/api/agent/conversations/save", json={"conversation_id": "test-save-2", "title": "Original", "user_id": "test-user-id"}, ) # Update response = client.post( "/api/agent/conversations/save", json={"conversation_id": "test-save-2", "title": "Updated Title", "user_id": "test-user-id"}, ) assert response.status_code == 200 assert response.json()["saved"] is True def test_save_conversation_default_user_id(): """POST /api/agent/conversations/save without user_id defaults to 'admin'.""" response = client.post( "/api/agent/conversations/save", json={"conversation_id": "test-save-default", "title": "Default User"}, ) assert response.status_code == 200 assert response.json()["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}.""" response = client.get("/api/agent/conversations/active") assert response.status_code == 200 data = response.json() 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(): """GET /api/agent/llm-config is reachable (skip if EncryptionManager unavailable).""" try: response = client.get("/api/agent/llm-config") assert response.status_code in (200, 500), \ f"Expected 200 or 500, got {response.status_code}: {response.text[:200]}" 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(): """LLM config response contains expected fields when 200.""" try: response = client.get("/api/agent/llm-config") if response.status_code == 200: data = response.json() 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(): """GET /api/assistant/history returns 404 for nonexistent conversation.""" import uuid bad_id = f"nonexistent-{uuid.uuid4().hex}" response = client.get(f"/api/assistant/history?conversation_id={bad_id}") # Endpoint now raises HTTPException(404) when conversation not found assert response.status_code == 404, f"Expected 404, got {response.status_code}" data = response.json() assert data["detail"] == "Conversation not found" def test_legacy_conversations_list(): """GET /api/assistant/conversations returns 200 with items (legacy compat).""" response = client.get("/api/assistant/conversations") assert response.status_code == 200 data = response.json() assert "items" in data def test_legacy_pagination_params(): """GET /api/assistant/conversations supports page/page_size (legacy compat).""" response = client.get("/api/assistant/conversations?page=1&page_size=5") assert response.status_code == 200 # #endregion TestAgentChat.Api.LegacyCompat # #endregion TestAgentChat.Api