generated from john/python-template
Jobs: jobs still stuck in queue. Fixes from testing.
This commit is contained in:
@@ -14,7 +14,6 @@ from transcription.db.models import Person
|
||||
from transcription.db.models import Source
|
||||
from transcription.services.documents import DocumentDeleteBlockedError
|
||||
from transcription.services.documents import DocumentError
|
||||
from transcription.services.documents import PersonDeleteBlockedError
|
||||
from transcription.services.documents import DocumentService
|
||||
|
||||
|
||||
@@ -153,7 +152,7 @@ async def test_update_person_refreshes_updated_timestamp(default_session_factory
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_person_blocks_when_linked_documents_exist(default_session_factory):
|
||||
async def test_delete_person_removes_links_when_linked_documents_exist(default_session_factory):
|
||||
service = DocumentService(session_factory=default_session_factory)
|
||||
|
||||
document = await service.create_document(
|
||||
@@ -172,8 +171,13 @@ async def test_delete_person_blocks_when_linked_documents_exist(default_session_
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(PersonDeleteBlockedError):
|
||||
await service.delete_person(person)
|
||||
await service.delete_person(person)
|
||||
|
||||
links = await service.list_document_people(person_id=person.id)
|
||||
assert links == []
|
||||
|
||||
with pytest.raises(DocumentError):
|
||||
await service.read_person_detail(person.id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
@@ -9,7 +10,9 @@ from transcription.db.models import Job
|
||||
from transcription.db.models import JobSource
|
||||
from transcription.db.models import Source
|
||||
from transcription.services.store import UploadError
|
||||
from transcription.services.store import create_upload_job
|
||||
from transcription.services.store import create_job_for_document
|
||||
from transcription.services.store import store_person_portrait
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -66,7 +69,52 @@ async def test_create_job_for_document_sorts_uploads_and_creates_links(async_ses
|
||||
assert [source.upload_name for source in sources] == ["A_page.pdf", "b_page.pdf"]
|
||||
assert all(source.filename.endswith(".pdf") for source in sources)
|
||||
assert all("A_page" not in source.filename and "b_page" not in source.filename for source in sources)
|
||||
assert all(Path(source.filename).stem == str(source.id) for source in sources)
|
||||
assert all(Path(source.file_path).parent == (tmp_path / "documents" / str(document.id)) for source in sources)
|
||||
|
||||
job_sources = (await async_session.exec(select(JobSource).where(JobSource.job_id == result.job_id))).all()
|
||||
assert len(job_sources) == 2
|
||||
assert set(result.source_ids) == {job_source.source_id for job_source in job_sources}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_upload_job_stores_source_under_document_id_directory(async_session, tmp_path):
|
||||
settings = Settings(openrouter_api_key="test-key", upload_dir=tmp_path)
|
||||
|
||||
result = await create_upload_job(
|
||||
filename="single-page.jpg",
|
||||
file_bytes=b"image-bytes",
|
||||
session=async_session,
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
expected_parent = tmp_path / "documents" / str(result.document_id)
|
||||
assert result.stored_path.parent == expected_parent
|
||||
assert result.stored_path.exists()
|
||||
|
||||
source = (
|
||||
await async_session.exec(
|
||||
select(Source)
|
||||
.where(Source.document_id == result.document_id)
|
||||
.order_by(Source.page_number) # pyright: ignore[reportArgumentType]
|
||||
)
|
||||
).first()
|
||||
assert source is not None
|
||||
assert Path(source.filename).stem == str(source.id)
|
||||
assert result.stored_path.name == source.filename
|
||||
assert Path(source.file_path).parent == expected_parent
|
||||
|
||||
|
||||
def test_store_person_portrait_stores_file_under_person_id_directory(tmp_path):
|
||||
settings = Settings(openrouter_api_key="test-key", upload_dir=tmp_path)
|
||||
person_id = uuid4()
|
||||
|
||||
stored_path = store_person_portrait(
|
||||
person_id=person_id,
|
||||
filename="portrait.png",
|
||||
file_bytes=b"portrait-bytes",
|
||||
settings=settings,
|
||||
)
|
||||
|
||||
assert stored_path.parent == (tmp_path / "persons" / str(person_id))
|
||||
assert stored_path.exists()
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from transcription.worker import run_worker_loop
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_worker_loop_survives_process_next_exception(monkeypatch, caplog):
|
||||
calls = 0
|
||||
stop_event = asyncio.Event()
|
||||
|
||||
async def _fake_process_next_queued_job(*, session=None, session_factory=None):
|
||||
nonlocal calls
|
||||
_ = (session, session_factory)
|
||||
calls += 1
|
||||
if calls == 1:
|
||||
raise RuntimeError("boom")
|
||||
stop_event.set()
|
||||
return False
|
||||
|
||||
monkeypatch.setattr("transcription.worker.process_next_queued_job", _fake_process_next_queued_job)
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
await run_worker_loop(stop_event=stop_event, poll_interval_seconds=0)
|
||||
|
||||
assert calls == 2
|
||||
assert "Worker loop exception" in caplog.text
|
||||
@@ -206,7 +206,7 @@ class TestPeoplePageRendering:
|
||||
assert "This action permanently deletes the person record." in response.text
|
||||
assert "Delete person permanently" in response.text
|
||||
|
||||
def test_person_delete_page_shows_blocked_state_when_linked_documents_exist(self, app_client):
|
||||
def test_person_delete_page_warns_links_will_be_removed_when_linked_documents_exist(self, app_client):
|
||||
_, client = app_client
|
||||
|
||||
async def _seed_links() -> str:
|
||||
@@ -233,6 +233,5 @@ class TestPeoplePageRendering:
|
||||
response = client.get(f"/ui/people/{person_id}/delete")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Delete is blocked because linked documents exist." in response.text
|
||||
assert "Linked documents: 1" in response.text
|
||||
assert "Go to Documents" in response.text
|
||||
assert "This will also remove 1 linked document relationship(s)." in response.text
|
||||
assert "Delete person permanently" in response.text
|
||||
|
||||
@@ -9,6 +9,7 @@ from sqlmodel import select
|
||||
from transcription.db import session_scope
|
||||
from transcription.db.models import Document
|
||||
from transcription.db.models import Job
|
||||
from transcription.db.models import JobStatus
|
||||
from transcription.db.models import Source
|
||||
|
||||
|
||||
@@ -140,6 +141,35 @@ class TestSourcesPageRendering:
|
||||
assert "Stored Filename:" in response.text
|
||||
assert "Delete Source" in response.text
|
||||
|
||||
def test_source_detail_page_displays_job_source_status_and_error_detail(self, app_client, seed_job):
|
||||
_, client = app_client
|
||||
job_id = seed_job(
|
||||
filename="failed-source.png",
|
||||
status=JobStatus.FAILED,
|
||||
transcription_text=None,
|
||||
error_detail="Provider timed out",
|
||||
)
|
||||
|
||||
async def _get_source_id() -> str:
|
||||
async with session_scope() as session:
|
||||
job = await session.get(Job, job_id)
|
||||
assert job is not None
|
||||
source = (
|
||||
await session.exec(select(Source).where(Source.document_id == job.document_id))
|
||||
).first()
|
||||
assert source is not None
|
||||
return str(source.id)
|
||||
|
||||
source_id = asyncio.run(_get_source_id())
|
||||
response = client.get(f"/ui/sources/{source_id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "JOB SOURCE OUTCOMES" in response.text
|
||||
assert "Status:" in response.text
|
||||
assert "failed" in response.text.lower()
|
||||
assert "Error Detail:" in response.text
|
||||
assert "Provider timed out" in response.text
|
||||
|
||||
def test_source_delete_page_blocks_when_source_is_job_linked(self, app_client, seed_job):
|
||||
_, client = app_client
|
||||
job_id = seed_job(filename="linked-source.png", transcription_text="linked text")
|
||||
|
||||
Reference in New Issue
Block a user