Add structured preview exception hierarchy
PreviewError subclasses carry a concise short label for UI, a full log message, backend name, and metadata fields (OnlyOffice code/status/url, backend stage, timeout seconds, cancel reason). Exceptions travel across the worker pool wire pickled in the binary response payload (ok=False) and are re-raised with their original type on the caller side. Combined pipelines such as pdf+pyvips tag the failing stage.
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""Tests for OnlyOffice error handling and structured responses."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from mediapreview import office
|
||||
from mediapreview.exceptions import (
|
||||
OnlyOfficeError,
|
||||
PreviewBackendError,
|
||||
backend_error,
|
||||
onlyoffice_error_from_code,
|
||||
onlyoffice_http_error,
|
||||
onlyoffice_no_fileurl_error,
|
||||
onlyoffice_unavailable_error,
|
||||
)
|
||||
from mediapreview.pool import generate_office_preview
|
||||
|
||||
FILES = Path(__file__).resolve().parent / "files"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("code", "short", "message"),
|
||||
[
|
||||
("-8", "jwt error", "OnlyOffice JWT authentication failed"),
|
||||
("-4", "input error", "OnlyOffice input error"),
|
||||
("-2", "timeout error", "OnlyOffice conversion timed out"),
|
||||
("-1", "unknown error", "OnlyOffice conversion failed with unknown error"),
|
||||
("-99", "-99 error", "OnlyOffice conversion failed: -99"),
|
||||
(None, "unknown error", "OnlyOffice conversion failed with unknown error"),
|
||||
],
|
||||
)
|
||||
def test_onlyoffice_error_from_code(code, short, message):
|
||||
"""Conversion status codes map to clean labels and messages."""
|
||||
err = onlyoffice_error_from_code(code)
|
||||
assert str(err) == message
|
||||
assert err.short == short
|
||||
assert err.code == code
|
||||
assert err.backend == "onlyoffice"
|
||||
assert isinstance(err, OnlyOfficeError)
|
||||
|
||||
|
||||
def test_onlyoffice_http_error():
|
||||
err = onlyoffice_http_error(502)
|
||||
assert str(err) == "OnlyOffice HTTP error: 502"
|
||||
assert err.status == 502
|
||||
assert err.short == "http error"
|
||||
|
||||
|
||||
def test_onlyoffice_unavailable_error():
|
||||
err = onlyoffice_unavailable_error("http://localhost:8988")
|
||||
assert "http://localhost:8988" in str(err)
|
||||
assert err.url == "http://localhost:8988"
|
||||
assert err.short == "unavailable"
|
||||
|
||||
|
||||
def test_onlyoffice_no_fileurl_error():
|
||||
err = onlyoffice_no_fileurl_error("<empty />")
|
||||
assert "<empty />" in str(err)
|
||||
assert err.snippet == "<empty />"
|
||||
assert err.short == "no-fileurl error"
|
||||
|
||||
|
||||
def test_backend_error_stage():
|
||||
"""Combined pipelines tag the failing stage."""
|
||||
err = backend_error("pdf+pyvips", "cannot read document", stage="pdf")
|
||||
assert err.backend == "pdf+pyvips"
|
||||
assert err.stage == "pdf"
|
||||
assert err.short == "cannot read document"
|
||||
assert isinstance(err, PreviewBackendError)
|
||||
|
||||
|
||||
def test_error_pickle_round_trip():
|
||||
"""Exceptions survive pickling (the worker pool wire) intact."""
|
||||
err = onlyoffice_error_from_code("-8")
|
||||
restored = pickle.loads(pickle.dumps(err))
|
||||
assert type(restored) is type(err)
|
||||
assert str(restored) == str(err)
|
||||
assert restored.code == err.code
|
||||
assert restored.short == err.short
|
||||
assert restored.backend == err.backend
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_office_preview_raises_structured_error(monkeypatch):
|
||||
"""On OnlyOffice failure, generate_office_preview raises OnlyOfficeError."""
|
||||
async def fake_convert(_filepath: Path, request_timeout: float = 5.0) -> bytes:
|
||||
raise onlyoffice_error_from_code("-8")
|
||||
|
||||
monkeypatch.setattr(office, "convert_to_png_async", fake_convert)
|
||||
|
||||
with pytest.raises(OnlyOfficeError) as exc_info:
|
||||
await generate_office_preview(
|
||||
FILES / "file-sample_100kB.docx",
|
||||
quality=60,
|
||||
maxsize=512,
|
||||
maxzoom=2.0,
|
||||
)
|
||||
|
||||
err = exc_info.value
|
||||
assert err.code == "-8"
|
||||
assert err.short == "jwt error"
|
||||
assert str(err) == "OnlyOffice JWT authentication failed"
|
||||
Reference in New Issue
Block a user