Files
roboco/tests/unit/utils/test_converters.py
T
Renn F 6b441e421d [chore] converters: typed InvalidIdentifierError from require_uuid + log the orchestrator drop (#25)
require_uuid raised a bare ValueError('UUID value cannot be None'), so a
malformed/None identifier propagated as an opaque error callers either let
500 or broad-catch-and-silently-swallow — the orchestrator reaper call site
wrapped it in a bare except-Exception return with NO log, dropping a bad
task_id_str invisibly. Introduce InvalidIdentifierError(ValueError) and
raise it from require_uuid for both None and unparseable input; it stays a
ValueError subclass so existing except-ValueError / except-Exception callers
are unaffected, but typed so a caller can handle a bad identifier distinctly.
The reaper now catches the typed error, logs at warning, and no-ops — the
drop is visible instead of swallowed.

Tests: None and an unparseable string both raise InvalidIdentifierError; it
subclasses ValueError (back-comat).
2026-06-30 19:25:02 +02:00

78 lines
2.2 KiB
Python

"""utils.converters coverage."""
from __future__ import annotations
from uuid import uuid4
import pytest
from roboco.utils.converters import (
InvalidIdentifierError,
require_uuid,
to_python_uuid,
to_python_uuid_list,
)
def test_require_uuid_passes_through() -> None:
u = uuid4()
assert require_uuid(u) is u
def test_require_uuid_parses_string() -> None:
u = uuid4()
assert require_uuid(str(u)) == u
def test_require_uuid_raises_for_none() -> None:
with pytest.raises(ValueError, match="cannot be None"):
require_uuid(None)
def test_require_uuid_none_raises_typed_identifier_error() -> None:
"""#25: a None identifier raises the typed InvalidIdentifierError (a
ValueError subclass), not a bare ValueError, so callers can distinguish a
bad identifier from any other exception instead of broad-catching."""
with pytest.raises(InvalidIdentifierError, match="cannot be None"):
require_uuid(None)
def test_require_uuid_unparseable_raises_typed_identifier_error() -> None:
"""#25: an unparseable identifier raises InvalidIdentifierError, not a bare
ValueError — the dropped-identifier pattern must surface as a typed error."""
with pytest.raises(InvalidIdentifierError, match="invalid UUID identifier"):
require_uuid("not-a-uuid")
def test_invalid_identifier_error_is_value_error() -> None:
"""Back-compat: InvalidIdentifierError subclasses ValueError so existing
`except ValueError` callers keep catching it."""
assert issubclass(InvalidIdentifierError, ValueError)
def test_to_python_uuid_returns_none_for_none() -> None:
assert to_python_uuid(None) is None
def test_to_python_uuid_passes_through_uuid() -> None:
u = uuid4()
assert to_python_uuid(u) is u
def test_to_python_uuid_parses_string() -> None:
u = uuid4()
assert to_python_uuid(str(u)) == u
def test_to_python_uuid_list_returns_empty_for_none() -> None:
assert to_python_uuid_list(None) == []
def test_to_python_uuid_list_converts_strings() -> None:
u1, u2 = uuid4(), uuid4()
result = to_python_uuid_list([str(u1), str(u2)])
assert result == [u1, u2]
def test_to_python_uuid_list_empty() -> None:
assert to_python_uuid_list([]) == []