"""Phase 9 unit tests — BlobLocator/ClobLocator value semantics. These don't require Informix; they exercise the typed locator wrappers directly to verify shape, immutability, equality, and repr safety. """ from __future__ import annotations import dataclasses import pytest from informix_db import BlobLocator, ClobLocator def test_blob_locator_holds_72_bytes() -> None: raw = bytes(range(72)) loc = BlobLocator(raw=raw) assert loc.raw == raw assert len(loc.raw) == 72 def test_clob_locator_holds_72_bytes() -> None: raw = bytes(reversed(range(72))) loc = ClobLocator(raw=raw) assert loc.raw == raw @pytest.mark.parametrize("size", [0, 1, 71, 73, 144]) def test_locator_rejects_wrong_size(size: int) -> None: """The constructor enforces exactly 72 bytes.""" with pytest.raises(ValueError, match="72 bytes"): BlobLocator(raw=bytes(size)) with pytest.raises(ValueError, match="72 bytes"): ClobLocator(raw=bytes(size)) def test_locator_is_frozen() -> None: """Instances are immutable per ``frozen=True`` dataclass decorator.""" loc = BlobLocator(raw=bytes(72)) with pytest.raises(dataclasses.FrozenInstanceError): loc.raw = b"x" * 72 # type: ignore[misc] def test_blob_and_clob_locator_are_distinct_types() -> None: """Same-bytes locators of different families compare unequal.""" raw = bytes(72) blob = BlobLocator(raw=raw) clob = ClobLocator(raw=raw) assert blob != clob assert not isinstance(blob, ClobLocator) assert not isinstance(clob, BlobLocator) def test_locator_equality() -> None: """Same bytes + same family → equal.""" raw = b"\x01\x02\x03" + bytes(69) a = BlobLocator(raw=raw) b = BlobLocator(raw=raw) assert a == b assert hash(a) == hash(b) def test_locator_repr_omits_raw_bytes() -> None: """``repr`` doesn't leak the opaque locator bytes (no use to user).""" raw = b"\xde\xad\xbe\xef" + bytes(68) loc = BlobLocator(raw=raw) r = repr(loc) assert "BlobLocator" in r assert "deadbeef" not in r.lower() assert raw.hex() not in r