261 lines
7.4 KiB
Python
261 lines
7.4 KiB
Python
from src.backend.config import settings
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
|
def test_settings_loads_api_key():
|
|
assert settings.api_key == "test-key-12345"
|
|
|
|
|
|
def test_settings_loads_db_path():
|
|
assert "test.db" in settings.db_path
|
|
|
|
|
|
def test_settings_loads_worker_url():
|
|
assert settings.worker_url == "http://localhost:3001"
|
|
|
|
|
|
def test_settings_loads_claude_model():
|
|
assert settings.claude_model == "claude-sonnet-4-6"
|
|
|
|
|
|
def test_settings_loads_rate_limits():
|
|
assert settings.rate_limit_per_minute == 100
|
|
assert settings.rate_limit_per_hour == 1000
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_init_db_creates_tables():
|
|
from src.backend.db.database import init_db, get_db
|
|
|
|
await init_db()
|
|
db = await get_db()
|
|
try:
|
|
cursor = await db.execute(
|
|
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
|
)
|
|
tables = [row[0] for row in await cursor.fetchall()]
|
|
assert "topics" in tables
|
|
assert "requests" in tables
|
|
finally:
|
|
await db.close()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_foreign_keys_enabled():
|
|
from src.backend.db.database import init_db, get_db
|
|
|
|
await init_db()
|
|
db = await get_db()
|
|
try:
|
|
cursor = await db.execute("PRAGMA foreign_keys")
|
|
row = await cursor.fetchone()
|
|
assert row[0] == 1
|
|
finally:
|
|
await db.close()
|
|
|
|
|
|
def test_topic_create_model_validates():
|
|
from src.backend.models.api import TopicCreate
|
|
|
|
topic = TopicCreate(
|
|
name="coding",
|
|
system_prompt="You are an expert coder.",
|
|
summary="Coding questions",
|
|
model="claude-sonnet-4-6",
|
|
)
|
|
assert topic.name == "coding"
|
|
assert topic.model == "claude-sonnet-4-6"
|
|
|
|
|
|
def test_topic_create_model_optional_fields():
|
|
from src.backend.models.api import TopicCreate
|
|
|
|
topic = TopicCreate(name="coding", system_prompt="You are an expert coder.")
|
|
assert topic.summary == ""
|
|
assert topic.model is None
|
|
|
|
|
|
def test_request_create_model_validates():
|
|
from src.backend.models.api import RequestCreate
|
|
|
|
req = RequestCreate(
|
|
prompt="How to do X?",
|
|
topic_id="550e8400-e29b-41d4-a716-446655440000",
|
|
)
|
|
assert req.session_id is None
|
|
assert req.model is None
|
|
assert req.summarize is False
|
|
|
|
|
|
def test_model_validation_rejects_invalid():
|
|
from src.backend.models.api import TopicCreate
|
|
from pydantic import ValidationError
|
|
|
|
with pytest.raises(ValidationError):
|
|
TopicCreate(name="test", system_prompt="test", model="gpt-4")
|
|
|
|
|
|
def test_envelope_ok():
|
|
from src.backend.models.api import ok
|
|
|
|
result = ok({"key": "value"})
|
|
assert result == {"success": True, "data": {"key": "value"}, "error": None}
|
|
|
|
|
|
def test_envelope_fail():
|
|
from src.backend.models.api import fail
|
|
|
|
result = fail("NOT_FOUND", "Resource not found")
|
|
assert result["success"] is False
|
|
assert result["error"]["code"] == "NOT_FOUND"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_worker_client_process():
|
|
from src.backend.services.worker_client import WorkerClient
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"success": True,
|
|
"result": "The answer is 42.",
|
|
"session_id": "sess-123",
|
|
}
|
|
|
|
with patch("src.backend.services.worker_client.httpx.AsyncClient") as MockClient:
|
|
mock_client_instance = AsyncMock()
|
|
mock_client_instance.post.return_value = mock_response
|
|
mock_client_instance.__aenter__ = AsyncMock(return_value=mock_client_instance)
|
|
mock_client_instance.__aexit__ = AsyncMock(return_value=False)
|
|
MockClient.return_value = mock_client_instance
|
|
|
|
client = WorkerClient()
|
|
result = await client.process(
|
|
prompt="What is the answer?",
|
|
system_prompt="You know everything.",
|
|
model="claude-sonnet-4-6",
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert result["result"] == "The answer is 42."
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_worker_client_summarize():
|
|
from src.backend.services.worker_client import WorkerClient
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"success": True,
|
|
"summary": "[2026-05-25] Discussed the meaning of life.",
|
|
}
|
|
|
|
with patch("src.backend.services.worker_client.httpx.AsyncClient") as MockClient:
|
|
mock_client_instance = AsyncMock()
|
|
mock_client_instance.post.return_value = mock_response
|
|
mock_client_instance.__aenter__ = AsyncMock(return_value=mock_client_instance)
|
|
mock_client_instance.__aexit__ = AsyncMock(return_value=False)
|
|
MockClient.return_value = mock_client_instance
|
|
|
|
client = WorkerClient()
|
|
result = await client.summarize(
|
|
exchanges=[{"prompt": "What?", "result": "42."}],
|
|
mode="incremental",
|
|
)
|
|
|
|
assert result["success"] is True
|
|
assert "Discussed" in result["summary"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_worker_client_health():
|
|
from src.backend.services.worker_client import WorkerClient
|
|
|
|
mock_response = AsyncMock()
|
|
mock_response.status_code = 200
|
|
|
|
with patch("src.backend.services.worker_client.httpx.AsyncClient") as MockClient:
|
|
mock_client_instance = AsyncMock()
|
|
mock_client_instance.get.return_value = mock_response
|
|
mock_client_instance.__aenter__ = AsyncMock(return_value=mock_client_instance)
|
|
mock_client_instance.__aexit__ = AsyncMock(return_value=False)
|
|
MockClient.return_value = mock_client_instance
|
|
|
|
client = WorkerClient()
|
|
is_healthy = await client.health()
|
|
assert is_healthy is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_model_request_overrides():
|
|
from src.backend.services.queue import resolve_model
|
|
|
|
model = resolve_model(
|
|
request_model="claude-opus-4-7",
|
|
topic_model="claude-sonnet-4-6",
|
|
)
|
|
assert model == "claude-opus-4-7"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_model_topic_fallback():
|
|
from src.backend.services.queue import resolve_model
|
|
|
|
model = resolve_model(
|
|
request_model=None,
|
|
topic_model="claude-sonnet-4-6",
|
|
)
|
|
assert model == "claude-sonnet-4-6"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_model_global_fallback():
|
|
from src.backend.services.queue import resolve_model
|
|
|
|
model = resolve_model(request_model=None, topic_model=None)
|
|
assert model == "claude-sonnet-4-6"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_one_request():
|
|
from src.backend.services.queue import process_one
|
|
from src.backend.db.database import (
|
|
init_db,
|
|
get_db,
|
|
create_topic,
|
|
create_request,
|
|
get_request,
|
|
)
|
|
|
|
await init_db()
|
|
db = await get_db()
|
|
try:
|
|
topic = await create_topic(db, "test-queue", "System prompt")
|
|
req = await create_request(db, "Hello", topic["id"])
|
|
finally:
|
|
await db.close()
|
|
|
|
mock_worker = AsyncMock()
|
|
mock_worker.process.return_value = {
|
|
"success": True,
|
|
"result": "Hi there!",
|
|
"session_id": None,
|
|
}
|
|
mock_worker.summarize.return_value = {
|
|
"success": True,
|
|
"summary": "Greeted the user.",
|
|
}
|
|
|
|
await process_one(mock_worker)
|
|
|
|
db = await get_db()
|
|
try:
|
|
updated = await get_request(db, req["id"])
|
|
assert updated["status"] == "completed"
|
|
assert updated["result"] == "Hi there!"
|
|
finally:
|
|
await db.close()
|