|
3 | 3 | import io |
4 | 4 |
|
5 | 5 | import pytest |
| 6 | +from pydantic import ValidationError |
6 | 7 |
|
7 | 8 | from lean_spec.spec.ssz.collections import SSZList |
8 | 9 | from lean_spec.spec.ssz.container import Container |
@@ -380,3 +381,50 @@ def test_from_hex_bad_hex_raises_value_error(self) -> None: |
380 | 381 | """Non-hex characters surface a ValueError from the underlying parser.""" |
381 | 382 | with pytest.raises(ValueError, match="non-hexadecimal number"): |
382 | 383 | OneByte.from_hex("zz") |
| 384 | + |
| 385 | + |
| 386 | +class TestHexStringValidator: |
| 387 | + """Pydantic validation accepts hex strings via the wrap validator.""" |
| 388 | + |
| 389 | + @pytest.mark.parametrize( |
| 390 | + "hex_input", |
| 391 | + [ |
| 392 | + pytest.param("0xab", id="with_prefix"), |
| 393 | + pytest.param("ab", id="without_prefix"), |
| 394 | + pytest.param("0xAB", id="uppercase_with_prefix"), |
| 395 | + ], |
| 396 | + ) |
| 397 | + def test_validates_hex_string(self, hex_input: str) -> None: |
| 398 | + """Pydantic validation tolerates the 0x prefix and mixed case alike.""" |
| 399 | + assert OneByte.model_validate(hex_input) == OneByte(a=Uint8(0xAB)) |
| 400 | + |
| 401 | + def test_validates_empty_string_as_empty_container(self) -> None: |
| 402 | + """An empty hex string validates to a zero-field container.""" |
| 403 | + assert EmptyContainer.model_validate("") == EmptyContainer() |
| 404 | + |
| 405 | + def test_dict_input_routes_to_field_validation(self) -> None: |
| 406 | + """A dict input goes through field-by-field validation, not hex decoding.""" |
| 407 | + assert OneByte.model_validate({"a": Uint8(0xAB)}) == OneByte(a=Uint8(0xAB)) |
| 408 | + |
| 409 | + def test_instance_input_passes_through(self) -> None: |
| 410 | + """An existing instance input is returned unchanged.""" |
| 411 | + instance = OneByte(a=Uint8(0xAB)) |
| 412 | + assert OneByte.model_validate(instance) == instance |
| 413 | + |
| 414 | + def test_wrong_length_hex_raises_with_class_name(self) -> None: |
| 415 | + """Hex with too many bytes raises a validation error tagged by the class name.""" |
| 416 | + # 2 hex bytes ("abcd") cannot fit a 1-byte container; trailing bytes trigger the error. |
| 417 | + with pytest.raises(ValidationError, match="invalid OneByte hex"): |
| 418 | + OneByte.model_validate("abcd") |
| 419 | + |
| 420 | + def test_nested_container_field_accepts_hex_string(self) -> None: |
| 421 | + """A nested container field accepts a hex string for its own SSZ encoding.""" |
| 422 | + # Fixture state: |
| 423 | + # inner.x (Uint64) = 1, inner.y (Uint64) = 2 -> 16 little-endian bytes |
| 424 | + outer = OuterFixedNested.model_validate( |
| 425 | + { |
| 426 | + "z": Uint64(7), |
| 427 | + "inner": "01000000000000000200000000000000", |
| 428 | + } |
| 429 | + ) |
| 430 | + assert outer == OuterFixedNested(z=Uint64(7), inner=InnerFixed(x=Uint64(1), y=Uint64(2))) |
0 commit comments