fix: retry execution attempt number conflicts

Co-authored-by: Copilot App <[email protected]>
This commit is contained in:
Jim Lancaster
2026-08-23 18:35:59 -05:00
co-authored by Copilot App
parent f9261a1af3
commit 2093eb6fb3
3 changed files with 187 additions and 43 deletions
+104
View File
@@ -2,9 +2,12 @@ from uuid import uuid4
import pytest
from pydantic import JsonValue
from sqlalchemy.exc import IntegrityError
from sqlmodel.ext.asyncio.session import AsyncSession
from transcription.db.models import Document
from transcription.db.models import DocumentPerson
from transcription.db.models import ExecutionAttempt
from transcription.db.models import Job
from transcription.db.models import JobSource
from transcription.db.models import JobSourceStatus
@@ -369,3 +372,104 @@ async def test_update_job_source_transcription_persists_provider_json_payloads(d
assert attempt.attempt.raw_transcription == "provider transcript"
assert attempt.attempt.normalized_metadata == metadata
assert attempt.attempt.sdk_response_snapshot == raw_payload
@pytest.mark.asyncio
async def test_update_job_source_transcription_retries_on_execution_attempt_integrity_conflict(
default_session_factory,
monkeypatch,
):
documents = DocumentService(session_factory=default_session_factory)
jobs = JobService(session_factory=default_session_factory)
transcriptions = SourceService(session_factory=default_session_factory)
document = await documents.create_document(Document(id=uuid4(), name="attempt-retry-doc"))
job = await jobs.create_job(Job(document_id=document.id))
source = await transcriptions.create_source(
Source(
document_id=document.id,
page_number=1,
upload_name="attempt-retry.jpg",
filename="attempt-retry.jpg",
file_path="uploads/attempt-retry.jpg",
file_hash="e" * 64,
file_size_bytes=1,
)
)
await transcriptions.create_job_source(
JobSource(job_id=job.id, source_id=source.id, status=JobSourceStatus.PENDING)
)
original_flush = AsyncSession.flush
execution_attempt_flushes = 0
async def _flaky_flush(self, *args, **kwargs):
nonlocal execution_attempt_flushes
if any(isinstance(item, ExecutionAttempt) for item in self.new):
execution_attempt_flushes += 1
if execution_attempt_flushes == 1:
raise IntegrityError("insert execution_attempt", {}, Exception("duplicate attempt number"))
return await original_flush(self, *args, **kwargs)
monkeypatch.setattr(AsyncSession, "flush", _flaky_flush)
await transcriptions.update_job_source_transcription(
job_id=job.id,
source_id=source.id,
text="retry succeeds",
provider="openrouter",
model="test-model",
)
assert execution_attempt_flushes == 2
rows = await transcriptions.list_job_sources(job_id=job.id)
assert len(rows) == 1
assert rows[0].status == JobSourceStatus.TRANSCRIBED
@pytest.mark.asyncio
async def test_update_job_source_transcription_raises_domain_error_after_attempt_retry_exhaustion(
default_session_factory,
monkeypatch,
):
documents = DocumentService(session_factory=default_session_factory)
jobs = JobService(session_factory=default_session_factory)
transcriptions = SourceService(session_factory=default_session_factory)
document = await documents.create_document(Document(id=uuid4(), name="attempt-exhaustion-doc"))
job = await jobs.create_job(Job(document_id=document.id))
source = await transcriptions.create_source(
Source(
document_id=document.id,
page_number=1,
upload_name="attempt-exhaustion.jpg",
filename="attempt-exhaustion.jpg",
file_path="uploads/attempt-exhaustion.jpg",
file_hash="f" * 64,
file_size_bytes=1,
)
)
await transcriptions.create_job_source(
JobSource(job_id=job.id, source_id=source.id, status=JobSourceStatus.PENDING)
)
original_flush = AsyncSession.flush
async def _always_conflict_flush(self, *args, **kwargs):
if any(isinstance(item, ExecutionAttempt) for item in self.new):
raise IntegrityError("insert execution_attempt", {}, Exception("duplicate attempt number"))
return await original_flush(self, *args, **kwargs)
monkeypatch.setattr(AsyncSession, "flush", _always_conflict_flush)
with pytest.raises(TranscriptionError) as exc_info:
await transcriptions.update_job_source_transcription(
job_id=job.id,
source_id=source.id,
text="will not persist",
provider="openrouter",
model="test-model",
)
assert exc_info.value.category == ErrorCategory.CONFLICT
assert "attempt number" in exc_info.value.message.lower()