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

416 lines
14 KiB
Python

"""
Tests for database/queue_service.py
Tests cover:
- UserQueueService initialization
- Queue status management
- Task metadata operations
- Task status updates
- Pending task retrieval
- Task cleanup
"""
from unittest.mock import Mock
from datetime import datetime, UTC
class TestUserQueueServiceInit:
"""Tests for UserQueueService initialization."""
def test_init_with_session(self):
"""Test initialization with a session."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
service = UserQueueService(mock_session)
assert service.session == mock_session
class TestUpdateQueueStatus:
"""Tests for update_queue_status method."""
def test_updates_existing_status(self):
"""Test updating existing queue status."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_status = Mock()
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
service.update_queue_status(5, 10, "task-123")
assert mock_status.active_tasks == 5
assert mock_status.queued_tasks == 10
assert mock_status.last_task_id == "task-123"
mock_session.commit.assert_called_once()
def test_creates_new_status_when_none_exists(self):
"""Test creating status when none exists."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_session.query.return_value.first.return_value = None
service = UserQueueService(mock_session)
service.update_queue_status(2, 5)
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
class TestGetQueueStatus:
"""Tests for get_queue_status method."""
def test_returns_status_dict(self):
"""Test returns status as dictionary."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_status = Mock()
mock_status.active_tasks = 3
mock_status.queued_tasks = 7
mock_status.last_checked = datetime.now(UTC)
mock_status.last_task_id = "task-456"
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
result = service.get_queue_status()
assert result["active_tasks"] == 3
assert result["queued_tasks"] == 7
assert result["last_task_id"] == "task-456"
def test_returns_none_when_no_status(self):
"""Test returns None when no status exists."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_session.query.return_value.first.return_value = None
service = UserQueueService(mock_session)
result = service.get_queue_status()
assert result is None
class TestAddTaskMetadata:
"""Tests for add_task_metadata method."""
def test_adds_task_metadata(self):
"""Test adding task metadata."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
# Mock for _increment_queue_count
mock_status = Mock()
mock_status.queued_tasks = 0
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
service.add_task_metadata("task-1", "research", priority=5)
mock_session.add.assert_called()
mock_session.commit.assert_called()
def test_increments_queue_count(self):
"""Test that queue count is incremented."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_status = Mock()
mock_status.queued_tasks = 2
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
service.add_task_metadata("task-1", "research")
assert mock_status.queued_tasks == 3
class TestUpdateTaskStatus:
"""Tests for update_task_status method."""
def test_updates_task_to_processing(self):
"""Test updating task to processing status."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_task = Mock()
mock_task.status = "queued"
mock_status = Mock()
mock_status.queued_tasks = 5
mock_status.active_tasks = 2
# Set up query chain
mock_filter = Mock()
mock_filter.first.return_value = mock_task
mock_query = Mock()
mock_query.filter_by.return_value = mock_filter
mock_query.first.return_value = mock_status
def query_side_effect(model):
if hasattr(model, "task_id"): # TaskMetadata
return mock_query
return Mock(first=Mock(return_value=mock_status))
mock_session.query.side_effect = query_side_effect
service = UserQueueService(mock_session)
service.update_task_status("task-1", "processing")
assert mock_task.status == "processing"
def test_updates_task_to_completed(self):
"""Test updating task to completed status."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_task = Mock()
mock_task.status = "processing"
mock_status = Mock()
mock_status.queued_tasks = 3
mock_status.active_tasks = 2
mock_filter = Mock()
mock_filter.first.return_value = mock_task
mock_query = Mock()
mock_query.filter_by.return_value = mock_filter
mock_query.first.return_value = mock_status
def query_side_effect(model):
if hasattr(model, "task_id"):
return mock_query
return Mock(first=Mock(return_value=mock_status))
mock_session.query.side_effect = query_side_effect
service = UserQueueService(mock_session)
service.update_task_status("task-1", "completed")
assert mock_task.status == "completed"
def test_updates_task_with_error(self):
"""Test updating task with error message."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_task = Mock()
mock_task.status = "processing"
mock_status = Mock()
mock_status.queued_tasks = 0
mock_status.active_tasks = 1
mock_filter = Mock()
mock_filter.first.return_value = mock_task
mock_query = Mock()
mock_query.filter_by.return_value = mock_filter
mock_query.first.return_value = mock_status
def query_side_effect(model):
if hasattr(model, "task_id"):
return mock_query
return Mock(first=Mock(return_value=mock_status))
mock_session.query.side_effect = query_side_effect
service = UserQueueService(mock_session)
service.update_task_status("task-1", "failed", "Something went wrong")
assert mock_task.status == "failed"
assert mock_task.error_message == "Something went wrong"
def test_handles_nonexistent_task(self):
"""Test handling when task doesn't exist."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_filter = Mock()
mock_filter.first.return_value = None
mock_query = Mock()
mock_query.filter_by.return_value = mock_filter
mock_session.query.return_value = mock_query
service = UserQueueService(mock_session)
# Should not raise
service.update_task_status("nonexistent", "completed")
class TestGetPendingTasks:
"""Tests for get_pending_tasks method."""
def test_returns_pending_tasks(self):
"""Test getting pending tasks."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_task1 = Mock()
mock_task1.task_id = "task-1"
mock_task1.task_type = "research"
mock_task1.created_at = datetime.now(UTC)
mock_task1.priority = 5
mock_task2 = Mock()
mock_task2.task_id = "task-2"
mock_task2.task_type = "analysis"
mock_task2.created_at = datetime.now(UTC)
mock_task2.priority = 3
mock_query = mock_session.query.return_value
mock_query.filter_by.return_value.order_by.return_value.limit.return_value.all.return_value = [
mock_task1,
mock_task2,
]
service = UserQueueService(mock_session)
result = service.get_pending_tasks(limit=5)
assert len(result) == 2
assert result[0]["task_id"] == "task-1"
assert result[0]["task_type"] == "research"
assert result[1]["task_id"] == "task-2"
def test_returns_empty_list_when_no_tasks(self):
"""Test returns empty list when no pending tasks."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_query = mock_session.query.return_value
mock_query.filter_by.return_value.order_by.return_value.limit.return_value.all.return_value = []
service = UserQueueService(mock_session)
result = service.get_pending_tasks()
assert result == []
class TestCleanupOldTasks:
"""Tests for cleanup_old_tasks method."""
def test_deletes_old_tasks(self):
"""Test deleting old completed tasks."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_query = mock_session.query.return_value
mock_query.filter.return_value.delete.return_value = 5
service = UserQueueService(mock_session)
result = service.cleanup_old_tasks(days=7)
assert result == 5
mock_session.commit.assert_called_once()
class TestQueueCountHelpers:
"""Tests for queue count helper methods."""
def test_increment_queue_count_existing(self):
"""Test incrementing queue count with existing status."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_status = Mock()
mock_status.queued_tasks = 5
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
service._increment_queue_count()
assert mock_status.queued_tasks == 6
def test_increment_queue_count_new_status(self):
"""Test incrementing queue count creates new status."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_session.query.return_value.first.return_value = None
service = UserQueueService(mock_session)
service._increment_queue_count()
mock_session.add.assert_called_once()
def test_update_queue_counts_clamps_to_zero(self):
"""Test that queue counts don't go below zero."""
from local_deep_research.database.queue_service import UserQueueService
mock_session = Mock()
mock_status = Mock()
mock_status.queued_tasks = 1
mock_status.active_tasks = 1
mock_session.query.return_value.first.return_value = mock_status
service = UserQueueService(mock_session)
service._update_queue_counts(-5, -5)
assert mock_status.queued_tasks == 0
assert mock_status.active_tasks == 0
class TestProcessingToQueuedRevert:
"""update_task_status('queued') after 'processing' reverts the counter.
Regression: the queued-dispatch loop's SystemAtCapacityError branch
re-queues a research without reverting the queued->processing counter
claim, so each capacity-rejected retry leaked a slot into active_tasks
and decremented queued_tasks. Under sustained capacity pressure
queued_tasks drifts to 0, at which point _process_user_queue treats the
queue as empty and stops dispatching the still-present rows. These use
a real in-memory DB so the counter arithmetic is actually exercised.
"""
@staticmethod
def _service():
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from local_deep_research.database.models.queue import (
QueueStatus,
TaskMetadata,
)
from local_deep_research.database.queue_service import (
UserQueueService,
)
engine = create_engine("sqlite:///:memory:")
QueueStatus.__table__.create(engine)
TaskMetadata.__table__.create(engine)
session = sessionmaker(bind=engine)()
return UserQueueService(session)
def test_processing_then_queued_round_trips_counts(self):
svc = self._service()
svc.add_task_metadata("r1", "research") # queued 1 / active 0
assert svc.get_queue_status()["queued_tasks"] == 1
assert svc.get_queue_status()["active_tasks"] == 0
svc.update_task_status("r1", "processing") # queued 0 / active 1
s = svc.get_queue_status()
assert s["queued_tasks"] == 0
assert s["active_tasks"] == 1
svc.update_task_status("r1", "queued") # reverted: queued 1 / active 0
s = svc.get_queue_status()
assert s["queued_tasks"] == 1, (
"queued->processing claim must be reverted on re-queue"
)
assert s["active_tasks"] == 0, (
"active slot must not leak after a capacity re-queue"
)
def test_repeated_capacity_rejects_do_not_drift(self):
"""Three processing->queued cycles (simulating capacity retries)
must leave the counter exactly where it started."""
svc = self._service()
svc.add_task_metadata("r1", "research")
for _ in range(3):
svc.update_task_status("r1", "processing")
svc.update_task_status("r1", "queued")
s = svc.get_queue_status()
assert s["queued_tasks"] == 1
assert s["active_tasks"] == 0