74e5e5b531
Add a Quizzical Beats MCP server and automation service for agentic quiz workflows. Includes tools for catalog lookup/add/import, round creation and naming, MP3/PDF generation and inspection, email delivery, TTS snippet updates, docs, and focused tests. Deployment intentionally not performed.
203 lines
7.3 KiB
Python
203 lines
7.3 KiB
Python
"""Tests for the import queue data structures."""
|
|
import pytest
|
|
import threading
|
|
import time
|
|
from unittest.mock import patch
|
|
|
|
from musicround.helpers.import_queue import ImportJob, ImportQueue, ImportWorker
|
|
from musicround.models import User, db
|
|
|
|
|
|
class TestImportJob:
|
|
"""Tests for the ImportJob dataclass."""
|
|
|
|
def test_import_job_creation(self):
|
|
"""Test creating an ImportJob with all fields."""
|
|
job = ImportJob(
|
|
priority=5,
|
|
service_name='spotify',
|
|
item_type='playlist',
|
|
item_id='abc123',
|
|
user_id=1,
|
|
)
|
|
assert job.priority == 5
|
|
assert job.service_name == 'spotify'
|
|
assert job.item_type == 'playlist'
|
|
assert job.item_id == 'abc123'
|
|
assert job.user_id == 1
|
|
|
|
def test_import_job_ordering_by_priority(self):
|
|
"""Test that ImportJobs are ordered by priority (lower = higher priority)."""
|
|
job_high = ImportJob(priority=1, service_name='spotify', item_type='track',
|
|
item_id='a', user_id=1)
|
|
job_low = ImportJob(priority=10, service_name='spotify', item_type='track',
|
|
item_id='b', user_id=1)
|
|
assert job_high < job_low
|
|
|
|
def test_import_job_equality(self):
|
|
"""Test that ImportJobs with the same priority compare as equal."""
|
|
job1 = ImportJob(priority=5, service_name='spotify', item_type='track',
|
|
item_id='x', user_id=1)
|
|
job2 = ImportJob(priority=5, service_name='deezer', item_type='album',
|
|
item_id='y', user_id=2)
|
|
# Only priority is used for comparison
|
|
assert job1 == job2
|
|
|
|
def test_import_job_deezer(self):
|
|
"""Test creating a Deezer ImportJob."""
|
|
job = ImportJob(
|
|
priority=3,
|
|
service_name='deezer',
|
|
item_type='album',
|
|
item_id='456',
|
|
user_id=7,
|
|
)
|
|
assert job.service_name == 'deezer'
|
|
assert job.item_type == 'album'
|
|
|
|
|
|
class TestImportQueue:
|
|
"""Tests for the ImportQueue class."""
|
|
|
|
def test_queue_creation(self):
|
|
"""Test creating an ImportQueue."""
|
|
queue = ImportQueue()
|
|
assert queue is not None
|
|
assert queue._counter == 0
|
|
|
|
def test_add_and_get_job(self):
|
|
"""Test adding a job to the queue and retrieving it."""
|
|
queue = ImportQueue()
|
|
job = ImportJob(priority=5, service_name='spotify', item_type='track',
|
|
item_id='track1', user_id=1)
|
|
queue.add_job(job)
|
|
retrieved = queue.get_job(timeout=1.0)
|
|
assert retrieved is not None
|
|
assert retrieved.item_id == 'track1'
|
|
|
|
def test_get_job_respects_priority(self):
|
|
"""Test that higher-priority jobs (lower number) are retrieved first."""
|
|
queue = ImportQueue()
|
|
low = ImportJob(priority=10, service_name='s', item_type='t', item_id='low', user_id=1)
|
|
high = ImportJob(priority=1, service_name='s', item_type='t', item_id='high', user_id=1)
|
|
low_again = ImportJob(priority=10, service_name='s', item_type='t', item_id='low2', user_id=1)
|
|
queue.add_job(low)
|
|
queue.add_job(high)
|
|
queue.add_job(low_again)
|
|
|
|
first = queue.get_job(timeout=0.1)
|
|
assert first.item_id == 'high'
|
|
|
|
def test_get_job_empty_returns_none(self):
|
|
"""Test that get_job returns None when the queue is empty."""
|
|
queue = ImportQueue()
|
|
result = queue.get_job(timeout=0.05)
|
|
assert result is None
|
|
|
|
def test_task_done(self):
|
|
"""Test that task_done can be called after retrieving a job."""
|
|
queue = ImportQueue()
|
|
job = ImportJob(priority=5, service_name='s', item_type='t', item_id='1', user_id=1)
|
|
queue.add_job(job)
|
|
queue.get_job(timeout=0.1)
|
|
# Should not raise
|
|
queue.task_done()
|
|
|
|
def test_counter_increments(self):
|
|
"""Test that internal counter increments with each job added."""
|
|
queue = ImportQueue()
|
|
assert queue._counter == 0
|
|
queue.add_job(ImportJob(priority=1, service_name='s', item_type='t', item_id='1', user_id=1))
|
|
assert queue._counter == 1
|
|
queue.add_job(ImportJob(priority=1, service_name='s', item_type='t', item_id='2', user_id=1))
|
|
assert queue._counter == 2
|
|
|
|
def test_fifo_within_same_priority(self):
|
|
"""Test that jobs with the same priority are retrieved in insertion order (FIFO)."""
|
|
queue = ImportQueue()
|
|
first = ImportJob(priority=5, service_name='s', item_type='t', item_id='first', user_id=1)
|
|
second = ImportJob(priority=5, service_name='s', item_type='t', item_id='second', user_id=1)
|
|
queue.add_job(first)
|
|
queue.add_job(second)
|
|
assert queue.get_job(timeout=0.1).item_id == 'first'
|
|
assert queue.get_job(timeout=0.1).item_id == 'second'
|
|
|
|
def test_thread_safety(self):
|
|
"""Test that the queue handles concurrent access safely."""
|
|
queue = ImportQueue()
|
|
results = []
|
|
errors = []
|
|
|
|
def producer():
|
|
try:
|
|
for i in range(5):
|
|
queue.add_job(ImportJob(
|
|
priority=i, service_name='s', item_type='t',
|
|
item_id=str(i), user_id=1,
|
|
))
|
|
except Exception as e:
|
|
errors.append(e)
|
|
|
|
def consumer():
|
|
try:
|
|
for _ in range(5):
|
|
job = queue.get_job(timeout=1.0)
|
|
if job:
|
|
results.append(job.item_id)
|
|
queue.task_done()
|
|
except Exception as e:
|
|
errors.append(e)
|
|
|
|
t1 = threading.Thread(target=producer)
|
|
t2 = threading.Thread(target=consumer)
|
|
t1.start()
|
|
t2.start()
|
|
t1.join(timeout=5)
|
|
t2.join(timeout=5)
|
|
|
|
assert not errors
|
|
assert len(results) == 5
|
|
|
|
|
|
class TestImportWorker:
|
|
"""Tests for ImportWorker job processing."""
|
|
|
|
def test_process_job_imports_as_user(self, app):
|
|
"""Test that _process_job logs in the target user and imports the item."""
|
|
with app.app_context():
|
|
user = User(username='workeruser', email='worker@example.com')
|
|
user.password = 'WorkerPass123!'
|
|
db.session.add(user)
|
|
db.session.commit()
|
|
|
|
worker = ImportWorker(app, ImportQueue())
|
|
job = ImportJob(
|
|
priority=1,
|
|
service_name='deezer',
|
|
item_type='track',
|
|
item_id='123',
|
|
user_id=user.id,
|
|
)
|
|
|
|
with patch('musicround.helpers.import_queue.ImportHelper.import_item') as mock_import:
|
|
worker._process_job(job)
|
|
|
|
mock_import.assert_called_once_with('deezer', 'track', '123')
|
|
|
|
def test_process_job_unknown_user_does_not_import(self, app):
|
|
"""Test that jobs for missing users are ignored."""
|
|
with app.app_context():
|
|
worker = ImportWorker(app, ImportQueue())
|
|
job = ImportJob(
|
|
priority=1,
|
|
service_name='deezer',
|
|
item_type='track',
|
|
item_id='123',
|
|
user_id=999,
|
|
)
|
|
|
|
with patch('musicround.helpers.import_queue.ImportHelper.import_item') as mock_import:
|
|
worker._process_job(job)
|
|
|
|
mock_import.assert_not_called()
|