diff --git a/document-parser/services/analysis_service.py b/document-parser/services/analysis_service.py index f630e95..36c2f5a 100644 --- a/document-parser/services/analysis_service.py +++ b/document-parser/services/analysis_service.py @@ -232,7 +232,7 @@ def _on_task_done(task: asyncio.Task, *, job_id: str) -> None: exc = task.exception() if exc: logger.error("Unhandled exception in analysis task %s: %s", job_id, exc, exc_info=True) - _schedule_mark_failed(job_id, str(exc)) + _schedule_mark_failed(job_id, _classify_error(exc)) def _schedule_mark_failed(job_id: str, error: str) -> None: diff --git a/document-parser/tests/test_analysis_service.py b/document-parser/tests/test_analysis_service.py index 83f6be8..66cbaac 100644 --- a/document-parser/tests/test_analysis_service.py +++ b/document-parser/tests/test_analysis_service.py @@ -20,7 +20,7 @@ class TestOnTaskDone: # Create a task that raises async def failing_task(): - raise RuntimeError("boom") + raise RuntimeError("unexpected failure") task = asyncio.create_task(failing_task()) await asyncio.sleep(0) # let the task fail @@ -30,7 +30,26 @@ class TestOnTaskDone: # ensure_future schedules it; give the event loop a tick await asyncio.sleep(0) - mock_mark.assert_called_once_with(job_id, "boom") + mock_mark.assert_called_once_with(job_id, "unexpected failure") + + @pytest.mark.asyncio + async def test_exception_uses_classify_error(self): + """_on_task_done should route exceptions through _classify_error.""" + job_id = "job-classify" + + async def timeout_task(): + raise TimeoutError("timeout exceeded while processing") + + task = asyncio.create_task(timeout_task()) + await asyncio.sleep(0) + + with patch("services.analysis_service._mark_failed", new_callable=AsyncMock) as mock_mark: + _on_task_done(task, job_id=job_id) + await asyncio.sleep(0) + + mock_mark.assert_called_once_with( + job_id, "Processing took too long — try with fewer pages or simpler options" + ) @pytest.mark.asyncio async def test_cancelled_task_marks_job_failed(self):