Add worker and job lifecycle tests #10
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The worker runtime, AI handler, and OCR handler are new code in this milestone
handling durable state and external API calls. They need their own coverage.
Scope
Tests for job lifecycle, retry behaviour, idempotency, and append-only evidence.
Implementation notes
queued->running->success, withstarted_atandfinished_atstamped andresult_summarywritten.retry_countincrements, job returns toqueued,and after
max_retriesit lands infailedwitherror_messageset.idempotency_keyreturns the existingjob instead of raising a unique-constraint 500.
evidence rows exist and the first is untouched. Same for OCR.
Tesseract installed.
runningfrom a previous process is requeued or failedon startup, not stranded.
Done when
References
backend/app/workers/backend/app/models/models.py(BackgroundJob)Depends on: the background worker runtime, the AI job handler, the OCR job handler, and the backend test harness.
Done in
44a3455.Most of this was already covered
Landed with #2, #3, #4 and #82, in
test_worker_runtime.py(31 tests),test_ai_handler.py(28),test_ocr_handler.py(32) andtest_evidence_chain_and_reruns.py:queued → running → success, withstarted_at,finished_at,result_summaryand the claim releasedretry_countincrements → requeued → exhaustion →failedwith a readableerror_message, plusPermanentJobErrornot retriedTwo things were genuinely missing.
1. "No test performs a real network call" was a docstring
Which is true until someone adds a test that reaches for the real thing. Not hypothetical here: this codebase calls a paid vision API, so an accidentally-live test bills money on every CI run and turns a provider outage into a red build.
It is now an autouse fixture that refuses outbound
connectandconnect_ex, allowing loopback, with@pytest.mark.networkas the opt-in that the two live-API suites carry (they were already skipped without credentials — the marker records the intent rather than granting it).Two choices worth naming:
connect_extoo. It returns an error code rather than raising, so a client using it would have walked straight past a guard onconnectalone.test_harness_isolation.pyasserts the guard works, that loopback still does, and that the marker really opts out.2.
JobStatus.cancelledexisted and nothing set itNo route, no CLI, no repository method — it has been in the enum since the first migration with neither a producer nor a consumer. That is a fair state for Phase 1 to be in. What is not fair is the worker being undefined against a status that already exists in the schema, and which an operator can set by hand today to stop a job that is misbehaving.
Three guarantees pinned:
successover it — leaving a job that was cancelled, reported as completed, with evidence in the archive to match.All three hold today, for load-bearing reasons rather than by accident: the claim is
UPDATE ... WHERE status = 'queued', and_finalizere-checks both owner and status before writing. Which is precisely why they were worth stating before something depends on them.Startup recovery also now runs through
run_foreverrather than callingreclaim_lapsed()by hand — "recovered at startup" is the claim, and calling the sweep directly would prove only that the sweep works.Done when
965 passed, 8 skipped; ruff clean.