154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
# #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
|