7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
399 lines
14 KiB
Python
399 lines
14 KiB
Python
"""
|
|
Integration tests for MCP server.
|
|
|
|
Tests for complete research workflows with realistic mocked data.
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
# Skip all tests if MCP is not available
|
|
try:
|
|
import mcp # noqa: F401
|
|
|
|
MCP_AVAILABLE = True
|
|
except ImportError:
|
|
MCP_AVAILABLE = False
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not MCP_AVAILABLE, reason="MCP package not installed"
|
|
)
|
|
|
|
|
|
class TestSettingsFlow:
|
|
"""Tests for settings snapshot creation and override flow."""
|
|
|
|
def test_settings_snapshot_created_for_quick_research(self):
|
|
"""Verify settings snapshot is created when calling quick_research."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.create_settings_snapshot"
|
|
) as mock_snapshot:
|
|
mock_snapshot.return_value = {"llm.provider": "openai"}
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
return_value={
|
|
"summary": "Test",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
},
|
|
):
|
|
result = quick_research(query="test")
|
|
|
|
mock_snapshot.assert_called_once()
|
|
assert result["status"] == "success"
|
|
|
|
def test_settings_overrides_applied_to_snapshot(self):
|
|
"""Verify overrides are passed to create_settings_snapshot."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.create_settings_snapshot"
|
|
) as mock_snapshot:
|
|
mock_snapshot.return_value = {}
|
|
with (
|
|
patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
return_value={
|
|
"summary": "Test",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
},
|
|
),
|
|
patch(
|
|
"local_deep_research.mcp.server._validate_search_engine",
|
|
side_effect=lambda e: e,
|
|
),
|
|
patch(
|
|
"local_deep_research.mcp.server._validate_strategy",
|
|
side_effect=lambda s: s,
|
|
),
|
|
):
|
|
quick_research(
|
|
query="test",
|
|
search_engine="wikipedia",
|
|
strategy="source-based",
|
|
iterations=3,
|
|
)
|
|
|
|
# Check that overrides were passed (last call is from quick_research)
|
|
call_kwargs = mock_snapshot.call_args[1]
|
|
assert "overrides" in call_kwargs
|
|
overrides = call_kwargs["overrides"]
|
|
assert overrides["search.tool"] == "wikipedia"
|
|
assert overrides["search.search_strategy"] == "source-based"
|
|
assert overrides["search.iterations"] == 3
|
|
|
|
def test_settings_snapshot_passed_to_api(self):
|
|
"""Verify settings snapshot is passed to the API function."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
mock_settings = {"llm.provider": "openai", "search.tool": "wikipedia"}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.create_settings_snapshot",
|
|
return_value=mock_settings,
|
|
):
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary"
|
|
) as mock_api:
|
|
mock_api.return_value = {
|
|
"summary": "Test",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
}
|
|
quick_research(query="test query")
|
|
|
|
# Verify settings_snapshot was passed to API
|
|
call_kwargs = mock_api.call_args[1]
|
|
assert "settings_snapshot" in call_kwargs
|
|
assert call_kwargs["settings_snapshot"] == mock_settings
|
|
|
|
def test_no_overrides_when_no_params(self):
|
|
"""Verify no overrides dict when no optional params provided."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.create_settings_snapshot"
|
|
) as mock_snapshot:
|
|
mock_snapshot.return_value = {}
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
return_value={
|
|
"summary": "Test",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
},
|
|
):
|
|
quick_research(query="test") # No optional params
|
|
|
|
# Should still be called (with empty overrides or no overrides kwarg)
|
|
mock_snapshot.assert_called_once()
|
|
|
|
|
|
class TestResearchFlowIntegration:
|
|
"""Tests for complete research workflows."""
|
|
|
|
def test_quick_research_complete_flow(self):
|
|
"""Test complete quick_research flow from input to output."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
mock_result = {
|
|
"summary": "Quantum computing uses qubits for computation.",
|
|
"findings": [
|
|
{"phase": "Iteration 1", "content": "Found info about qubits"},
|
|
{
|
|
"phase": "Iteration 2",
|
|
"content": "Found info about superposition",
|
|
},
|
|
],
|
|
"sources": [
|
|
{"title": "Wikipedia", "link": "https://wikipedia.org/quantum"},
|
|
],
|
|
"iterations": 2,
|
|
"formatted_findings": "## Findings\n\nContent here",
|
|
}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
return_value=mock_result,
|
|
):
|
|
result = quick_research(
|
|
query="What is quantum computing?",
|
|
search_engine="wikipedia",
|
|
iterations=2,
|
|
)
|
|
|
|
assert result["status"] == "success"
|
|
assert result["summary"] == mock_result["summary"]
|
|
assert len(result["findings"]) == 2
|
|
assert len(result["sources"]) == 1
|
|
assert result["iterations"] == 2
|
|
|
|
def test_detailed_research_complete_flow(self):
|
|
"""Test complete detailed_research flow."""
|
|
from local_deep_research.mcp.server import detailed_research
|
|
|
|
mock_result = {
|
|
"query": "machine learning applications",
|
|
"research_id": "research-abc-123",
|
|
"summary": "Machine learning has many applications...",
|
|
"findings": [{"content": "Finding 1"}, {"content": "Finding 2"}],
|
|
"sources": [{"title": "Source 1", "link": "https://example.com"}],
|
|
"iterations": 3,
|
|
"formatted_findings": "Formatted content",
|
|
"metadata": {
|
|
"timestamp": "2024-01-15T10:00:00Z",
|
|
"search_tool": "arxiv",
|
|
"strategy": "source-based",
|
|
},
|
|
}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_detailed_research",
|
|
return_value=mock_result,
|
|
):
|
|
result = detailed_research(
|
|
query="machine learning applications",
|
|
search_engine="arxiv",
|
|
strategy="source-based",
|
|
)
|
|
|
|
assert result["status"] == "success"
|
|
assert result["query"] == "machine learning applications"
|
|
assert result["research_id"] == "research-abc-123"
|
|
assert "metadata" in result
|
|
assert result["metadata"]["search_tool"] == "arxiv"
|
|
|
|
def test_generate_report_complete_flow(self):
|
|
"""Test complete generate_report flow."""
|
|
from local_deep_research.mcp.server import generate_report
|
|
|
|
mock_result = {
|
|
"content": "# Research Report\n\n## Introduction\n\nThis report...\n\n## Findings\n\n...",
|
|
"metadata": {
|
|
"generated_at": "2024-01-15T10:00:00Z",
|
|
"query": "climate change",
|
|
},
|
|
}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_generate_report",
|
|
return_value=mock_result,
|
|
):
|
|
result = generate_report(
|
|
query="climate change impacts",
|
|
searches_per_section=3,
|
|
)
|
|
|
|
assert result["status"] == "success"
|
|
assert result["content"].startswith("# Research Report")
|
|
assert "metadata" in result
|
|
|
|
|
|
class TestDiscoveryToolsIntegration:
|
|
"""Integration tests for discovery tools."""
|
|
|
|
def test_list_strategies_returns_all_expected_strategies(self):
|
|
"""Verify list_strategies returns the complete list."""
|
|
from local_deep_research.mcp.server import list_strategies
|
|
|
|
result = list_strategies()
|
|
|
|
assert result["status"] == "success"
|
|
strategy_names = [s["name"] for s in result["strategies"]]
|
|
|
|
# Check for key strategies
|
|
assert "source-based" in strategy_names
|
|
assert "focused-iteration" in strategy_names
|
|
assert "topic-organization" in strategy_names
|
|
assert "langgraph-agent" in strategy_names
|
|
|
|
def test_get_configuration_structure(self):
|
|
"""Verify get_configuration returns expected structure."""
|
|
from local_deep_research.mcp.server import get_configuration
|
|
|
|
result = get_configuration()
|
|
|
|
assert result["status"] == "success"
|
|
assert "config" in result
|
|
|
|
config = result["config"]
|
|
assert "llm" in config
|
|
assert "search" in config
|
|
|
|
# Check LLM config structure
|
|
assert "provider" in config["llm"]
|
|
assert "model" in config["llm"]
|
|
assert "temperature" in config["llm"]
|
|
|
|
# Check search config structure
|
|
assert "default_engine" in config["search"]
|
|
assert "default_strategy" in config["search"]
|
|
assert "iterations" in config["search"]
|
|
|
|
|
|
class TestErrorFlowIntegration:
|
|
"""Integration tests for error handling flows."""
|
|
|
|
def test_quick_research_error_flow(self):
|
|
"""Test error handling flow in quick_research."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
side_effect=Exception("LLM service unavailable: 503"),
|
|
):
|
|
result = quick_research(query="test")
|
|
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "service_unavailable"
|
|
|
|
def test_detailed_research_error_flow(self):
|
|
"""Test error handling flow in detailed_research."""
|
|
from local_deep_research.mcp.server import detailed_research
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_detailed_research",
|
|
side_effect=Exception("Invalid API key"),
|
|
):
|
|
result = detailed_research(query="test")
|
|
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "auth_error"
|
|
|
|
def test_generate_report_error_flow(self):
|
|
"""Test error handling flow in generate_report."""
|
|
from local_deep_research.mcp.server import generate_report
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_generate_report",
|
|
side_effect=Exception("Request timeout after 30s"),
|
|
):
|
|
result = generate_report(query="test")
|
|
|
|
assert result["status"] == "error"
|
|
assert "timeout" in result["error"].lower()
|
|
assert result["error_type"] == "timeout"
|
|
|
|
def test_analyze_documents_error_flow(self):
|
|
"""Test error handling flow in analyze_documents."""
|
|
from local_deep_research.mcp.server import analyze_documents
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_analyze_documents",
|
|
side_effect=Exception("Collection 'nonexistent' not found"),
|
|
):
|
|
result = analyze_documents(
|
|
query="test", collection_name="nonexistent"
|
|
)
|
|
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "model_not_found"
|
|
|
|
|
|
class TestMultipleCallsIsolation:
|
|
"""Tests to verify calls don't interfere with each other."""
|
|
|
|
def test_settings_isolation_between_calls(self):
|
|
"""Verify settings from one call don't leak to another."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
calls = []
|
|
|
|
def capture_call(*args, **kwargs):
|
|
calls.append(kwargs.get("settings_snapshot", {}))
|
|
return {
|
|
"summary": "Test",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
side_effect=capture_call,
|
|
):
|
|
# First call with wikipedia
|
|
quick_research(query="test1", search_engine="wikipedia")
|
|
# Second call with arxiv
|
|
quick_research(query="test2", search_engine="arxiv")
|
|
|
|
# Each call should have its own settings
|
|
assert len(calls) == 2
|
|
# Settings should be different (or at least both captured)
|
|
|
|
def test_error_in_one_call_doesnt_affect_next(self):
|
|
"""Verify an error in one call doesn't affect subsequent calls."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
call_count = [0]
|
|
|
|
def mock_api(*args, **kwargs):
|
|
call_count[0] += 1
|
|
if call_count[0] == 1:
|
|
raise Exception("First call fails")
|
|
return {
|
|
"summary": "Success",
|
|
"findings": [],
|
|
"sources": [],
|
|
"iterations": 1,
|
|
}
|
|
|
|
with patch(
|
|
"local_deep_research.mcp.server.ldr_quick_summary",
|
|
side_effect=mock_api,
|
|
):
|
|
result1 = quick_research(query="test1")
|
|
result2 = quick_research(query="test2")
|
|
|
|
assert result1["status"] == "error"
|
|
assert result2["status"] == "success"
|
|
assert result2["summary"] == "Success"
|