generated from john/python-template
102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
import pytest
|
|
|
|
from transcription.errors import AppError
|
|
from transcription.errors import ErrorCategory
|
|
from transcription.ui.components.error_presenter import display_error_category
|
|
from transcription.ui.components.error_presenter import display_failure_detail
|
|
from transcription.ui.components.error_presenter import run_ui_action
|
|
|
|
|
|
def test_display_error_category_uses_canonical_taxonomy():
|
|
assert display_error_category(AppError("x", category=ErrorCategory.USER_INPUT)) == "validation"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.NOT_FOUND)) == "not_found"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.CONFLICT)) == "conflict"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.EXTERNAL_PROVIDER)) == "external"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.EXTERNAL_TIMEOUT)) == "timeout"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.INFRA_TRANSIENT)) == "timeout"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.PROCESSING)) == "internal"
|
|
assert display_error_category(AppError("x", category=ErrorCategory.INFRA_PERSISTENT)) == "internal"
|
|
|
|
|
|
def test_display_failure_detail_returns_none_for_missing_detail():
|
|
assert display_failure_detail(None) is None
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("raw_detail", "expected_basename"),
|
|
[
|
|
(
|
|
(
|
|
"[internal_unexpected_error] Unexpected error during worker.process_job. | "
|
|
"detail=PermissionError: [Errno 13] Permission denied: '/app/uploads/documents/abc/page-1.jpg' | "
|
|
"suggestion=Retry once. | error_id=deadbeef"
|
|
),
|
|
"page-1.jpg",
|
|
),
|
|
(
|
|
(
|
|
"[infrastructure_persistent_error] Prompt file not found: transcribe_document.md | "
|
|
"detail=FileNotFoundError: [Errno 2] No such file or directory: "
|
|
r"'C:\app\prompts\transcribe_document.md' | "
|
|
"suggestion=Verify PROMPT_DIR. | error_id=feedface"
|
|
),
|
|
"transcribe_document.md",
|
|
),
|
|
],
|
|
)
|
|
def test_display_failure_detail_strips_absolute_paths_from_detail_segment(raw_detail, expected_basename):
|
|
rendered = display_failure_detail(raw_detail)
|
|
|
|
assert rendered is not None
|
|
assert rendered.startswith("[")
|
|
assert "suggestion=" in rendered
|
|
assert "error_id=" in rendered
|
|
assert expected_basename in rendered
|
|
assert "/app/uploads/documents/abc/page-1.jpg" not in rendered
|
|
assert r"C:\app\prompts\transcribe_document.md" not in rendered
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_ui_action_returns_success_value():
|
|
outcome = await run_ui_action(
|
|
operation="ui.test.success",
|
|
title="Should not show",
|
|
action=lambda: _async_value(123),
|
|
)
|
|
|
|
assert outcome.ok is True
|
|
assert outcome.value == 123
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_ui_action_shows_error_and_returns_failed(monkeypatch):
|
|
captured: dict[str, object] = {}
|
|
|
|
def _capture(exc: Exception, *, title: str, operation: str) -> None:
|
|
captured["exc"] = exc
|
|
captured["title"] = title
|
|
captured["operation"] = operation
|
|
|
|
monkeypatch.setattr("transcription.ui.components.error_presenter.show_error", _capture)
|
|
|
|
outcome = await run_ui_action(
|
|
operation="ui.test.failure",
|
|
title="Load failed",
|
|
action=lambda: _async_raises(RuntimeError("boom")),
|
|
)
|
|
|
|
assert outcome.ok is False
|
|
assert outcome.value is None
|
|
assert isinstance(captured["exc"], RuntimeError)
|
|
assert str(captured["exc"]) == "boom"
|
|
assert captured["title"] == "Load failed"
|
|
assert captured["operation"] == "ui.test.failure"
|
|
|
|
|
|
async def _async_value(value: int) -> int:
|
|
return value
|
|
|
|
|
|
async def _async_raises(exc: Exception) -> int:
|
|
raise exc
|