108 lines
4.0 KiB
Python
108 lines
4.0 KiB
Python
"""Regression tests for the deployed backend research pipeline fixes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from uuid import uuid4
|
|
|
|
import httpx
|
|
from unittest.mock import AsyncMock
|
|
|
|
from nsct.config import AppSettings, AudioConfig, DatabaseConfig, LLMConfig, VisionConfig
|
|
from nsct.orchestration.orchestrator import ResearchOrchestrator
|
|
from nsct.orchestration.state import ResearchRunState
|
|
from nsct.providers.llm import _LLMProviderImpl
|
|
from nsct.providers.metrics import ProviderMetrics
|
|
from nsct.providers.searxng import SearXNGProvider
|
|
|
|
|
|
def _config(*, searxng_base_url: str | None = "http://searxng:8080") -> AppSettings:
|
|
return AppSettings(
|
|
llm=LLMConfig(base_url="http://llm.example/v1", model="test-model"),
|
|
vision=VisionConfig(base_url="http://vision.example/v1", model="vision"),
|
|
audio=AudioConfig(base_url="http://audio.example/v1"),
|
|
postgres=DatabaseConfig(url="sqlite+aiosqlite://"),
|
|
searxng_base_url=searxng_base_url,
|
|
)
|
|
|
|
|
|
def test_llm_base_url_does_not_duplicate_existing_v1() -> None:
|
|
provider = _LLMProviderImpl(_config(), ProviderMetrics())
|
|
provider._api_key = "test-key"
|
|
client = provider._create_client()
|
|
try:
|
|
assert str(client.base_url) == "http://llm.example/v1/"
|
|
finally:
|
|
asyncio.run(client.close())
|
|
|
|
|
|
def test_searxng_provider_normalizes_json_results() -> None:
|
|
async def run() -> None:
|
|
provider = SearXNGProvider("http://searxng:8080")
|
|
provider._client = httpx.AsyncClient(
|
|
transport=httpx.MockTransport(
|
|
lambda request: httpx.Response(
|
|
200,
|
|
json={
|
|
"results": [
|
|
{"url": "https://example.org/a", "title": "A", "content": "Snippet"},
|
|
{"url": "mailto:invalid@example.org", "title": "Invalid"},
|
|
]
|
|
},
|
|
)
|
|
)
|
|
)
|
|
try:
|
|
results = await provider.search("test")
|
|
assert len(results) == 1
|
|
assert results[0].provider == "searxng"
|
|
assert results[0].url == "https://example.org/a"
|
|
assert results[0].snippet == "Snippet"
|
|
assert provider._client is not None
|
|
finally:
|
|
await provider._client.aclose()
|
|
|
|
asyncio.run(run())
|
|
|
|
|
|
def test_orchestrator_registers_searxng_provider() -> None:
|
|
orchestrator = ResearchOrchestrator(_config(), uuid4(), "test")
|
|
provider = orchestrator._get_multi_search().get_provider("searxng")
|
|
assert isinstance(provider, SearXNGProvider)
|
|
|
|
|
|
def test_pipeline_executes_extracting_between_fetching_and_analyzing() -> None:
|
|
async def run() -> None:
|
|
orchestrator = ResearchOrchestrator(_config(), uuid4(), "test")
|
|
orchestrator._comparison_data = {}
|
|
for step in ("planning", "searching", "fetching", "extracting", "analyzing", "comparing", "synthesizing"):
|
|
async def handler(target: str = step) -> dict[str, object]:
|
|
orchestrator._transition_to(target)
|
|
return {"success": True, "data": {}}
|
|
|
|
setattr(orchestrator, f"_step_{step}", AsyncMock(side_effect=handler))
|
|
|
|
result = await orchestrator.run()
|
|
|
|
assert result["success"] is True
|
|
assert orchestrator._step_extracting.await_count == 1
|
|
assert orchestrator.state is ResearchRunState.COMPLETED
|
|
|
|
asyncio.run(run())
|
|
|
|
|
|
def test_no_search_provider_marks_run_failed_with_reason() -> None:
|
|
async def run() -> None:
|
|
orchestrator = ResearchOrchestrator(_config(searxng_base_url=None), uuid4(), "test")
|
|
await orchestrator.start()
|
|
orchestrator._step_planning = AsyncMock(return_value={"success": True, "data": {}})
|
|
|
|
result = await orchestrator.run()
|
|
|
|
assert result["success"] is False
|
|
assert result["failed_step"] == "searching"
|
|
assert "No search provider" in result["error"]
|
|
assert orchestrator.state is ResearchRunState.FAILED
|
|
|
|
asyncio.run(run())
|