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
390 lines
15 KiB
Python
390 lines
15 KiB
Python
"""
|
|
Tests for MCP server validation functions.
|
|
|
|
These tests verify input validation for all MCP tool parameters.
|
|
"""
|
|
|
|
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 TestValidationFunctions:
|
|
"""Tests for parameter validation helper functions."""
|
|
|
|
def test_validate_query_valid(self):
|
|
"""Test valid query passes validation."""
|
|
from local_deep_research.mcp.server import _validate_query
|
|
|
|
result = _validate_query("What is quantum computing?")
|
|
assert result == "What is quantum computing?"
|
|
|
|
def test_validate_query_strips_whitespace(self):
|
|
"""Test query is stripped of whitespace."""
|
|
from local_deep_research.mcp.server import _validate_query
|
|
|
|
result = _validate_query(" test query ")
|
|
assert result == "test query"
|
|
|
|
def test_validate_query_empty_raises(self):
|
|
"""Test empty query raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_query,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="cannot be empty"):
|
|
_validate_query("")
|
|
|
|
def test_validate_query_whitespace_only_raises(self):
|
|
"""Test whitespace-only query raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_query,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="cannot be empty"):
|
|
_validate_query(" ")
|
|
|
|
def test_validate_query_too_long_raises(self):
|
|
"""Test query exceeding max length raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_query,
|
|
ValidationError,
|
|
)
|
|
|
|
long_query = "x" * 10001
|
|
with pytest.raises(ValidationError, match="exceeds maximum length"):
|
|
_validate_query(long_query)
|
|
|
|
def test_validate_query_at_max_length(self):
|
|
"""Test query at exactly max length is valid."""
|
|
from local_deep_research.mcp.server import _validate_query
|
|
|
|
max_query = "x" * 10000
|
|
result = _validate_query(max_query)
|
|
assert len(result) == 10000
|
|
|
|
def test_validate_iterations_valid(self):
|
|
"""Test valid iterations pass validation."""
|
|
from local_deep_research.mcp.server import _validate_iterations
|
|
|
|
assert _validate_iterations(5) == 5
|
|
assert _validate_iterations(1) == 1
|
|
assert _validate_iterations(20) == 20
|
|
|
|
def test_validate_iterations_none_allowed(self):
|
|
"""Test None iterations returns None."""
|
|
from local_deep_research.mcp.server import _validate_iterations
|
|
|
|
assert _validate_iterations(None) is None
|
|
|
|
def test_validate_iterations_zero_raises(self):
|
|
"""Test zero iterations raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_iterations,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="positive integer"):
|
|
_validate_iterations(0)
|
|
|
|
def test_validate_iterations_negative_raises(self):
|
|
"""Test negative iterations raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_iterations,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="positive integer"):
|
|
_validate_iterations(-5)
|
|
|
|
def test_validate_iterations_exceeds_max_raises(self):
|
|
"""Test iterations exceeding max raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_iterations,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="cannot exceed"):
|
|
_validate_iterations(21, max_val=20)
|
|
|
|
def test_validate_iterations_custom_max(self):
|
|
"""Test iterations with custom max value."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_iterations,
|
|
ValidationError,
|
|
)
|
|
|
|
assert _validate_iterations(5, max_val=10) == 5
|
|
with pytest.raises(ValidationError, match="cannot exceed 10"):
|
|
_validate_iterations(11, max_val=10)
|
|
|
|
def test_validate_questions_per_iteration_valid(self):
|
|
"""Test valid questions_per_iteration passes validation."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_questions_per_iteration,
|
|
)
|
|
|
|
assert _validate_questions_per_iteration(3) == 3
|
|
assert _validate_questions_per_iteration(1) == 1
|
|
assert _validate_questions_per_iteration(10) == 10
|
|
|
|
def test_validate_questions_per_iteration_none_allowed(self):
|
|
"""Test None questions_per_iteration returns None."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_questions_per_iteration,
|
|
)
|
|
|
|
assert _validate_questions_per_iteration(None) is None
|
|
|
|
def test_validate_questions_per_iteration_zero_raises(self):
|
|
"""Test zero questions_per_iteration raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_questions_per_iteration,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="positive integer"):
|
|
_validate_questions_per_iteration(0)
|
|
|
|
def test_validate_questions_per_iteration_exceeds_max_raises(self):
|
|
"""Test questions_per_iteration exceeding max raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_questions_per_iteration,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="cannot exceed 10"):
|
|
_validate_questions_per_iteration(11)
|
|
|
|
def test_validate_max_results_valid(self):
|
|
"""Test valid max_results passes validation."""
|
|
from local_deep_research.mcp.server import _validate_max_results
|
|
|
|
assert _validate_max_results(10) == 10
|
|
assert _validate_max_results(1) == 1
|
|
assert _validate_max_results(100) == 100
|
|
|
|
def test_validate_max_results_zero_raises(self):
|
|
"""Test zero max_results raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_max_results,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="positive integer"):
|
|
_validate_max_results(0)
|
|
|
|
def test_validate_max_results_exceeds_max_raises(self):
|
|
"""Test max_results exceeding max raises ValidationError."""
|
|
from local_deep_research.mcp.server import (
|
|
_validate_max_results,
|
|
ValidationError,
|
|
)
|
|
|
|
with pytest.raises(ValidationError, match="cannot exceed 100"):
|
|
_validate_max_results(101)
|
|
|
|
|
|
class TestValidationInTools:
|
|
"""Tests for validation being applied in tool functions."""
|
|
|
|
def test_quick_research_empty_query_returns_error(self):
|
|
"""Test quick_research with empty query returns validation error."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
result = quick_research(query="")
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
assert "empty" in result["error"].lower()
|
|
|
|
def test_quick_research_invalid_iterations_returns_error(self):
|
|
"""Test quick_research with invalid iterations returns validation error."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
result = quick_research(query="Test query", iterations=0)
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_quick_research_iterations_exceeds_max_returns_error(self):
|
|
"""Test quick_research with iterations exceeding max returns validation error."""
|
|
from local_deep_research.mcp.server import quick_research
|
|
|
|
result = quick_research(query="Test query", iterations=100)
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_detailed_research_empty_query_returns_error(self):
|
|
"""Test detailed_research with empty query returns validation error."""
|
|
from local_deep_research.mcp.server import detailed_research
|
|
|
|
result = detailed_research(query=" ")
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_generate_report_empty_query_returns_error(self):
|
|
"""Test generate_report with empty query returns validation error."""
|
|
from local_deep_research.mcp.server import generate_report
|
|
|
|
result = generate_report(query="")
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_generate_report_invalid_searches_per_section(self):
|
|
"""Test generate_report with invalid searches_per_section returns error."""
|
|
from local_deep_research.mcp.server import generate_report
|
|
|
|
result = generate_report(query="Test query", searches_per_section=0)
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_generate_report_searches_per_section_exceeds_max(self):
|
|
"""Test generate_report with searches_per_section exceeding max returns error."""
|
|
from local_deep_research.mcp.server import generate_report
|
|
|
|
result = generate_report(query="Test query", searches_per_section=11)
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_analyze_documents_empty_query_returns_error(self):
|
|
"""Test analyze_documents with empty query returns validation error."""
|
|
from local_deep_research.mcp.server import analyze_documents
|
|
|
|
result = analyze_documents(query="", collection_name="test")
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_analyze_documents_empty_collection_returns_error(self):
|
|
"""Test analyze_documents with empty collection returns validation error."""
|
|
from local_deep_research.mcp.server import analyze_documents
|
|
|
|
result = analyze_documents(query="Test", collection_name="")
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
def test_analyze_documents_invalid_max_results_returns_error(self):
|
|
"""Test analyze_documents with invalid max_results returns error."""
|
|
from local_deep_research.mcp.server import analyze_documents
|
|
|
|
result = analyze_documents(
|
|
query="Test", collection_name="test", max_results=0
|
|
)
|
|
assert result["status"] == "error"
|
|
assert result["error_type"] == "validation_error"
|
|
|
|
|
|
class TestErrorClassification:
|
|
"""Tests for the _classify_error function."""
|
|
|
|
def test_classify_service_unavailable(self):
|
|
"""Test 503 error is classified as service_unavailable."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert (
|
|
_classify_error("Error 503: Service unavailable")
|
|
== "service_unavailable"
|
|
)
|
|
assert (
|
|
_classify_error("The server is unavailable")
|
|
== "service_unavailable"
|
|
)
|
|
|
|
def test_classify_model_not_found(self):
|
|
"""Test 404 error is classified as model_not_found."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert (
|
|
_classify_error("Error 404: Model not found") == "model_not_found"
|
|
)
|
|
assert _classify_error("Resource not found") == "model_not_found"
|
|
|
|
def test_classify_auth_error(self):
|
|
"""Test auth errors are classified correctly."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Invalid API key") == "auth_error"
|
|
assert _classify_error("Authentication failed") == "auth_error"
|
|
assert _classify_error("Unauthorized access") == "auth_error"
|
|
assert _classify_error("Error 401: Unauthorized") == "auth_error"
|
|
|
|
def test_classify_timeout(self):
|
|
"""Test timeout errors are classified correctly."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Request timeout") == "timeout"
|
|
# "Connection timed out" matches "timed out" check which comes before
|
|
# the "connection" check, so it returns "timeout"
|
|
assert _classify_error("Connection timed out") == "timeout"
|
|
# "Operation timed out" also matches "timed out"
|
|
assert _classify_error("Operation timed out") == "timeout"
|
|
assert _classify_error("A timeout occurred") == "timeout"
|
|
|
|
def test_classify_rate_limit(self):
|
|
"""Test rate limit errors are classified correctly."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Rate limit exceeded") == "rate_limit"
|
|
assert _classify_error("Error 429: Too many requests") == "rate_limit"
|
|
|
|
def test_classify_connection_error(self):
|
|
"""Test connection errors are classified correctly."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Connection refused") == "connection_error"
|
|
assert (
|
|
_classify_error("Failed to establish connection")
|
|
== "connection_error"
|
|
)
|
|
|
|
def test_classify_validation_error(self):
|
|
"""Test validation errors are classified correctly."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Validation failed") == "validation_error"
|
|
assert _classify_error("Invalid parameter") == "validation_error"
|
|
|
|
def test_classify_unknown_error(self):
|
|
"""Test unknown errors are classified as unknown."""
|
|
from local_deep_research.mcp.server import _classify_error
|
|
|
|
assert _classify_error("Some random error") == "unknown"
|
|
assert _classify_error("Unexpected exception") == "unknown"
|
|
|
|
|
|
class TestGetAvailableStrategies:
|
|
"""Tests for the get_available_strategies function.
|
|
|
|
Note: These tests don't require the MCP package as they test the factory module.
|
|
"""
|
|
|
|
@pytest.mark.skipif(
|
|
True, reason="Tested in test_search_system_factory.py instead"
|
|
)
|
|
def test_get_available_strategies_returns_list(self):
|
|
"""Test get_available_strategies returns a list."""
|
|
pass
|
|
|
|
def test_list_strategies_uses_get_available_strategies(self):
|
|
"""Test MCP list_strategies tool uses the factory function."""
|
|
from local_deep_research.mcp.server import list_strategies
|
|
from local_deep_research.search_system_factory import (
|
|
get_available_strategies,
|
|
)
|
|
|
|
result = list_strategies()
|
|
expected = get_available_strategies()
|
|
|
|
assert result["status"] == "success"
|
|
assert result["strategies"] == expected
|