generated from john/python-template
ver1-step2 implemented
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
"""Tests for transcription.worker."""
|
||||
|
||||
from pathlib import Path
|
||||
from threading import Event
|
||||
|
||||
import pytest
|
||||
from sqlmodel import select
|
||||
|
||||
from transcription.config import Settings
|
||||
from transcription.errors import AppError, ErrorCategory
|
||||
from transcription.models import Document, Job, JobStatus, Transcript
|
||||
from transcription.providers.base import TranscriptionResult
|
||||
from transcription.worker import process_next_queued_job, run_worker_loop
|
||||
@@ -37,7 +40,7 @@ class TestWorkerQueueBehavior:
|
||||
class TestWorkerSuccessPath:
|
||||
"""Verify worker success-path lifecycle transitions and transcript persistence."""
|
||||
|
||||
def test_transitions_processing_to_transcribed(self, session, monkeypatch):
|
||||
def test_transitions_processing_to_transcribed(self, session, monkeypatch, tmp_path: Path):
|
||||
"""process_next_queued_job transitions queued jobs to transcribed on success."""
|
||||
job = _create_queued_job(session)
|
||||
|
||||
@@ -45,6 +48,10 @@ class TestWorkerSuccessPath:
|
||||
return TranscriptionResult(text="ok", provider="openrouter", model="test-model")
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
|
||||
)
|
||||
|
||||
processed = process_next_queued_job(session=session)
|
||||
session.refresh(job)
|
||||
@@ -52,7 +59,7 @@ class TestWorkerSuccessPath:
|
||||
assert processed is True
|
||||
assert job.status == JobStatus.TRANSCRIBED
|
||||
|
||||
def test_persists_transcript_text_on_success(self, session, monkeypatch):
|
||||
def test_persists_transcript_text_on_success(self, session, monkeypatch, tmp_path: Path):
|
||||
"""process_next_queued_job stores transcript text for successful jobs."""
|
||||
job = _create_queued_job(session)
|
||||
|
||||
@@ -60,6 +67,10 @@ class TestWorkerSuccessPath:
|
||||
return TranscriptionResult(text="Transcript body", provider="openrouter", model="test-model")
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
|
||||
)
|
||||
|
||||
process_next_queued_job(session=session)
|
||||
|
||||
@@ -75,7 +86,7 @@ class TestWorkerSuccessPath:
|
||||
class TestWorkerFailurePath:
|
||||
"""Verify worker failure-path lifecycle transitions and error persistence."""
|
||||
|
||||
def test_sets_failed_and_error_detail_on_failure(self, session, monkeypatch):
|
||||
def test_sets_failed_and_error_detail_on_failure(self, session, monkeypatch, tmp_path: Path):
|
||||
"""process_next_queued_job marks failed and stores error detail on exception."""
|
||||
job = _create_queued_job(session)
|
||||
|
||||
@@ -83,6 +94,10 @@ class TestWorkerFailurePath:
|
||||
raise RuntimeError("provider failure")
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
|
||||
)
|
||||
|
||||
processed = process_next_queued_job(session=session)
|
||||
session.refresh(job)
|
||||
@@ -99,7 +114,7 @@ class TestWorkerFailurePath:
|
||||
assert "error_id=" in transcript.error_detail
|
||||
assert "suggestion=" in transcript.error_detail
|
||||
|
||||
def test_updates_existing_transcript_if_present(self, session, monkeypatch):
|
||||
def test_updates_existing_transcript_if_present(self, session, monkeypatch, tmp_path: Path):
|
||||
"""process_next_queued_job updates existing transcript instead of duplicating."""
|
||||
job = _create_queued_job(session)
|
||||
existing = Transcript(job_id=job.id, text="old", error_detail=None)
|
||||
@@ -111,6 +126,10 @@ class TestWorkerFailurePath:
|
||||
raise RuntimeError("provider failure")
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(openrouter_api_key="test-key", prompt_dir=tmp_path),
|
||||
)
|
||||
|
||||
process_next_queued_job(session=session)
|
||||
|
||||
@@ -125,6 +144,73 @@ class TestWorkerFailurePath:
|
||||
assert "error_id=" in transcripts[0].error_detail
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
class TestWorkerRetryBehavior:
|
||||
"""Verify worker retry and terminal failure policies."""
|
||||
|
||||
def test_retriable_failure_requeues_until_limit(self, session, monkeypatch, tmp_path: Path):
|
||||
"""Retriable failures requeue jobs while retry budget remains."""
|
||||
job = _create_queued_job(session)
|
||||
|
||||
def _fake_transcribe(_path):
|
||||
raise AppError(
|
||||
"temporary upstream outage",
|
||||
category=ErrorCategory.EXTERNAL_PROVIDER,
|
||||
suggestion="Retry from jobs page.",
|
||||
retriable=True,
|
||||
)
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(
|
||||
openrouter_api_key="test-key",
|
||||
prompt_dir=tmp_path,
|
||||
worker_max_retries=1,
|
||||
worker_retry_backoff_seconds=0.0,
|
||||
),
|
||||
)
|
||||
|
||||
processed = process_next_queued_job(session=session)
|
||||
session.refresh(job)
|
||||
|
||||
assert processed is True
|
||||
assert job.status == JobStatus.QUEUED
|
||||
assert job.retry_count == 1
|
||||
|
||||
def test_retriable_failure_exhaustion_sets_failed(self, session, monkeypatch, tmp_path: Path):
|
||||
"""Retriable failures transition to failed when retry budget is exhausted."""
|
||||
job = _create_queued_job(session)
|
||||
job.retry_count = 1
|
||||
session.add(job)
|
||||
session.commit()
|
||||
|
||||
def _fake_transcribe(_path):
|
||||
raise AppError(
|
||||
"temporary upstream outage",
|
||||
category=ErrorCategory.EXTERNAL_PROVIDER,
|
||||
suggestion="Retry from jobs page.",
|
||||
retriable=True,
|
||||
)
|
||||
|
||||
monkeypatch.setattr("transcription.worker.transcribe_document_image", _fake_transcribe)
|
||||
monkeypatch.setattr(
|
||||
"transcription.worker.get_settings",
|
||||
lambda: Settings(
|
||||
openrouter_api_key="test-key",
|
||||
prompt_dir=tmp_path,
|
||||
worker_max_retries=1,
|
||||
worker_retry_backoff_seconds=0.0,
|
||||
),
|
||||
)
|
||||
|
||||
process_next_queued_job(session=session)
|
||||
session.refresh(job)
|
||||
|
||||
assert job.status == JobStatus.FAILED
|
||||
assert job.retry_count == 1
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestWorkerLoopControl:
|
||||
"""Verify worker loop start/stop behavior."""
|
||||
@@ -136,7 +222,7 @@ class TestWorkerLoopControl:
|
||||
|
||||
called = {"value": False}
|
||||
|
||||
def _fake_process_next_queued_job():
|
||||
def _fake_process_next_queued_job(**_kwargs):
|
||||
called["value"] = True
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user