Files
learningcircuit--local-deep…/tests/web/queue/test_processor_v2.py
T
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

416 lines
13 KiB
Python

"""
Tests for the QueueProcessorV2 class.
Tests cover:
- Initialization
- Starting and stopping
- User activity notification
- Research queuing
- Completion notifications
- Error handling
"""
import threading
from unittest.mock import Mock, MagicMock, patch
class TestQueueProcessorV2Init:
"""Tests for QueueProcessorV2 initialization."""
def test_init_default_interval(self):
"""Initializes with default check interval."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
assert processor.check_interval == 10
assert processor.running is False
assert processor.thread is None
def test_init_custom_interval(self):
"""Initializes with custom check interval."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2(check_interval=30)
assert processor.check_interval == 30
def test_init_creates_empty_user_set(self):
"""Initializes with empty users to check set."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
assert len(processor._users_to_check) == 0
def test_init_creates_empty_pending_operations(self):
"""Initializes with empty pending operations dict."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
assert processor.pending_operations == {}
class TestQueueProcessorV2StartStop:
"""Tests for start and stop methods."""
def test_start_sets_running_flag(self):
"""start sets running flag to True."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
with patch.object(processor, "_process_queue_loop"):
processor.start()
assert processor.running is True
# Clean up
processor.running = False
if processor.thread:
processor.thread.join(timeout=1)
def test_start_creates_thread(self):
"""start creates a daemon thread."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
with patch.object(processor, "_process_queue_loop"):
processor.start()
assert processor.thread is not None
assert processor.thread.daemon is True
# Clean up
processor.running = False
processor.thread.join(timeout=1)
def test_start_when_already_running(self):
"""start does nothing if already running."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.running = True
# Should not create a new thread
processor.start()
assert processor.thread is None
def test_stop_sets_running_flag_false(self):
"""stop sets running flag to False."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.running = True
processor.thread = Mock()
processor.thread.join = Mock()
processor.stop()
assert processor.running is False
processor.thread.join.assert_called_once_with(timeout=10)
def test_stop_interrupts_check_interval_wait(self):
"""stop returns promptly while the loop sleeps between iterations.
The autouse reset_singletons fixture stops the processor after
every app test, so a stop() that blocks for check_interval adds
that many seconds of teardown to each of those tests (observed
as uniform 10.00s teardowns that pushed CI past its job timeout).
"""
import time
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2(check_interval=30)
# Wrap the real wait so the test knows when the loop has entered
# it — deterministic, unlike sleeping and hoping the first
# iteration has finished.
loop_entered_wait = threading.Event()
real_wait = processor._stop_event.wait
def tracking_wait(timeout=None):
loop_entered_wait.set()
return real_wait(timeout)
processor._stop_event.wait = tracking_wait
processor.start()
try:
assert loop_entered_wait.wait(timeout=10), (
"loop never reached its inter-iteration wait"
)
started = time.monotonic()
processor.stop()
elapsed = time.monotonic() - started
assert elapsed < 5, (
f"stop() blocked for {elapsed:.1f}s; the loop's "
"inter-iteration wait was not interrupted"
)
assert not processor.thread.is_alive()
finally:
processor.stop()
def test_start_after_stop_rearms_loop_wait(self):
"""A restarted processor loops again instead of exiting instantly.
stop() leaves _stop_event set; start() must clear it or the
restarted loop's wait returns immediately every iteration
(busy-spin) — and create_app() does restart this singleton after
the test-suite teardown stops it.
"""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.start()
processor.stop()
with patch.object(processor, "_process_queue_loop"):
processor.start()
try:
assert not processor._stop_event.is_set()
finally:
processor.stop()
class TestQueueProcessorV2NotifyUserActivity:
"""Tests for notify_user_activity method."""
def test_notify_user_activity_adds_to_set(self):
"""notify_user_activity adds user to check set."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.notify_user_activity("testuser", "session123")
assert ("testuser", "session123") in processor._users_to_check
def test_notify_user_activity_thread_safe(self):
"""notify_user_activity is thread safe."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
# Add from multiple threads
threads = []
for i in range(10):
t = threading.Thread(
target=processor.notify_user_activity,
args=(f"user{i}", f"session{i}"),
)
threads.append(t)
t.start()
for t in threads:
t.join()
# All should be added
assert len(processor._users_to_check) == 10
class TestQueueProcessorV2NotifyResearchQueued:
"""Tests for notify_research_queued method."""
@patch("local_deep_research.web.queue.processor_v2.get_user_db_session")
def test_notify_research_queued_queues_task(self, mock_get_session):
"""notify_research_queued adds task to queue."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
mock_session = MagicMock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
with patch(
"local_deep_research.web.queue.processor_v2.UserQueueService"
) as mock_queue_service_class:
mock_queue_service = Mock()
mock_queue_service_class.return_value = mock_queue_service
processor.notify_research_queued("testuser", "research123")
mock_queue_service.add_task_metadata.assert_called_once()
class TestQueueProcessorV2NotifyResearchCompleted:
"""Tests for notify_research_completed method."""
def test_notify_research_completed_removes_from_active(self):
"""notify_research_completed removes user from active set."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor._users_to_check.add("testuser:session123")
# Complete should trigger next queue check
processor.notify_research_completed("testuser", 123)
# Method exists and doesn't crash
class TestQueueProcessorV2QueueOperations:
"""Tests for queue operation methods."""
def test_queue_error_update_stores_pending(self):
"""queue_error_update stores pending operation."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.queue_error_update(
username="testuser",
research_id="123",
status="failed",
error_message="Test error",
metadata={"key": "value"},
completed_at="2024-01-01T00:00:00",
)
# Should store in pending operations
assert len(processor.pending_operations) > 0
# Check operation details
operation = list(processor.pending_operations.values())[0]
assert operation["operation_type"] == "error_update"
assert operation["username"] == "testuser"
assert operation["status"] == "failed"
def test_queue_progress_update_stores_pending(self):
"""queue_progress_update stores pending operation."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
processor.queue_progress_update(
username="testuser",
research_id="123",
progress=0.5,
)
# Should store in pending operations
assert len(processor.pending_operations) > 0
class TestQueueProcessorV2PendingOperations:
"""Tests for pending operations handling."""
def test_pending_operations_thread_safe(self):
"""Pending operations are thread safe."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
# Add operations from multiple threads using progress updates (simpler signature)
threads = []
for i in range(10):
t = threading.Thread(
target=processor.queue_progress_update,
kwargs={
"username": f"user{i}",
"research_id": str(i),
"progress": 0.5,
},
)
threads.append(t)
t.start()
for t in threads:
t.join()
# All should be stored
assert len(processor.pending_operations) == 10
class TestQueueProcessorV2DirectExecution:
"""Tests for direct execution mode."""
@patch("local_deep_research.web.queue.processor_v2.session_password_store")
@patch("local_deep_research.web.queue.processor_v2.db_manager")
@patch("local_deep_research.web.queue.processor_v2.get_user_db_session")
def test_direct_execution_checks_queue_mode(
self, mock_get_session, mock_db_manager, mock_password_store
):
"""Direct execution checks user's queue_mode setting."""
from local_deep_research.web.queue.processor_v2 import (
QueueProcessorV2,
)
processor = QueueProcessorV2()
mock_password_store.get_session_password.return_value = "password123"
mock_db_manager.open_user_database.return_value = Mock()
mock_session = MagicMock()
mock_session.__enter__ = Mock(return_value=mock_session)
mock_session.__exit__ = Mock(return_value=False)
mock_get_session.return_value = mock_session
with patch(
"local_deep_research.web.queue.processor_v2.UserQueueService"
) as mock_queue:
mock_queue_instance = Mock()
mock_queue.return_value = mock_queue_instance
# Direct execution requires settings manager
with patch(
"local_deep_research.settings.manager.SettingsManager"
) as mock_settings:
mock_settings_instance = Mock()
mock_settings_instance.get_setting.side_effect = (
lambda key, default: (
"queue" if key == "app.queue_mode" else default
)
)
mock_settings.return_value = mock_settings_instance
processor.notify_research_queued(
"testuser",
"research123",
session_id="session456",
query="test query",
)
# Should fall back to queue mode
mock_queue_instance.add_task_metadata.assert_called_once()