Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion quiabo/jobs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Routes for submitting and managing OCR jobs."""

import traceback

from pathlib import Path

from celery.result import AsyncResult
from flask import Blueprint, request

from quiabo.tasks import run_tesseract_job
Expand Down Expand Up @@ -51,6 +52,26 @@ def create_job():
"job_status": f"/jobs/{celery_result.id}",
}, 202

@bp.get("/<job_id>")
def get_job(job_id: str):
"""Get the status of an OCR job."""
celery_result = AsyncResult(job_id)

response = {
"id": job_id,
"status": celery_result.state,
}

if celery_result.state == "SUCCESS":
response["result"] = celery_result.result
response["date_done"] = celery_result.date_done.isoformat()

if celery_result.state == "FAILURE":
response["result"] = str(celery_result.result)
response["traceback"] = celery_result.traceback

return response, 200


def _error_response(error: Exception, status_code: int):
"""Build error response."""
Expand Down
77 changes: 77 additions & 0 deletions test/unit/test_jobs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Tests for the jobs endpoints."""
from datetime import datetime
from types import SimpleNamespace
from unittest.mock import Mock


Expand Down Expand Up @@ -169,3 +171,78 @@ def test_create_job_requires_filelist_to_be_file(client, tmp_path):
assert response.json["status"] == "ERROR"
assert "does not exist or is not a file" in response.json["result"]
assert "traceback" in response.json

def test_get_job_route_exists(client):
"""GET /jobs/<job_id> is a valid route."""
response = client.get("/jobs/test-job-id")

assert response.status_code != 404

def test_get_job_returns_celery_status(client, monkeypatch):
"""GET /jobs/<job_id> returns job's Celery status."""
fake_result = SimpleNamespace(state="STARTED")

monkeypatch.setattr(
"quiabo.jobs.AsyncResult",
lambda job_id: fake_result,
)

response = client.get("/jobs/test-job-id")

assert response.status_code == 200
assert response.json == {
"id": "test-job-id",
"status": "STARTED",
}

def test_get_successful_job_returns_result(client, monkeypatch):
"""GET a successful job returns its result and completion date."""
fake_result = SimpleNamespace(
state="SUCCESS",
result={
"output_path": "/app/files/test.pdf",
"sha256": "abc123",
},
date_done=datetime(2026, 9, 28, 16, 0, 0),
)

monkeypatch.setattr(
"quiabo.jobs.AsyncResult",
lambda job_id: fake_result,
)

response = client.get("/jobs/test-job-id")

assert response.status_code == 200
assert response.json == {
"id": "test-job-id",
"status": "SUCCESS",
"result": {
"output_path": "/app/files/test.pdf",
"sha256": "abc123",
},
"date_done": "2026-09-28T16:00:00",
}

def test_get_failed_job_returns_error(client, monkeypatch):
"""GET a failed job returns its error and traceback."""
fake_result = SimpleNamespace(
state="FAILURE",
result="Tesseract failed",
traceback="Traceback: something went horribly wrong",
)

monkeypatch.setattr(
"quiabo.jobs.AsyncResult",
lambda job_id: fake_result,
)

response = client.get("/jobs/test-job-id")

assert response.status_code == 200
assert response.json == {
"id": "test-job-id",
"status": "FAILURE",
"result": "Tesseract failed",
"traceback": "Traceback: something went horribly wrong",
}
Loading