Files
ss-tools/backend/tests/test_agent/test_conversation_api.py
2026-06-30 19:05:17 +03:00

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