V6.1 UI refinements, add Maintenance jobs to Settings
Quality Gate / gate (push) Failing after 2m57s

This commit is contained in:
Jim Lancaster
2026-08-31 11:31:25 -05:00
parent daa1642933
commit 9990583345
28 changed files with 1056 additions and 174 deletions
@@ -0,0 +1,59 @@
"""Tests for maintenance run persistence and execution lifecycle."""
from __future__ import annotations
from pathlib import Path
import pytest
from transcription.config import Settings
from transcription.db.models import MaintenanceJobType
from transcription.db.models import MaintenanceRunStatus
from transcription.services.maintenance import MaintenanceExecution
from transcription.services.maintenance import MaintenanceService
@pytest.mark.asyncio
async def test_enqueue_and_list_runs(default_session_factory, default_settings):
service = MaintenanceService(session_factory=default_session_factory, settings=default_settings)
first = await service.enqueue_run(job_type=MaintenanceJobType.BACKUP, triggered_by="test")
second = await service.enqueue_run(job_type=MaintenanceJobType.STORAGE_RECONCILIATION, triggered_by="test")
runs = await service.list_runs(limit=10)
assert len(runs) == 2
assert runs[0].id == second.id
assert runs[1].id == first.id
assert runs[0].status == MaintenanceRunStatus.QUEUED
@pytest.mark.asyncio
async def test_process_next_queued_run_persists_terminal_result(
default_session_factory,
default_settings,
tmp_path,
monkeypatch,
):
settings = default_settings.model_copy(update={"log_dir": tmp_path / "logs"})
settings = Settings.model_validate(settings.model_dump())
service = MaintenanceService(session_factory=default_session_factory, settings=settings)
queued = await service.enqueue_run(job_type=MaintenanceJobType.BACKUP, triggered_by="test")
async def _fake_execute(_run):
return MaintenanceExecution(
status=MaintenanceRunStatus.SUCCEEDED,
summary="Synthetic success",
output="stdout line\nstderr line",
)
monkeypatch.setattr(service, "_execute_run", _fake_execute)
processed = await service.process_next_queued_run()
assert processed is True
runs = await service.list_runs(limit=10)
updated = next(run for run in runs if run.id == queued.id)
assert updated.status == MaintenanceRunStatus.SUCCEEDED
assert updated.summary == "Synthetic success"
assert updated.log_path is not None
assert (settings.log_dir / Path(updated.log_path)).is_file()
+51
View File
@@ -204,3 +204,54 @@ async def test_process_next_leaves_a_caller_owned_bundle_open(monkeypatch):
assert await process_next_queued_job(services=bundle) is False
assert closed is False
@pytest.mark.asyncio
async def test_run_worker_loop_processes_queued_maintenance_runs(monkeypatch):
stop_event = asyncio.Event()
maintenance_calls = 0
class _Sources:
async def aclose(self):
return
class _Maintenance:
async def process_next_queued_run(self):
nonlocal maintenance_calls
maintenance_calls += 1
if maintenance_calls == 1:
return True
stop_event.set()
return False
class _Jobs:
settings = Settings(openrouter_api_key="test-key", worker_stale_job_seconds=120.0)
async def requeue_stale_processing_jobs(self, *, stale_before, session=None):
_ = (stale_before, session)
return 0
class _Bundle:
def __init__(self):
self.jobs = _Jobs()
self.sources = cast("SourceService", _Sources())
self.maintenance = _Maintenance()
async def aclose(self):
await self.sources.aclose()
monkeypatch.setattr(
"transcription.worker.ServiceBundle.from_session_factory",
classmethod(lambda _cls, _factory=None, **_kwargs: cast(ServiceBundle, _Bundle())),
)
async def _no_jobs(*, session=None, session_factory=None, services=None):
_ = (session, session_factory, services)
return False
monkeypatch.setattr("transcription.worker.process_next_queued_job", _no_jobs)
fake_session_factory = cast(async_sessionmaker[AsyncSession], object())
await run_worker_loop(stop_event=stop_event, poll_interval_seconds=0, session_factory=fake_session_factory)
assert maintenance_calls >= 2
+15 -8
View File
@@ -103,7 +103,8 @@ class TestDocumentsPageRendering:
r'"name":"tags","label":"Tags".*'
r'"name":"document_date","label":"Document Date".*'
r'"name":"document_type","label":"Type".*'
r'"name":"source_count","label":"# Sources"',
r'"name":"source_count","label":"# Sources".*'
r'"name":"transcription_status","label":"Transcription Status"',
response.text,
re.DOTALL,
)
@@ -181,17 +182,23 @@ class TestDocumentsPageRendering:
assert response.status_code == 200
assert "Letter from Hig" in response.text
assert "ZC-1924-001" in response.text
assert "Zenna Cochran" in response.text
assert "Document Type:" in response.text
assert "Letter" in response.text
assert "07-04-1924" in response.text
assert "1924-07-04" not in response.text
assert "google.com/maps/search/?api=1&query=Salt+Lake+City%2C+Utah" in response.text
assert "PIPELINE JOBS" in response.text.upper()
assert "Document Details" in response.text
assert "Edit Document" in response.text
assert "Back to Documents" in response.text
@pytest.mark.asyncio
async def test_document_info_page_renders_metadata_cards(self, app_client, seed_person_and_document):
_, client = app_client
doc_id, _ = seed_person_and_document
response = client.get(f"/ui/documents/{doc_id}/info")
assert response.status_code == 200
assert "Document Info" in response.text
assert "ZC-1924-001" in response.text
assert "google.com/maps/search/?api=1&query=Salt+Lake+City%2C+Utah" in response.text
@pytest.mark.asyncio
async def test_document_detail_page_renders_person_context_back_button(self, app_client, seed_person_and_document):
_, client = app_client
-2
View File
@@ -28,9 +28,7 @@ class TestNavigationAndMounts:
"/ui/homepage",
"/ui/homepage/edit",
"/ui/documents",
"/ui/tags",
"/ui/people",
"/ui/sources",
"/ui/jobs",
"/ui/settings",
],
+1 -4
View File
@@ -14,17 +14,13 @@ class TestPageRegistration:
homepage_response = client.get("/ui/homepage")
documents_response = client.get("/ui/documents")
people_response = client.get("/ui/people")
sources_response = client.get("/ui/sources")
jobs_response = client.get("/ui/jobs")
tags_response = client.get("/ui/tags")
settings_response = client.get("/ui/settings")
assert homepage_response.status_code == 200
assert documents_response.status_code == 200
assert people_response.status_code == 200
assert sources_response.status_code == 200
assert jobs_response.status_code == 200
assert tags_response.status_code == 200
assert settings_response.status_code == 200
assert "Runtime Settings" in settings_response.text
assert "Document Types" in settings_response.text
@@ -32,5 +28,6 @@ class TestPageRegistration:
assert "Tags" in settings_response.text
assert "Prompts" in settings_response.text
assert "Home Page Text" in settings_response.text
assert "Maintenance" in settings_response.text
assert "Other settings not shown here" in settings_response.text
assert "README.md" not in settings_response.text
+1
View File
@@ -306,6 +306,7 @@ class TestPeoplePageRendering:
assert response.status_code == 200
assert "Document Name" in response.text
assert "Document Date" in response.text
assert "Number of Pages" in response.text
assert "Linked Document" in response.text
assert "Author" in response.text
+6 -6
View File
@@ -165,12 +165,10 @@ class TestSourcesPageRendering:
def test_sources_page_renders_empty_state(self, app_client):
_, client = app_client
response = client.get("/ui/sources")
response = client.get("/ui/sources", follow_redirects=False)
assert response.status_code == 200
assert "Source Asset Records" in response.text
assert "No source asset records found in repository." in response.text
assert "Upload New Documents" not in response.text
assert response.status_code == 307
assert response.headers["location"] == "/ui/documents"
@pytest.mark.asyncio
async def test_sources_page_lists_seeded_sources(self, app_client):
@@ -192,10 +190,12 @@ class TestSourcesPageRendering:
)
)
await session.commit()
document_id = str(document.id)
response = client.get("/ui/sources")
response = client.get(f"/ui/sources?document_id={document_id}")
assert response.status_code == 200
assert "Sources for Document" in response.text
assert "page_one.png" in response.text
assert "Source Document" in response.text
assert "Stored Filename" not in response.text
+4 -34
View File
@@ -1,43 +1,13 @@
"""Tests for the tags page route and grouped filtering behavior."""
"""Tags route retirement guards."""
import pytest
from transcription.db import session_scope
from transcription.db.models import Document
from transcription.services.documents import DocumentService
@pytest.mark.integration
class TestTagsPageRendering:
def test_tags_page_renders_empty_state_without_tags(self, app_client):
class TestTagsPageRetired:
def test_tags_page_route_is_not_registered(self, app_client):
_, client = app_client
response = client.get("/ui/tags")
assert response.status_code == 200
assert "Tags" in response.text
assert "No tags are configured yet." in response.text
@pytest.mark.asyncio
async def test_tags_page_groups_documents_by_tag(self, app_client):
app, client = app_client
documents = DocumentService(session_factory=app.state.runtime.session_factory)
async with session_scope(session_factory=app.state.runtime.session_factory) as session:
first = Document(name="Tagged Letter")
second = Document(name="Tagged Journal")
session.add_all([first, second])
await session.flush()
await documents.sync_document_tags_by_labels(document_id=first.id, labels=["Family"], session=session)
await documents.sync_document_tags_by_labels(
document_id=second.id,
labels=["Family", "Research"],
session=session,
)
await session.commit()
response = client.get("/ui/tags")
assert response.status_code == 200
assert "Filter by tag" in response.text
assert "Tags" in response.text
assert response.status_code == 404