generated from john/python-template
Updated test suite
This commit is contained in:
@@ -4,85 +4,93 @@ import pytest
|
||||
|
||||
from transcription.models import Document
|
||||
from transcription.models import Job
|
||||
from transcription.models import JobStatus
|
||||
from transcription.models import Source
|
||||
from transcription.services.documents import DocumentService
|
||||
from transcription.services.jobs import JobService
|
||||
from transcription.services.jobs import JobStatus
|
||||
|
||||
|
||||
class TestJobService:
|
||||
class TestBasicCRUD:
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_job(self, job_service: JobService):
|
||||
"""Test creating a job."""
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_and_read_job(self, job_service: JobService, document_service: DocumentService):
|
||||
document = Document(id=uuid4(), name="test-bundle")
|
||||
await document_service.create_document(document=document)
|
||||
|
||||
def fake_job_factory():
|
||||
return Job(document_id=uuid4())
|
||||
job = Job(document_id=document.id)
|
||||
await job_service.create_job(job=job)
|
||||
|
||||
await job_service.create_job(job=fake_job_factory())
|
||||
fetched = await job_service.read_job(job_id=job.id)
|
||||
assert fetched.id == job.id
|
||||
assert fetched.document is not None
|
||||
assert fetched.document.id == document.id
|
||||
|
||||
async with job_service._session_scope() as session:
|
||||
for _ in range(10):
|
||||
await job_service.create_job(job=fake_job_factory(), session=session)
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_job_state_updates_status_and_retry(self, job_service: JobService, document_service: DocumentService):
|
||||
document = Document(id=uuid4(), name="test-bundle")
|
||||
await document_service.create_document(document=document)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_backpropagation(self, job_service: JobService, document_service: DocumentService):
|
||||
"""Test that creating a job backpropagates to the related document."""
|
||||
doc_id = uuid4()
|
||||
document = Document(
|
||||
id=doc_id,
|
||||
filename="test.txt",
|
||||
file_path="/path/to/test.txt",
|
||||
job = Job(document_id=document.id)
|
||||
await job_service.create_job(job=job)
|
||||
|
||||
updated = await job_service.update_job_state(
|
||||
job_id=job.id,
|
||||
status=JobStatus.PROCESSING,
|
||||
retry_count_increment=1,
|
||||
)
|
||||
|
||||
assert updated.status == JobStatus.PROCESSING
|
||||
assert updated.retry_count == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_jobs_by_status(self, job_service: JobService, document_service: DocumentService):
|
||||
document = Document(id=uuid4(), name="query-doc")
|
||||
await document_service.create_document(document=document)
|
||||
|
||||
await job_service.create_job(job=Job(document_id=document.id, status=JobStatus.PROCESSING))
|
||||
await job_service.create_job(job=Job(document_id=document.id, status=JobStatus.QUEUED))
|
||||
|
||||
result = await job_service.query_jobs(status=JobStatus.PROCESSING)
|
||||
assert len(result) == 1
|
||||
assert result[0].status == JobStatus.PROCESSING
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_jobs_by_source_filename(self, job_service: JobService, document_service: DocumentService):
|
||||
document = Document(id=uuid4(), name="source-doc")
|
||||
await document_service.create_document(document=document)
|
||||
|
||||
job = Job(document_id=document.id)
|
||||
await job_service.create_job(job=job)
|
||||
|
||||
async with job_service._session_scope() as session:
|
||||
session.add(
|
||||
Source(
|
||||
document_id=document.id,
|
||||
job_id=job.id,
|
||||
upload_name="letter.jpg",
|
||||
filename="stored-letter.jpg",
|
||||
file_path="/uploads/stored-letter.jpg",
|
||||
)
|
||||
)
|
||||
await document_service.create_document(document=document)
|
||||
job = Job(document_id=doc_id)
|
||||
await job_service.create_job(job=job)
|
||||
await session.commit()
|
||||
|
||||
read_job = await job_service.read_job(job_id=job.id)
|
||||
assert isinstance(read_job.document, Document)
|
||||
assert read_job.document.id == document.id
|
||||
result = await job_service.query_jobs(filename="stored-letter.jpg")
|
||||
assert len(result) == 1
|
||||
assert result[0].id == job.id
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reading_job(self, job_service: JobService):
|
||||
"""Test reading a job."""
|
||||
uuid = uuid4()
|
||||
await job_service.create_job(job=Job(id=uuid, document_id=uuid4()))
|
||||
job = await job_service.read_job(job_id=uuid)
|
||||
assert job.id == uuid
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_next_queued_job_orders_by_created_date(
|
||||
self,
|
||||
job_service: JobService,
|
||||
document_service: DocumentService,
|
||||
):
|
||||
document = Document(id=uuid4(), name="ordered-doc")
|
||||
await document_service.create_document(document=document)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_updating_job(self, job_service: JobService):
|
||||
"""Test updating a job."""
|
||||
uuid = uuid4()
|
||||
job = Job(id=uuid, document_id=uuid4())
|
||||
async with job_service._session_scope() as session:
|
||||
await job_service.create_job(job=job, session=session)
|
||||
job.status = JobStatus.PROCESSING
|
||||
await job_service.update_job(job=job, session=session)
|
||||
read_job = await job_service.read_job(job_id=uuid, session=session)
|
||||
assert read_job == job
|
||||
first = Job(document_id=document.id, status=JobStatus.QUEUED)
|
||||
second = Job(document_id=document.id, status=JobStatus.QUEUED)
|
||||
await job_service.create_job(job=first)
|
||||
await job_service.create_job(job=second)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deleting_job(self, job_service: JobService):
|
||||
"""Test deleting a job."""
|
||||
|
||||
class TestServiceMethods:
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_jobs(self, job_service: JobService):
|
||||
"""Test querying jobs."""
|
||||
await job_service.create_job(job=Job(document_id=uuid4(), status=JobStatus.PROCESSING))
|
||||
result = await job_service.query_jobs(status=JobStatus.PROCESSING)
|
||||
jobs = {str(job.id).split("-")[0]: job.status for job in result}
|
||||
assert len(jobs) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_jobs(self, job_service: JobService):
|
||||
"""Test listing jobs."""
|
||||
n = 5
|
||||
for _ in range(n):
|
||||
await job_service.create_job(job=Job(document_id=uuid4()))
|
||||
jobs = await job_service.list_jobs()
|
||||
assert len(jobs) == n
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_job_status(self, job_service: JobService):
|
||||
"""Test marking a job with a new status."""
|
||||
next_job = await job_service.read_next_queued_job()
|
||||
assert next_job is not None
|
||||
assert next_job.id == first.id
|
||||
|
||||
@@ -49,10 +49,11 @@ class TestRealImageExternalTranscription:
|
||||
assert REAL_IMAGES_DIR.exists()
|
||||
assert _real_image_paths()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("image_path", _real_image_paths(), ids=lambda p: p.name)
|
||||
def test_transcribes_real_image_fixture(self, image_path: Path):
|
||||
async def test_transcribes_real_image_fixture(self, image_path: Path):
|
||||
"""Real fixture image produces a non-empty transcription result."""
|
||||
result = transcribe_document_image(image_path)
|
||||
result = await transcribe_document_image(image_path)
|
||||
assert result.provider == "openrouter"
|
||||
assert isinstance(result.model, str) and result.model.strip()
|
||||
assert isinstance(result.text, str) and result.text.strip()
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Reliability tests for worker workflow timeout behavior."""
|
||||
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from transcription.config import Settings
|
||||
from transcription.models import Document
|
||||
from transcription.models import Job
|
||||
from transcription.models import JobStatus
|
||||
from transcription.models import Source
|
||||
from transcription.services import ServiceBundle
|
||||
from transcription.services.workflows import process_queued_job
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestWorkflowReliability:
|
||||
"""Verify timeout and terminal-state reliability behavior."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_process_queued_job_timeout_marks_job_failed(self, default_session_factory, monkeypatch):
|
||||
"""Provider timeout transitions a queued job to failed with error detail."""
|
||||
services = ServiceBundle()
|
||||
object.__setattr__(services, "documents", services.documents.__class__(session_factory=default_session_factory))
|
||||
object.__setattr__(services, "jobs", services.jobs.__class__(session_factory=default_session_factory))
|
||||
object.__setattr__(services, "transcriptions", services.transcriptions.__class__(session_factory=default_session_factory))
|
||||
|
||||
async with services.jobs._session_scope() as session:
|
||||
document = Document(id=uuid4(), name="timeout-doc")
|
||||
session.add(document)
|
||||
await session.flush()
|
||||
|
||||
job = Job(document_id=document.id, status=JobStatus.QUEUED)
|
||||
session.add(job)
|
||||
await session.flush()
|
||||
|
||||
source = Source(
|
||||
document_id=document.id,
|
||||
job_id=job.id,
|
||||
upload_name="timeout.jpg",
|
||||
filename="timeout.jpg",
|
||||
file_path=str(Path("tests/fixtures/images/real/Book Two - page 02.jpg")),
|
||||
)
|
||||
session.add(source)
|
||||
await session.commit()
|
||||
|
||||
loaded = await services.jobs.read_job(job_id=job.id, session=session)
|
||||
|
||||
async def _never_returns(image_path, *, prompt_name="transcribe_document.md", settings=None, provider=None):
|
||||
_ = (image_path, prompt_name, settings, provider)
|
||||
raise TimeoutError("simulated provider timeout")
|
||||
|
||||
monkeypatch.setattr("transcription.services.workflows.transcribe_document_image", _never_returns)
|
||||
|
||||
timeout_settings = Settings(openrouter_api_key="test-key", worker_provider_timeout_seconds=20.0)
|
||||
result = await process_queued_job(job=loaded, services=services, settings=timeout_settings)
|
||||
|
||||
assert result is not None
|
||||
assert result.status == JobStatus.FAILED
|
||||
assert result.error_detail is not None
|
||||
assert "timed out" in result.error_detail.lower()
|
||||
assert "20.0s" in result.error_detail
|
||||
Reference in New Issue
Block a user