Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

1438 lines
52 KiB
Python

"""
Tests for benchmarks/web_api/benchmark_service.py
Tests cover:
- BenchmarkQueueTracker functionality
- BenchmarkService initialization and methods
- Config hash generation
"""
from datetime import datetime, timedelta, UTC
from unittest.mock import Mock, patch
class TestBenchmarkTaskStatus:
"""Tests for BenchmarkTaskStatus enum."""
def test_status_values(self):
"""Test that status enum has expected values."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkTaskStatus,
)
assert BenchmarkTaskStatus.QUEUED.value == "queued"
assert BenchmarkTaskStatus.PROCESSING.value == "processing"
assert BenchmarkTaskStatus.COMPLETED.value == "completed"
assert BenchmarkTaskStatus.FAILED.value == "failed"
assert BenchmarkTaskStatus.CANCELLED.value == "cancelled"
class TestBenchmarkQueueTracker:
"""Tests for BenchmarkQueueTracker class."""
def test_add_task(self):
"""Test adding a task to the tracker."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-123", "testuser", "benchmark")
task = tracker.get_task_status("task-123")
assert task is not None
assert task["username"] == "testuser"
assert task["task_type"] == "benchmark"
assert task["status"] == BenchmarkTaskStatus.QUEUED.value
def test_update_task_status(self):
"""Test updating task status."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-123", "testuser")
tracker.update_task_status("task-123", BenchmarkTaskStatus.PROCESSING)
task = tracker.get_task_status("task-123")
assert task["status"] == BenchmarkTaskStatus.PROCESSING.value
def test_update_nonexistent_task(self):
"""Test updating status of non-existent task logs warning."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
# Should not raise, just log warning
tracker.update_task_status("nonexistent", BenchmarkTaskStatus.COMPLETED)
task = tracker.get_task_status("nonexistent")
assert task is None
def test_remove_task(self):
"""Test removing a task from tracker."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-123", "testuser")
tracker.remove_task("task-123")
task = tracker.get_task_status("task-123")
assert task is None
def test_remove_nonexistent_task(self):
"""Test removing non-existent task doesn't raise."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
)
tracker = BenchmarkQueueTracker()
# Should not raise
tracker.remove_task("nonexistent")
def test_cleanup_completed_tasks(self):
"""Test cleanup of old completed tasks."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-old", "testuser")
tracker.update_task_status("task-old", BenchmarkTaskStatus.COMPLETED)
# Manually set updated_at to be old
tracker.tasks["task-old"]["updated_at"] = datetime.now(UTC) - timedelta(
hours=2
)
tracker.add_task("task-new", "testuser")
tracker.update_task_status("task-new", BenchmarkTaskStatus.COMPLETED)
# Cleanup with 1 hour max age
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Old task should be removed
assert tracker.get_task_status("task-old") is None
# New task should remain
assert tracker.get_task_status("task-new") is not None
def test_cleanup_skips_processing_tasks(self):
"""Test that cleanup doesn't remove processing tasks."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-processing", "testuser")
tracker.update_task_status(
"task-processing", BenchmarkTaskStatus.PROCESSING
)
# Set old timestamp
tracker.tasks["task-processing"]["updated_at"] = datetime.now(
UTC
) - timedelta(hours=2)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Processing task should remain
assert tracker.get_task_status("task-processing") is not None
class TestBenchmarkServiceInit:
"""Tests for BenchmarkService initialization."""
def test_init_default(self):
"""Test default initialization."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
# Mock socket service
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
assert service.active_runs == {}
assert service.socket_service is not None
assert service.queue_tracker is not None
def test_init_with_custom_socket(self):
"""Test initialization with custom socket service."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
assert service.socket_service is mock_socket
def test_init_socket_service_fallback(self):
"""Test fallback to mock socket when Flask unavailable."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService",
side_effect=Exception("No Flask app"),
):
service = BenchmarkService()
# Should have fallback mock socket
assert service.socket_service is not None
# Mock should have emit_to_room method
assert hasattr(service.socket_service, "emit_to_room")
class TestBenchmarkServiceConfigHash:
"""Tests for config hash generation."""
def test_generate_config_hash(self):
"""Test generating config hash."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
config = {
"iterations": 2,
"questions_per_iteration": 3,
"search_strategy": "iterdrag",
}
hash1 = service.generate_config_hash(config)
hash2 = service.generate_config_hash(config)
# Same config should produce same hash
assert hash1 == hash2
assert len(hash1) == 8 # Short hash
def test_different_configs_different_hashes(self):
"""Test that different configs produce different hashes."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
config1 = {"iterations": 2}
config2 = {"iterations": 3}
hash1 = service.generate_config_hash(config1)
hash2 = service.generate_config_hash(config2)
assert hash1 != hash2
class TestBenchmarkServiceActiveRuns:
"""Tests for active run tracking."""
def test_track_active_run(self):
"""Test tracking an active benchmark run."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
service.active_runs[1] = {
"run_id": 1,
"status": "running",
"progress": 50,
}
assert 1 in service.active_runs
assert service.active_runs[1]["progress"] == 50
def test_rate_limit_tracking(self):
"""Test rate limit tracking per run."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
service.rate_limit_detected[1] = True
service.rate_limit_detected[2] = False
assert service.rate_limit_detected[1] is True
assert service.rate_limit_detected[2] is False
class TestBenchmarkQueueTrackerThreadSafety:
"""Tests for thread safety of BenchmarkQueueTracker."""
def test_concurrent_add_and_update(self):
"""Test concurrent access to tracker."""
import threading
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
errors = []
def add_tasks():
try:
for i in range(100):
tracker.add_task(
f"task-{threading.current_thread().name}-{i}", "user"
)
except Exception as e:
errors.append(e)
def update_tasks():
try:
for i in range(100):
tracker.update_task_status(
f"task-{threading.current_thread().name}-{i}",
BenchmarkTaskStatus.COMPLETED,
)
except Exception as e:
errors.append(e)
threads = []
for i in range(4):
t = threading.Thread(target=add_tasks, name=f"add-{i}")
threads.append(t)
t = threading.Thread(target=update_tasks, name=f"update-{i}")
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
# No errors should have occurred
assert len(errors) == 0
class TestBenchmarkServiceQueueTrackerIntegration:
"""Tests for queue tracker integration with service."""
def test_service_uses_queue_tracker(self):
"""Test that service uses queue tracker."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
BenchmarkTaskStatus,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
# Use the tracker through the service
service.queue_tracker.add_task("test-task", "testuser", "benchmark")
task = service.queue_tracker.get_task_status("test-task")
assert task is not None
assert task["status"] == BenchmarkTaskStatus.QUEUED.value
class TestBenchmarkServiceQueryHash:
"""Tests for query hash generation."""
def test_generate_query_hash(self):
"""Test generating query hash."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
hash1 = service.generate_query_hash("What is AI?", "simpleqa")
hash2 = service.generate_query_hash("What is AI?", "simpleqa")
# Same question and dataset should produce same hash
assert hash1 == hash2
assert len(hash1) == 32 # MD5 hex digest
def test_different_questions_different_hashes(self):
"""Test that different questions produce different hashes."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
hash1 = service.generate_query_hash("Question 1", "simpleqa")
hash2 = service.generate_query_hash("Question 2", "simpleqa")
assert hash1 != hash2
def test_different_datasets_different_hashes(self):
"""Test that different datasets produce different hashes."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
hash1 = service.generate_query_hash("Same question", "simpleqa")
hash2 = service.generate_query_hash("Same question", "browsecomp")
assert hash1 != hash2
def test_query_hash_strips_whitespace(self):
"""Test that query hash strips whitespace from question."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.SocketIOService"
) as mock_socket:
mock_socket.return_value = Mock()
service = BenchmarkService()
hash1 = service.generate_query_hash(" Question ", "simpleqa")
hash2 = service.generate_query_hash("Question", "simpleqa")
assert hash1 == hash2
class TestBenchmarkServiceProgressUpdate:
"""Tests for progress update functionality."""
def test_send_progress_update(self):
"""Test sending progress update."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
service._send_progress_update(
benchmark_run_id=1,
completed=5,
total=10,
)
# Should have called emit_to_subscribers
mock_socket.emit_to_subscribers.assert_called_once()
call_args = mock_socket.emit_to_subscribers.call_args
assert call_args[0][0] == "research_progress"
assert call_args[0][1] == 1
def test_send_progress_update_handles_zero_total(self):
"""Test progress update handles zero total gracefully."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
# Should not raise division by zero
service._send_progress_update(
benchmark_run_id=1,
completed=0,
total=0,
)
mock_socket.emit_to_subscribers.assert_called_once()
class TestBenchmarkQueueTrackerCleanup:
"""Additional tests for queue tracker cleanup."""
def test_cleanup_failed_tasks(self):
"""Test cleanup of old failed tasks."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-failed", "testuser")
tracker.update_task_status("task-failed", BenchmarkTaskStatus.FAILED)
# Set old timestamp
tracker.tasks["task-failed"]["updated_at"] = datetime.now(
UTC
) - timedelta(hours=2)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Failed task should be removed
assert tracker.get_task_status("task-failed") is None
def test_cleanup_cancelled_tasks(self):
"""Test cleanup of old cancelled tasks."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-cancelled", "testuser")
tracker.update_task_status(
"task-cancelled", BenchmarkTaskStatus.CANCELLED
)
# Set old timestamp
tracker.tasks["task-cancelled"]["updated_at"] = datetime.now(
UTC
) - timedelta(hours=2)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Cancelled task should be removed
assert tracker.get_task_status("task-cancelled") is None
def test_cleanup_skips_queued_tasks(self):
"""Test that cleanup doesn't remove queued tasks."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-queued", "testuser")
# Keep as QUEUED (default)
# Set old timestamp
tracker.tasks["task-queued"]["created_at"] = datetime.now(
UTC
) - timedelta(hours=2)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Queued task should remain
assert tracker.get_task_status("task-queued") is not None
def test_cleanup_uses_created_at_if_no_updated_at(self):
"""Test that cleanup uses created_at if updated_at is missing."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("task-old", "testuser")
tracker.update_task_status("task-old", BenchmarkTaskStatus.COMPLETED)
# Remove updated_at and set old created_at
if "updated_at" in tracker.tasks["task-old"]:
del tracker.tasks["task-old"]["updated_at"]
tracker.tasks["task-old"]["created_at"] = datetime.now(UTC) - timedelta(
hours=2
)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
# Task should be removed based on created_at
assert tracker.get_task_status("task-old") is None
class TestBenchmarkServiceCancelBenchmark:
"""Tests for cancel benchmark functionality."""
def test_cancel_sets_status_cancelled(self):
"""Test that cancel sets status to cancelled."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
# Add an active run
service.active_runs[1] = {"status": "running"}
with patch.object(service, "update_benchmark_status"):
service.cancel_benchmark(1, username="testuser")
assert service.active_runs[1]["status"] == "cancelled"
def test_cancel_returns_true_on_success(self):
"""Test that cancel returns True on success."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
service.active_runs[1] = {"status": "running"}
with patch.object(service, "update_benchmark_status"):
result = service.cancel_benchmark(1, username="testuser")
assert result is True
class TestBenchmarkServiceSyncResults:
"""Tests for sync pending results functionality."""
def test_sync_pending_results_returns_zero_for_unknown_run(self):
"""Test that sync returns 0 for unknown benchmark run."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
result = service.sync_pending_results(99999, username="testuser")
assert result == 0
class TestBenchmarkServiceCreateBenchmarkRun:
"""Tests for create_benchmark_run functionality."""
def test_create_benchmark_run_success(self):
"""Test creating a benchmark run in the database."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
# Mock the database session - patch at the source module
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
# Mock the BenchmarkRun model to capture the created object
created_run = Mock()
created_run.id = 1
def add_side_effect(run):
run.id = 1
mock_session.add.side_effect = add_side_effect
mock_session.commit = Mock()
search_config = {"iterations": 2, "search_strategy": "iterdrag"}
evaluation_config = {"model_name": "test-model"}
datasets_config = {"simpleqa": {"count": 10}}
run_id = service.create_benchmark_run(
run_name="Test Run",
search_config=search_config,
evaluation_config=evaluation_config,
datasets_config=datasets_config,
username="testuser",
)
assert run_id == 1
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
def test_create_benchmark_run_generates_config_hash(self):
"""Test that create_benchmark_run generates config hash."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
def capture_run(run):
run.id = 1
assert run.config_hash is not None
# Config hash is first 8 chars of MD5 hexdigest (see generate_config_hash)
assert len(run.config_hash) == 8
mock_session.add.side_effect = capture_run
service.create_benchmark_run(
run_name="Test",
search_config={"iterations": 2},
evaluation_config={},
datasets_config={"simpleqa": {"count": 5}},
)
def test_create_benchmark_run_calculates_total_examples(self):
"""Test that total_examples is calculated correctly."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
def capture_run(run):
run.id = 1
assert run.total_examples == 25 # 10 + 15
mock_session.add.side_effect = capture_run
service.create_benchmark_run(
run_name="Test",
search_config={},
evaluation_config={},
datasets_config={
"simpleqa": {"count": 10},
"browsecomp": {"count": 15},
},
)
def test_create_benchmark_run_handles_db_error(self):
"""Test that create_benchmark_run handles database errors."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
import pytest
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_session.commit.side_effect = Exception("Database error")
mock_get_session.return_value = mock_session
with pytest.raises(Exception, match="Database error"):
service.create_benchmark_run(
run_name="Test",
search_config={},
evaluation_config={},
datasets_config={"simpleqa": {"count": 5}},
)
mock_session.rollback.assert_called_once()
class TestBenchmarkServiceStartBenchmark:
"""Tests for start_benchmark functionality."""
def test_start_benchmark_creates_thread(self):
"""Test that start_benchmark creates a background thread."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
# Mock the benchmark run query
mock_run = Mock()
mock_run.id = 1
mock_run.config_hash = "abc12345"
mock_run.datasets_config = {"simpleqa": {"count": 2}}
mock_run.search_config = {}
mock_run.evaluation_config = {}
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
# Mock SettingsManager
with patch(
"local_deep_research.settings.SettingsManager"
) as mock_settings_mgr:
mock_settings_mgr.return_value.get_all_settings.return_value = {}
# Mock flask session
with patch(
"flask.session",
{"session_id": "test-session"},
):
with patch(
"local_deep_research.database.session_passwords.session_password_store"
) as mock_password_store:
mock_password_store.get_session_password.return_value = "test-password"
# Mock the thread execution
with patch.object(
service,
"_run_benchmark_thread",
return_value=None,
):
result = service.start_benchmark(
1, username="testuser", user_password="test"
)
assert result is True
assert 1 in service.active_runs
assert service.active_runs[1]["status"] == "running"
def test_start_benchmark_stores_data_in_memory(self):
"""Test that start_benchmark stores benchmark data in memory."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.id = 1
mock_run.config_hash = "abc12345"
mock_run.datasets_config = {"simpleqa": {"count": 2}}
mock_run.search_config = {"iterations": 2}
mock_run.evaluation_config = {"model_name": "test"}
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
with patch(
"local_deep_research.settings.SettingsManager"
) as mock_settings_mgr:
mock_settings_mgr.return_value.get_all_settings.return_value = {
"key": "value"
}
with patch(
"flask.session",
{"session_id": "test-session"},
):
with patch(
"local_deep_research.database.session_passwords.session_password_store"
):
with patch.object(
service,
"_run_benchmark_thread",
return_value=None,
):
service.start_benchmark(1, username="testuser")
assert "data" in service.active_runs[1]
assert (
service.active_runs[1]["data"][
"benchmark_run_id"
]
== 1
)
assert (
service.active_runs[1]["data"]["username"]
== "testuser"
)
def test_start_benchmark_handles_not_found(self):
"""Test that start_benchmark handles benchmark not found."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
# Return None for the benchmark run
mock_session.query.return_value.filter.return_value.first.return_value = None
result = service.start_benchmark(999, username="testuser")
assert result is False
def test_start_benchmark_cleans_up_active_runs_on_spawn_failure(self):
"""If Thread.start raises after active_runs was populated, the stale
entry must be removed so later cancel/status calls don't see a
thread-less zombie run."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
from local_deep_research.database.models import BenchmarkStatus
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.id = 42
mock_run.config_hash = "hash42"
mock_run.datasets_config = {}
mock_run.search_config = {}
mock_run.evaluation_config = {}
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
with patch("local_deep_research.settings.SettingsManager") as sm:
sm.return_value.get_all_settings.return_value = {}
with (
patch("flask.session", {"session_id": "sid"}),
patch(
"local_deep_research.database.session_passwords.session_password_store"
),
patch("threading.Thread") as mock_thread_cls,
):
mock_thread = Mock()
mock_thread.start.side_effect = RuntimeError(
"thread spawn failed"
)
mock_thread_cls.return_value = mock_thread
result = service.start_benchmark(
42, username="testuser", user_password="pw"
)
assert result is False
# active_runs must not retain the zombie entry.
assert 42 not in service.active_runs
# BenchmarkRun.status was flipped to FAILED by the error handler.
assert mock_run.status == BenchmarkStatus.FAILED
class TestBenchmarkServiceProcessTask:
"""Tests for _process_benchmark_task functionality."""
def test_process_benchmark_task_success(self):
"""Test successful processing of a benchmark task."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "What is 2+2?",
"correct_answer": "4",
"query_hash": "hash123",
"task_index": 0,
}
search_config = {"iterations": 1}
evaluation_config = {}
with patch(
"local_deep_research.config.thread_settings.get_settings_context"
) as mock_get_ctx:
mock_ctx = Mock()
mock_ctx.snapshot = {}
mock_get_ctx.return_value = mock_ctx
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.format_query"
) as mock_format:
mock_format.return_value = "formatted query"
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.quick_summary"
) as mock_summary:
mock_summary.return_value = {
"summary": "The answer is 4.",
"sources": [],
}
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.extract_answer_from_response"
) as mock_extract:
mock_extract.return_value = {
"extracted_answer": "4",
"confidence": "100",
}
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.grade_single_result"
) as mock_grade:
mock_grade.return_value = {
"is_correct": True,
"graded_confidence": "100",
"grader_response": "Correct!",
}
result = service._process_benchmark_task(
task, search_config, evaluation_config
)
assert result["response"] == "The answer is 4."
assert result["is_correct"] is True
assert result["query_hash"] == "hash123"
def test_process_benchmark_task_handles_research_error(self):
"""Test handling of research errors in task processing."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "What is 2+2?",
"correct_answer": "4",
"query_hash": "hash123",
"task_index": 0,
}
with patch(
"local_deep_research.config.thread_settings.get_settings_context"
) as mock_get_ctx:
mock_ctx = Mock()
mock_ctx.snapshot = {}
mock_get_ctx.return_value = mock_ctx
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.format_query"
) as mock_format:
mock_format.return_value = "formatted query"
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.quick_summary"
) as mock_summary:
mock_summary.side_effect = Exception("Research failed")
result = service._process_benchmark_task(task, {}, {})
assert "research_error" in result
assert "Research failed" in result["research_error"]
def test_process_benchmark_task_handles_evaluation_error(self):
"""Test handling of evaluation errors in task processing."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "What is 2+2?",
"correct_answer": "4",
"query_hash": "hash123",
"task_index": 0,
}
with patch(
"local_deep_research.config.thread_settings.get_settings_context"
) as mock_get_ctx:
mock_ctx = Mock()
mock_ctx.snapshot = {}
mock_get_ctx.return_value = mock_ctx
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.format_query"
) as mock_format:
mock_format.return_value = "formatted query"
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.quick_summary"
) as mock_summary:
mock_summary.return_value = {
"summary": "Answer",
"sources": [],
}
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.extract_answer_from_response"
) as mock_extract:
mock_extract.return_value = {
"extracted_answer": "4",
"confidence": "100",
}
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.grade_single_result"
) as mock_grade:
mock_grade.side_effect = Exception("Grading failed")
result = service._process_benchmark_task(
task, {}, {}
)
assert result["is_correct"] is None
assert "evaluation_error" in result
class TestBenchmarkServiceGetBenchmarkStatus:
"""Tests for get_benchmark_status functionality."""
def test_get_benchmark_status_returns_none_for_unknown(self):
"""Test that get_benchmark_status returns None for unknown run."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_session.query.return_value.filter.return_value.first.return_value = None
result = service.get_benchmark_status(999)
assert result is None
def test_get_benchmark_status_method_exists(self):
"""Test that get_benchmark_status method exists and is callable."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
assert hasattr(service, "get_benchmark_status")
assert callable(service.get_benchmark_status)
def test_get_benchmark_status_returns_dict_or_none(self):
"""Test that get_benchmark_status returns dict or None."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
import inspect
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
# Verify function signature
sig = inspect.signature(service.get_benchmark_status)
params = list(sig.parameters.keys())
assert "benchmark_run_id" in params
def test_get_benchmark_status_does_not_leak_raw_error(self):
"""A failed run's raw error_message (which can carry server-level LLM
endpoints / filesystem paths) must NOT be returned by
get_benchmark_status — it is replaced with a generic message (CWE-209).
"""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
BenchmarkStatus,
_GENERIC_BENCHMARK_ERROR,
)
secret = "Connection refused to http://internal-llm:8000/v1 /srv/secret"
mock_run = Mock()
mock_run.id = 1
mock_run.run_name = "run"
mock_run.status = BenchmarkStatus.FAILED
mock_run.completed_examples = 0
mock_run.total_examples = 10
mock_run.failed_examples = 1
mock_run.overall_accuracy = None
mock_run.processing_rate = None
mock_run.created_at = None
mock_run.start_time = None # skips the timing-calculation block
mock_run.end_time = None
mock_run.error_message = secret
service = BenchmarkService(socket_service=Mock())
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
q = mock_session.query.return_value
q.filter.return_value.first.return_value = mock_run
q.filter.return_value.all.return_value = []
q.filter.return_value.filter.return_value.all.return_value = []
result = service.get_benchmark_status(1)
assert result is not None
assert result["error_message"] == _GENERIC_BENCHMARK_ERROR
assert secret not in str(result)
assert "internal-llm" not in str(result)
class TestBenchmarkServiceTaskQueue:
"""Tests for task queue creation."""
def test_create_task_queue_creates_tasks(self):
"""Test that _create_task_queue creates tasks correctly."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
datasets_config = {"simpleqa": {"count": 3}}
# Mock load_dataset
with patch(
"local_deep_research.benchmarks.web_api.benchmark_service.load_dataset"
) as mock_load:
mock_load.return_value = [
{"id": "1", "problem": "Q1", "answer": "A1"},
{"id": "2", "problem": "Q2", "answer": "A2"},
{"id": "3", "problem": "Q3", "answer": "A3"},
]
tasks = service._create_task_queue(
datasets_config=datasets_config,
benchmark_run_id=1,
)
assert len(tasks) == 3
assert tasks[0]["question"] == "Q1"
assert tasks[0]["correct_answer"] == "A1"
assert tasks[0]["benchmark_run_id"] == 1
class TestBenchmarkServiceUpdateStatus:
"""Tests for update_benchmark_status functionality."""
def test_update_benchmark_status_updates_db(self):
"""Test that update_benchmark_status updates the database."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
from local_deep_research.database.models.benchmark import (
BenchmarkStatus,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.status = BenchmarkStatus.PENDING
mock_run.start_time = None
mock_run.end_time = None
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
service.update_benchmark_status(
1, BenchmarkStatus.IN_PROGRESS, username="testuser"
)
assert mock_run.status == BenchmarkStatus.IN_PROGRESS
mock_session.commit.assert_called_once()
def test_update_benchmark_status_sets_start_time(self):
"""Test that start_time is set when transitioning to IN_PROGRESS."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
from local_deep_research.database.models.benchmark import (
BenchmarkStatus,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.status = BenchmarkStatus.PENDING
mock_run.start_time = None
mock_run.end_time = None
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
service.update_benchmark_status(1, BenchmarkStatus.IN_PROGRESS)
assert mock_run.start_time is not None
def test_update_benchmark_status_sets_end_time_on_completion(self):
"""Test that end_time is set when transitioning to COMPLETED."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
from local_deep_research.database.models.benchmark import (
BenchmarkStatus,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.status = BenchmarkStatus.IN_PROGRESS
mock_run.start_time = datetime.now(UTC)
mock_run.end_time = None
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
service.update_benchmark_status(1, BenchmarkStatus.COMPLETED)
assert mock_run.end_time is not None
def test_update_benchmark_status_stores_error_message(self):
"""Test that error message is stored when provided."""
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
from local_deep_research.database.models.benchmark import (
BenchmarkStatus,
)
mock_socket = Mock()
service = BenchmarkService(socket_service=mock_socket)
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = Mock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
mock_run = Mock()
mock_run.status = BenchmarkStatus.IN_PROGRESS
mock_run.start_time = datetime.now(UTC)
mock_run.end_time = None
mock_run.error_message = None
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
service.update_benchmark_status(
1,
BenchmarkStatus.FAILED,
error_message="Test error",
)
assert mock_run.error_message == "Test error"
class TestSessionRollbackExceptionHandling:
"""Tests for session rollback exception handling (PR #2029).
PR #2029 changed bare `except:` to `except Exception:` in the
session.rollback() error handler in sync_pending_results.
"""
def test_rollback_exception_is_caught(self):
"""Exception during rollback is caught and doesn't propagate."""
mock_session = Mock()
mock_session.rollback.side_effect = Exception("Rollback failed")
# Simulate the pattern from sync_pending_results
caught = False
try:
mock_session.rollback()
except Exception:
caught = True
assert caught
def test_rollback_success_no_exception(self):
"""Successful rollback raises no exception."""
mock_session = Mock()
mock_session.rollback.return_value = None
error = None
try:
mock_session.rollback()
except Exception as e:
error = e
assert error is None