Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

1801 lines
61 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Comprehensive coverage tests for benchmarks/web_api/benchmark_service.py.
Focuses on uncovered code paths: complex branching in _run_benchmark_thread,
_process_benchmark_task, _sync_results_to_database, get_benchmark_status,
sync_pending_results, cancel_benchmark, update_benchmark_status, and more.
"""
import json
from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock, Mock, patch
import pytest
# ---------------------------------------------------------------------------
# Helpers to import from the module under test
# ---------------------------------------------------------------------------
MODULE = "local_deep_research.benchmarks.web_api.benchmark_service"
SETTINGS_CTX_MODULE = "local_deep_research.config.thread_settings"
def _import_service():
"""Import BenchmarkService with SocketIOService mocked."""
with patch(f"{MODULE}.SocketIOService"):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
return BenchmarkService
def _make_service(socket=None):
"""Create a BenchmarkService with a mock socket service."""
cls = _import_service()
svc = cls(socket_service=socket or MagicMock())
return svc
# ============================================================
# BenchmarkQueueTracker deeper coverage
# ============================================================
class TestQueueTrackerCleanup:
"""Cover cleanup_completed_tasks edge cases."""
def test_cleanup_removes_old_completed(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("old1", "user")
tracker.update_task_status("old1", BenchmarkTaskStatus.COMPLETED)
# Manually set updated_at to the past
tracker.tasks["old1"]["updated_at"] = datetime.now(UTC) - timedelta(
seconds=7200
)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "old1" not in tracker.tasks
def test_cleanup_keeps_recent_completed(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("new1", "user")
tracker.update_task_status("new1", BenchmarkTaskStatus.COMPLETED)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "new1" in tracker.tasks
def test_cleanup_removes_old_failed(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("fail1", "user")
tracker.update_task_status("fail1", BenchmarkTaskStatus.FAILED)
tracker.tasks["fail1"]["updated_at"] = datetime.now(UTC) - timedelta(
seconds=7200
)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "fail1" not in tracker.tasks
def test_cleanup_removes_old_cancelled(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("canc1", "user")
tracker.update_task_status("canc1", BenchmarkTaskStatus.CANCELLED)
tracker.tasks["canc1"]["updated_at"] = datetime.now(UTC) - timedelta(
seconds=7200
)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "canc1" not in tracker.tasks
def test_cleanup_keeps_processing_tasks(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("proc1", "user")
tracker.update_task_status("proc1", BenchmarkTaskStatus.PROCESSING)
tracker.tasks["proc1"]["updated_at"] = datetime.now(UTC) - timedelta(
seconds=7200
)
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "proc1" in tracker.tasks
def test_cleanup_uses_created_at_when_no_updated_at(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("t1", "user")
# Set status directly without going through update (no updated_at)
tracker.tasks["t1"]["status"] = BenchmarkTaskStatus.COMPLETED.value
tracker.tasks["t1"]["created_at"] = datetime.now(UTC) - timedelta(
seconds=7200
)
if "updated_at" in tracker.tasks["t1"]:
del tracker.tasks["t1"]["updated_at"]
tracker.cleanup_completed_tasks(max_age_seconds=3600)
assert "t1" not in tracker.tasks
def test_update_nonexistent_task_logs_warning(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
BenchmarkTaskStatus,
)
tracker = BenchmarkQueueTracker()
# Should not raise; logs a warning
tracker.update_task_status("nonexistent", BenchmarkTaskStatus.COMPLETED)
def test_remove_task(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
)
tracker = BenchmarkQueueTracker()
tracker.add_task("rm1", "user")
tracker.remove_task("rm1")
assert tracker.get_task_status("rm1") is None
def test_remove_nonexistent_task(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkQueueTracker,
)
tracker = BenchmarkQueueTracker()
# Should not raise
tracker.remove_task("doesnotexist")
# ============================================================
# BenchmarkService initialization
# ============================================================
class TestBenchmarkServiceInit:
def test_init_with_provided_socket(self):
mock_socket = MagicMock()
svc = _make_service(socket=mock_socket)
assert svc.socket_service is mock_socket
def test_init_fallback_socket(self):
"""When SocketIOService raises, a MockSocketService is created."""
with patch(
f"{MODULE}.SocketIOService", side_effect=RuntimeError("no app")
):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkService,
)
svc = BenchmarkService()
# The mock socket should have emit_to_room method
svc.socket_service.emit_to_room("room", "event", {})
# ============================================================
# generate_config_hash / generate_query_hash
# ============================================================
class TestHashGeneration:
def test_config_hash_deterministic(self):
svc = _make_service()
cfg = {"iterations": 5, "search_tool": "searxng", "model_name": "gpt-4"}
h1 = svc.generate_config_hash(cfg)
h2 = svc.generate_config_hash(cfg)
assert h1 == h2
assert len(h1) == 8
def test_config_hash_ignores_none_values(self):
svc = _make_service()
cfg1 = {"iterations": 5, "search_tool": None}
cfg2 = {"iterations": 5}
assert svc.generate_config_hash(cfg1) == svc.generate_config_hash(cfg2)
def test_config_hash_differs_for_different_configs(self):
svc = _make_service()
h1 = svc.generate_config_hash({"iterations": 5})
h2 = svc.generate_config_hash({"iterations": 10})
assert h1 != h2
def test_query_hash_deterministic(self):
svc = _make_service()
h1 = svc.generate_query_hash("What is AI?", "simpleqa")
h2 = svc.generate_query_hash("What is AI?", "simpleqa")
assert h1 == h2
def test_query_hash_strips_whitespace(self):
svc = _make_service()
h1 = svc.generate_query_hash(" What is AI? ", "simpleqa")
h2 = svc.generate_query_hash("What is AI?", "simpleqa")
assert h1 == h2
def test_query_hash_case_insensitive_dataset(self):
svc = _make_service()
h1 = svc.generate_query_hash("What is AI?", "SimpleQA")
h2 = svc.generate_query_hash("What is AI?", "simpleqa")
assert h1 == h2
# ============================================================
# create_benchmark_run
# ============================================================
class TestCreateBenchmarkRun:
@patch(f"{MODULE}.BenchmarkRun")
def test_create_benchmark_run_success(self, mock_run_cls):
svc = _make_service()
mock_session = MagicMock()
mock_run_instance = MagicMock()
mock_run_instance.id = 42
mock_run_cls.return_value = mock_run_instance
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
result = svc.create_benchmark_run(
run_name="test",
search_config={"iterations": 5},
evaluation_config={},
datasets_config={"simpleqa": {"count": 10}},
username="user1",
)
assert result == 42
mock_session.add.assert_called_once_with(mock_run_instance)
mock_session.commit.assert_called_once()
@patch(f"{MODULE}.BenchmarkRun")
def test_create_benchmark_run_db_error(self, mock_run_cls):
svc = _make_service()
mock_session = MagicMock()
mock_session.commit.side_effect = RuntimeError("db error")
mock_run_cls.return_value = MagicMock()
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
with pytest.raises(RuntimeError, match="db error"):
svc.create_benchmark_run(
run_name="test",
search_config={},
evaluation_config={},
datasets_config={},
)
mock_session.rollback.assert_called_once()
# ============================================================
# _create_task_queue
# ============================================================
class TestCreateTaskQueue:
@patch(f"{MODULE}.load_dataset")
def test_creates_tasks_from_dataset(self, mock_load):
svc = _make_service()
mock_load.return_value = [
{"id": "ex1", "problem": "Q1?", "answer": "A1"},
{"id": "ex2", "problem": "Q2?", "answer": "A2"},
]
tasks = svc._create_task_queue(
{"simpleqa": {"count": 2}},
benchmark_run_id=1,
)
assert len(tasks) == 2
assert tasks[0]["question"] == "Q1?"
assert tasks[0]["dataset_type"] == "simpleqa"
@patch(f"{MODULE}.load_dataset")
def test_passes_seed_from_dataset_config(self, mock_load):
"""A "seed" in the dataset config drives reproducible sampling."""
svc = _make_service()
mock_load.return_value = [
{"id": "ex1", "problem": "Q1?", "answer": "A1"},
]
svc._create_task_queue(
{"simpleqa": {"count": 1, "seed": 7}},
benchmark_run_id=1,
)
mock_load.assert_called_once_with(
dataset_type="simpleqa", num_examples=1, seed=7
)
@patch(f"{MODULE}.load_dataset")
def test_no_seed_means_random_sampling(self, mock_load):
"""Without a configured seed the sample stays random (seed=None)."""
svc = _make_service()
mock_load.return_value = [
{"id": "ex1", "problem": "Q1?", "answer": "A1"},
]
svc._create_task_queue(
{"simpleqa": {"count": 1}},
benchmark_run_id=1,
)
mock_load.assert_called_once_with(
dataset_type="simpleqa", num_examples=1, seed=None
)
@patch(f"{MODULE}.load_dataset")
def test_queues_every_sampled_question(self, mock_load):
"""Regression test for #4498: every sampled question becomes a task.
The removed cross-run reuse feature silently skipped questions that
had results in previous compatible runs, which broke the
completed/total accounting (#4451). A run must always process its
full sample, even if identical questions were answered before."""
svc = _make_service()
examples = [
{"id": f"ex{i}", "problem": f"Q{i}?", "answer": f"A{i}"}
for i in range(5)
]
mock_load.return_value = examples
tasks = svc._create_task_queue(
{"simpleqa": {"count": 5}},
benchmark_run_id=1,
)
assert len(tasks) == len(examples)
assert [t["question"] for t in tasks] == [
e["problem"] for e in examples
]
assert [t["task_index"] for t in tasks] == list(range(5))
@patch(f"{MODULE}.load_dataset")
def test_skips_zero_count_datasets(self, mock_load):
svc = _make_service()
tasks = svc._create_task_queue(
{"simpleqa": {"count": 0}},
benchmark_run_id=1,
)
assert len(tasks) == 0
mock_load.assert_not_called()
@patch(f"{MODULE}.load_dataset")
def test_browsecomp_dataset_type(self, mock_load):
svc = _make_service()
mock_load.return_value = [
{"id": "b1", "problem": "Browse Q?", "answer": "Browse A"},
]
tasks = svc._create_task_queue(
{"browsecomp": {"count": 1}},
benchmark_run_id=1,
)
assert len(tasks) == 1
assert tasks[0]["dataset_type"] == "browsecomp"
@patch(f"{MODULE}.load_dataset")
def test_example_without_id_gets_default(self, mock_load):
svc = _make_service()
mock_load.return_value = [
{"problem": "Q?", "answer": "A"}, # no id
]
tasks = svc._create_task_queue(
{"simpleqa": {"count": 1}},
benchmark_run_id=1,
)
assert tasks[0]["example_id"] == "example_0"
# ============================================================
# _process_benchmark_task
# ============================================================
class TestProcessBenchmarkTask:
def _make_task(self, **overrides):
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "What is 2+2?",
"correct_answer": "4",
"query_hash": "abc123",
"task_index": 0,
"username": "user1",
"user_password": None,
}
task.update(overrides)
return task
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_successful_processing_with_grading(
self, mock_format, mock_summary, mock_extract, mock_grade
):
svc = _make_service()
mock_format.return_value = "formatted query"
mock_summary.return_value = {
"summary": "The answer is 4",
"sources": [{"url": "http://example.com"}],
}
mock_extract.return_value = {
"extracted_answer": "4",
"confidence": "95",
}
mock_grade.return_value = {
"is_correct": True,
"graded_confidence": "98",
"grader_response": "Correct answer",
}
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(
self._make_task(),
{"iterations": 5},
{},
)
assert result["is_correct"] is True
assert result["extracted_answer"] == "4"
assert result["response"] == "The answer is 4"
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_grading_error_in_result(
self, mock_format, mock_summary, mock_extract, mock_grade
):
svc = _make_service()
mock_format.return_value = "q"
mock_summary.return_value = {"summary": "resp", "sources": []}
mock_extract.return_value = {
"extracted_answer": "ans",
"confidence": "50",
}
mock_grade.return_value = {"grading_error": "model unavailable"}
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
assert result["is_correct"] is None
assert "model unavailable" in result["grader_response"]
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_grading_returns_none(
self, mock_format, mock_summary, mock_extract, mock_grade
):
svc = _make_service()
mock_format.return_value = "q"
mock_summary.return_value = {"summary": "resp", "sources": []}
mock_extract.return_value = {"extracted_answer": "ans"}
mock_grade.return_value = None
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
assert result["is_correct"] is None
assert "No evaluation results returned" in result["grader_response"]
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_grading_exception(
self, mock_format, mock_summary, mock_extract, mock_grade
):
svc = _make_service()
mock_format.return_value = "q"
mock_summary.return_value = {"summary": "resp", "sources": []}
mock_extract.return_value = {"extracted_answer": "ans"}
mock_grade.side_effect = ValueError("grade fail")
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
assert result["is_correct"] is None
assert "grade fail" in result["grader_response"]
@patch(f"{MODULE}.format_query")
def test_research_error(self, mock_format):
svc = _make_service()
mock_format.side_effect = RuntimeError("research crashed")
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
assert "research_error" in result
assert "research crashed" in result["research_error"]
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_extract_returns_string(
self, mock_format, mock_summary, mock_extract, mock_grade
):
"""When extract_answer_from_response returns a string instead of dict."""
svc = _make_service()
mock_format.return_value = "q"
mock_summary.return_value = {"summary": "resp", "sources": []}
mock_extract.return_value = "plain string answer"
mock_grade.return_value = {
"is_correct": False,
"graded_confidence": "10",
"grader_response": "Wrong",
}
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
assert result["extracted_answer"] == "plain string answer"
assert result["confidence"] == "100"
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_sources_from_all_links(
self, mock_format, mock_summary, mock_extract, mock_grade
):
"""When sources is empty but all_links_of_system is present."""
svc = _make_service()
mock_format.return_value = "q"
mock_summary.return_value = {
"summary": "resp",
"sources": [],
"all_links_of_system": ["http://link1.com"],
}
mock_extract.return_value = {"extracted_answer": "ans"}
mock_grade.return_value = {
"is_correct": True,
"graded_confidence": "90",
"grader_response": "ok",
}
mock_settings = MagicMock()
mock_settings.snapshot = {}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
result = svc._process_benchmark_task(self._make_task(), {}, {})
sources = json.loads(result["sources"])
assert "http://link1.com" in sources
# ============================================================
# _send_progress_update
# ============================================================
class TestSendProgressUpdate:
def test_sends_progress_via_socket(self):
mock_socket = MagicMock()
svc = _make_service(socket=mock_socket)
svc._send_progress_update(1, 5, 10)
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][2]["progress"] == 50.0
def test_sends_zero_progress_when_total_zero(self):
mock_socket = MagicMock()
svc = _make_service(socket=mock_socket)
svc._send_progress_update(1, 0, 0)
call_args = mock_socket.emit_to_subscribers.call_args
assert call_args[0][2]["progress"] == 0
def test_exception_in_send_is_caught(self):
mock_socket = MagicMock()
mock_socket.emit_to_subscribers.side_effect = RuntimeError(
"socket error"
)
svc = _make_service(socket=mock_socket)
# Should not raise
svc._send_progress_update(1, 5, 10)
# ============================================================
# sync_pending_results
# ============================================================
class TestSyncPendingResults:
def test_returns_zero_when_no_active_run(self):
svc = _make_service()
assert svc.sync_pending_results(999) == 0
def test_saves_new_results(self):
svc = _make_service()
svc.active_runs[1] = {
"data": {"username": "user1", "user_password": None},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"task_index": 0,
},
],
}
mock_session = MagicMock()
# No rows persisted for this run yet
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
count = svc.sync_pending_results(1, "user1")
assert count == 1
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
def test_skips_already_saved_indices(self):
svc = _make_service()
svc.active_runs[1] = {
"data": {"username": "user1", "user_password": None},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"task_index": 0,
},
],
"saved_indices": {0},
}
mock_session = MagicMock()
# No rows persisted for this run yet; idx 0 is skipped via saved_indices.
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
count = svc.sync_pending_results(1)
assert count == 0
def test_skips_existing_db_result(self):
svc = _make_service()
svc.active_runs[1] = {
"data": {"username": "user1", "user_password": None},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"task_index": 0,
},
],
}
mock_session = MagicMock()
# query_hash "h1" already persisted for this run
mock_session.query.return_value.filter.return_value.all.return_value = [
("h1",)
]
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
count = svc.sync_pending_results(1)
assert count == 0
mock_session.add.assert_not_called()
def test_handles_db_error(self):
svc = _make_service()
svc.active_runs[1] = {
"data": {"username": "user1", "user_password": None},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"task_index": 0,
},
],
}
mock_session = MagicMock()
mock_session.query.side_effect = RuntimeError("db error")
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
count = svc.sync_pending_results(1)
assert count == 0
def test_uses_username_from_run_data(self):
svc = _make_service()
svc.active_runs[1] = {
"data": {"username": "fromdata", "user_password": "pw"},
"results": [],
}
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_session = MagicMock()
mock_session.query.return_value.filter.return_value.all.return_value = []
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc.sync_pending_results(1)
mock_get_session.assert_called_once_with("fromdata", "pw")
# ============================================================
# _sync_results_to_database
# ============================================================
class TestSyncResultsToDatabase:
def test_returns_early_if_no_active_run(self):
svc = _make_service()
# Should not raise
svc._sync_results_to_database(999)
def test_returns_early_if_thread_not_complete(self):
svc = _make_service()
svc.active_runs[1] = {"thread_complete": False}
svc._sync_results_to_database(1)
# No DB calls expected
def test_syncs_completed_run(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
svc.active_runs[1] = {
"thread_complete": True,
"data": {"username": "user1", "user_password": None},
"completion_info": {
"status": BenchmarkStatus.COMPLETED,
"end_time": datetime.now(UTC),
"completed_examples": 2,
"failed_examples": 0,
},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"is_correct": True,
"processing_time": 10.0,
"task_index": 0,
},
{
"example_id": "ex2",
"query_hash": "h2",
"dataset_type": "simpleqa",
"question": "Q2?",
"correct_answer": "A2",
"is_correct": False,
"processing_time": 20.0,
"task_index": 1,
},
],
}
mock_session = MagicMock()
mock_benchmark_run = MagicMock()
mock_benchmark_run.status = BenchmarkStatus.COMPLETED
mock_session.query.return_value.filter.return_value.first.return_value = mock_benchmark_run
# _persist_unsaved_results reads existing query_hashes for the run.
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc._sync_results_to_database(1)
mock_session.commit.assert_called_once()
# Active run should be cleaned up
assert 1 not in svc.active_runs
def test_calculates_accuracy_correctly(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
svc.active_runs[1] = {
"thread_complete": True,
"data": {"username": "user1", "user_password": None},
"completion_info": {
"status": BenchmarkStatus.COMPLETED,
"completed_examples": 2,
"failed_examples": 0,
},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"is_correct": True,
"processing_time": 10.0,
"task_index": 0,
},
{
"example_id": "ex2",
"query_hash": "h2",
"dataset_type": "simpleqa",
"question": "Q2?",
"correct_answer": "A2",
"is_correct": True,
"processing_time": 20.0,
"task_index": 1,
},
],
}
mock_session = MagicMock()
mock_benchmark_run = MagicMock()
mock_benchmark_run.status = BenchmarkStatus.COMPLETED
mock_session.query.return_value.filter.return_value.first.return_value = mock_benchmark_run
# _persist_unsaved_results reads existing query_hashes for the run.
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc._sync_results_to_database(1)
# 2 correct out of 2 = 100%
assert mock_benchmark_run.overall_accuracy == 100.0
def test_skips_already_saved_results(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
svc.active_runs[1] = {
"thread_complete": True,
"data": {"username": "user1", "user_password": None},
"completion_info": {
"status": BenchmarkStatus.FAILED,
"error_message": "something failed",
},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"task_index": 0,
},
],
"saved_indices": {0},
}
mock_session = MagicMock()
mock_benchmark_run = MagicMock()
mock_session.query.return_value.filter.return_value.first.return_value = mock_benchmark_run
# _persist_unsaved_results reads existing query_hashes for the run.
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc._sync_results_to_database(1)
# Only the run status update, no result adds
assert mock_session.add.call_count == 0
def test_handles_db_exception(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
svc.active_runs[1] = {
"thread_complete": True,
"data": {"username": "user1", "user_password": None},
"completion_info": {
"status": BenchmarkStatus.COMPLETED,
},
"results": [],
}
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
side_effect=RuntimeError("db down")
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
# Should not raise
svc._sync_results_to_database(1)
def test_no_accuracy_for_non_completed_status(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
svc.active_runs[1] = {
"thread_complete": True,
"data": {"username": "user1", "user_password": None},
"completion_info": {
"status": BenchmarkStatus.FAILED,
"error_message": "crashed",
},
"results": [
{
"example_id": "ex1",
"query_hash": "h1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"is_correct": True,
"processing_time": 10.0,
"task_index": 0,
},
],
}
mock_session = MagicMock()
mock_benchmark_run = MagicMock()
mock_benchmark_run.status = BenchmarkStatus.FAILED
mock_session.query.return_value.filter.return_value.first.return_value = mock_benchmark_run
# _persist_unsaved_results reads existing query_hashes for the run.
mock_session.query.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc._sync_results_to_database(1)
# Should NOT set overall_accuracy for failed runs
# (the code only calculates accuracy when status == COMPLETED)
# The mock starts with whatever default it has; we just verify
# the condition was checked by checking the status was set to FAILED
assert mock_benchmark_run.status == BenchmarkStatus.FAILED
# ============================================================
# _calculate_final_accuracy
# ============================================================
class TestCalculateFinalAccuracy:
def test_calculates_accuracy(self):
svc = _make_service()
mock_session = MagicMock()
mock_r1 = MagicMock()
mock_r1.is_correct = True
mock_r1.processing_time = 30.0
mock_r2 = MagicMock()
mock_r2.is_correct = False
mock_r2.processing_time = 60.0
mock_session.query.return_value.filter.return_value.filter.return_value.all.return_value = [
mock_r1,
mock_r2,
]
mock_run = MagicMock()
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc._calculate_final_accuracy(1, "user1")
assert mock_run.overall_accuracy == 50.0
def test_no_results(self):
svc = _make_service()
mock_session = MagicMock()
mock_session.query.return_value.filter.return_value.filter.return_value.all.return_value = []
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
# Should not raise
svc._calculate_final_accuracy(1)
def test_handles_exception(self):
svc = _make_service()
mock_session = MagicMock()
mock_session.query.side_effect = RuntimeError("db error")
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
# Should not raise
svc._calculate_final_accuracy(1)
# ============================================================
# update_benchmark_status
# ============================================================
class TestUpdateBenchmarkStatus:
def test_update_status_in_progress(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_run = MagicMock()
mock_run.start_time = None
mock_run.end_time = None
mock_session.query.return_value.filter.return_value.first.return_value = mock_run
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc.update_benchmark_status(1, BenchmarkStatus.IN_PROGRESS)
assert mock_run.status == BenchmarkStatus.IN_PROGRESS
assert mock_run.start_time is not None
def test_update_status_completed(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_run = MagicMock()
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
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc.update_benchmark_status(1, BenchmarkStatus.COMPLETED)
assert mock_run.end_time is not None
def test_update_status_failed_sets_end_time(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_run = MagicMock()
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
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
svc.update_benchmark_status(
1, BenchmarkStatus.FAILED, error_message="oops"
)
assert mock_run.end_time is not None
assert mock_run.error_message == "oops"
def test_update_status_not_found(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_session.query.return_value.filter.return_value.first.return_value = None
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
# Should not raise
svc.update_benchmark_status(1, BenchmarkStatus.COMPLETED)
def test_update_status_db_error(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_session.query.side_effect = RuntimeError("db error")
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
# Should not raise
svc.update_benchmark_status(1, BenchmarkStatus.COMPLETED)
mock_session.rollback.assert_called_once()
# ============================================================
# cancel_benchmark
# ============================================================
class TestCancelBenchmark:
def test_cancel_active_run(self):
svc = _make_service()
svc.active_runs[1] = {"status": "running"}
with patch.object(svc, "update_benchmark_status"):
result = svc.cancel_benchmark(1, "user1")
assert result is True
assert svc.active_runs[1]["status"] == "cancelled"
def test_cancel_nonexistent_run(self):
svc = _make_service()
with patch.object(svc, "update_benchmark_status"):
result = svc.cancel_benchmark(999, "user1")
assert result is True
def test_cancel_exception(self):
svc = _make_service()
with patch.object(
svc, "update_benchmark_status", side_effect=RuntimeError("err")
):
result = svc.cancel_benchmark(1)
assert result is False
# ============================================================
# get_benchmark_status
# ============================================================
class TestGetBenchmarkStatus:
def test_returns_none_when_not_found(self):
svc = _make_service()
mock_session = MagicMock()
mock_session.query.return_value.filter.return_value.first.return_value = None
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
result = svc.get_benchmark_status(999)
assert result is None
def test_returns_status_with_running_accuracy(self):
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
svc = _make_service()
mock_session = MagicMock()
mock_run = MagicMock()
mock_run.id = 1
mock_run.run_name = "test run"
mock_run.status = BenchmarkStatus.IN_PROGRESS
mock_run.completed_examples = 2
mock_run.total_examples = 10
mock_run.failed_examples = 0
mock_run.overall_accuracy = None
mock_run.processing_rate = None
mock_run.config_hash = "abc123"
mock_run.created_at = datetime.now(UTC)
mock_run.start_time = datetime.now(UTC) - timedelta(seconds=60)
mock_run.end_time = None
mock_run.error_message = None
mock_r1 = MagicMock()
mock_r1.is_correct = True
mock_r1.dataset_type = MagicMock()
mock_r1.dataset_type.value = "simpleqa"
mock_r2 = MagicMock()
mock_r2.is_correct = False
mock_r2.dataset_type = MagicMock()
mock_r2.dataset_type.value = "simpleqa"
# Mock the chain of queries
# We need to handle multiple query() calls differently
call_count = {"n": 0}
def side_effect_query(*args):
call_count["n"] += 1
m = MagicMock()
if call_count["n"] == 1:
# benchmark_run query
m.filter.return_value.first.return_value = mock_run
elif call_count["n"] == 2:
# current_results query
m.filter.return_value.filter.return_value.all.return_value = [
mock_r1,
mock_r2,
]
elif call_count["n"] == 3:
# all_results_for_timing query
m.filter.return_value.all.return_value = [mock_r1, mock_r2]
return m
mock_session.query.side_effect = side_effect_query
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
result = svc.get_benchmark_status(1, "user1")
assert result is not None
assert result["running_accuracy"] == 50.0
assert "simpleqa_accuracy" in result
def test_handles_exception(self):
svc = _make_service()
mock_session = MagicMock()
mock_session.query.side_effect = RuntimeError("db error")
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
result = svc.get_benchmark_status(1)
assert result is None
# ============================================================
# _run_benchmark_thread
# ============================================================
class TestRunBenchmarkThread:
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_runs_tasks(self, mock_set_ctx, mock_semaphore):
svc = _make_service()
svc.active_runs[1] = {
"data": {
"username": "user1",
"user_password": None,
"config_hash": "abc",
"datasets_config": {"simpleqa": {"count": 1}},
"search_config": {"iterations": 1},
"evaluation_config": {},
"settings_snapshot": {},
},
"results": [],
}
mock_task_result = {"example_id": "ex1", "is_correct": True}
with (
patch.object(
svc,
"_create_task_queue",
return_value=[
{
"benchmark_run_id": 1,
"example_id": "ex1",
"task_index": 0,
}
],
),
patch.object(
svc, "_process_benchmark_task", return_value=mock_task_result
),
patch.object(svc, "_send_progress_update"),
patch.object(svc, "_sync_results_to_database"),
):
svc._run_benchmark_thread(1)
assert len(svc.active_runs[1]["results"]) == 1
assert svc.active_runs[1]["thread_complete"] is True
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_handles_cancelled_run(self, mock_set_ctx, mock_semaphore):
svc = _make_service()
svc.active_runs[1] = {
"data": {
"username": "user1",
"user_password": None,
"config_hash": "abc",
"datasets_config": {},
"search_config": {},
"evaluation_config": {},
"settings_snapshot": {},
},
"status": "cancelled",
"results": [],
}
with (
patch.object(
svc,
"_create_task_queue",
return_value=[
{
"benchmark_run_id": 1,
"example_id": "ex1",
"task_index": 0,
}
],
),
patch.object(svc, "_sync_results_to_database"),
):
svc._run_benchmark_thread(1)
info = svc.active_runs[1]["completion_info"]
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
assert info["status"] == BenchmarkStatus.CANCELLED
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_handles_task_error_rate_limit(
self, mock_set_ctx, mock_semaphore
):
svc = _make_service()
svc.active_runs[1] = {
"data": {
"username": "user1",
"user_password": None,
"config_hash": "abc",
"datasets_config": {},
"search_config": {},
"evaluation_config": {},
"settings_snapshot": {},
},
"results": [],
}
with (
patch.object(
svc,
"_create_task_queue",
return_value=[
{
"benchmark_run_id": 1,
"example_id": "ex1",
"task_index": 0,
}
],
),
patch.object(
svc,
"_process_benchmark_task",
side_effect=RuntimeError("403 Forbidden rate limit"),
),
patch.object(svc, "_sync_results_to_database"),
):
svc._run_benchmark_thread(1)
assert svc.rate_limit_detected.get(1) is True
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_handles_missing_data(self, mock_set_ctx, mock_semaphore):
svc = _make_service()
svc.active_runs[1] = {} # No "data" key
with patch.object(svc, "_sync_results_to_database"):
svc._run_benchmark_thread(1)
info = svc.active_runs[1]["completion_info"]
from local_deep_research.benchmarks.web_api.benchmark_service import (
BenchmarkStatus,
)
assert info["status"] == BenchmarkStatus.FAILED
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_settings_context_values(self, mock_set_ctx, mock_semaphore):
"""Test that SettingsContext correctly extracts values from setting objects."""
svc = _make_service()
svc.active_runs[1] = {
"data": {
"username": "user1",
"user_password": None,
"config_hash": "abc",
"datasets_config": {},
"search_config": {},
"evaluation_config": {},
"settings_snapshot": {
"key1": {"value": "val1"},
"key2": "direct_val",
},
},
"results": [],
}
captured_ctx = {}
def capture_ctx(ctx):
captured_ctx["ctx"] = ctx
mock_set_ctx.side_effect = capture_ctx
with (
patch.object(svc, "_create_task_queue", return_value=[]),
patch.object(svc, "_sync_results_to_database"),
):
svc._run_benchmark_thread(1)
ctx = captured_ctx["ctx"]
assert ctx.get_setting("key1") == "val1"
assert ctx.get_setting("key2") == "direct_val"
assert ctx.get_setting("nonexistent", "default") == "default"
@patch(f"{MODULE}._global_research_semaphore")
@patch(f"{SETTINGS_CTX_MODULE}.set_settings_context")
def test_thread_total_zero_examples_progress(
self, mock_set_ctx, mock_semaphore
):
"""Cover branch where total_examples is 0 for progress calculation."""
svc = _make_service()
svc.active_runs[1] = {
"data": {
"username": "user1",
"user_password": None,
"config_hash": "abc",
"datasets_config": {},
"search_config": {},
"evaluation_config": {},
"settings_snapshot": {},
},
"results": [],
}
with (
patch.object(svc, "_create_task_queue", return_value=[]),
patch.object(svc, "_sync_results_to_database"),
):
svc._run_benchmark_thread(1)
# Completion should still happen (progress = 0 since total = 0)
assert svc.active_runs[1]["thread_complete"] is True
# ============================================================
# start_benchmark
# ============================================================
class TestStartBenchmark:
def test_start_benchmark_run_not_found(self):
svc = _make_service()
mock_session = MagicMock()
mock_session.query.return_value.filter.return_value.first.return_value = None
with patch(
"local_deep_research.database.session_context.get_user_db_session"
) as mock_get_session:
mock_get_session.return_value.__enter__ = Mock(
return_value=mock_session
)
mock_get_session.return_value.__exit__ = Mock(return_value=False)
result = svc.start_benchmark(999, "user1")
assert result is False
# ============================================================
# Progress callback in _process_benchmark_task
# ============================================================
class TestProgressCallback:
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_callback_milestone_types(
self, mock_format, mock_summary, mock_extract, mock_grade
):
"""Test that the progress callback categorizes log types correctly."""
mock_socket = MagicMock()
svc = _make_service(socket=mock_socket)
def capture_quick_summary(**kwargs):
cb = kwargs.get("progress_callback")
if cb:
# Test various status strings
cb("Starting phase", 10, {"phase": "init"})
cb("Completed search", 50, {})
cb("Error occurred", 80, {})
cb("Processing data", 60, {})
return {"summary": "result", "sources": []}
mock_format.return_value = "q"
mock_summary.side_effect = capture_quick_summary
mock_extract.return_value = {"extracted_answer": "ans"}
mock_grade.return_value = {
"is_correct": True,
"graded_confidence": "90",
"grader_response": "ok",
}
mock_settings = MagicMock()
mock_settings.snapshot = {}
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"query_hash": "h1",
"task_index": 0,
"username": "user1",
"user_password": None,
}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
svc._process_benchmark_task(task, {}, {})
# Verify socket was called multiple times (from callback + regular calls)
assert mock_socket.emit_to_subscribers.call_count >= 4
@patch(f"{MODULE}.grade_single_result")
@patch(f"{MODULE}.extract_answer_from_response")
@patch(f"{MODULE}.quick_summary")
@patch(f"{MODULE}.format_query")
def test_callback_handles_exception(
self, mock_format, mock_summary, mock_extract, mock_grade
):
"""Test that exceptions in the progress callback are caught."""
mock_socket = MagicMock()
mock_socket.emit_to_subscribers.side_effect = RuntimeError(
"socket dead"
)
svc = _make_service(socket=mock_socket)
def trigger_callback(**kwargs):
cb = kwargs.get("progress_callback")
if cb:
cb("test status", 50, {}) # Should not raise
return {"summary": "result", "sources": []}
mock_format.return_value = "q"
mock_summary.side_effect = trigger_callback
mock_extract.return_value = {"extracted_answer": "ans"}
mock_grade.return_value = {
"is_correct": True,
"graded_confidence": "90",
"grader_response": "ok",
}
mock_settings = MagicMock()
mock_settings.snapshot = {}
task = {
"benchmark_run_id": 1,
"example_id": "ex1",
"dataset_type": "simpleqa",
"question": "Q?",
"correct_answer": "A",
"query_hash": "h1",
"task_index": 0,
"username": "user1",
"user_password": None,
}
with patch(
f"{SETTINGS_CTX_MODULE}.get_settings_context",
return_value=mock_settings,
):
# Should not raise despite socket error in callback
result = svc._process_benchmark_task(task, {}, {})
assert result is not None