| 1 |
"""`mint_ulid` — 26-char Crockford base32 + monotonicity (Sprint 13).""" |
| 2 |
|
| 3 |
from __future__ import annotations |
| 4 |
|
| 5 |
import re |
| 6 |
|
| 7 |
from dlm.io.ulid import mint_ulid |
| 8 |
|
| 9 |
_ULID_RE = re.compile(r"^[0-9A-HJKMNPQRSTVWXYZ]{26}$") |
| 10 |
|
| 11 |
|
| 12 |
class TestShape: |
| 13 |
def test_returns_26_chars(self) -> None: |
| 14 |
assert len(mint_ulid()) == 26 |
| 15 |
|
| 16 |
def test_matches_crockford_regex(self) -> None: |
| 17 |
for _ in range(100): |
| 18 |
assert _ULID_RE.fullmatch(mint_ulid()) |
| 19 |
|
| 20 |
def test_uppercase_only(self) -> None: |
| 21 |
ulid = mint_ulid() |
| 22 |
assert ulid == ulid.upper() |
| 23 |
|
| 24 |
|
| 25 |
class TestUniqueness: |
| 26 |
def test_two_consecutive_calls_differ(self) -> None: |
| 27 |
"""80 bits of entropy per call; collision probability is negligible.""" |
| 28 |
assert mint_ulid() != mint_ulid() |
| 29 |
|
| 30 |
def test_one_thousand_distinct(self) -> None: |
| 31 |
ulids = {mint_ulid() for _ in range(1000)} |
| 32 |
assert len(ulids) == 1000 |
| 33 |
|
| 34 |
|
| 35 |
class TestSortability: |
| 36 |
def test_later_ulid_sorts_greater(self) -> None: |
| 37 |
"""Timestamp prefix means lexicographic order ~ time order. |
| 38 |
|
| 39 |
Sleep is risky in tests; use `time.time` mocking-free assertion |
| 40 |
via the 48-bit timestamp prefix: two calls spaced by a second's |
| 41 |
worth of monotonic progress should sort in that order. |
| 42 |
""" |
| 43 |
import time |
| 44 |
|
| 45 |
a = mint_ulid() |
| 46 |
time.sleep(0.002) |
| 47 |
b = mint_ulid() |
| 48 |
# First 10 chars encode the timestamp (48 bits → 10 Crockford chars). |
| 49 |
# Even two milliseconds apart the prefix must not regress. |
| 50 |
assert a[:10] <= b[:10] |
| 51 |
|
| 52 |
|
| 53 |
class TestInternals: |
| 54 |
def test_wrong_payload_size_raises(self) -> None: |
| 55 |
"""`_encode_crockford` rejects anything other than exactly 16 bytes.""" |
| 56 |
import pytest |
| 57 |
|
| 58 |
from dlm.io.ulid import _encode_crockford |
| 59 |
|
| 60 |
with pytest.raises(ValueError, match="16 bytes"): |
| 61 |
_encode_crockford(b"\x00" * 15) |
| 62 |
with pytest.raises(ValueError, match="16 bytes"): |
| 63 |
_encode_crockford(b"\x00" * 17) |
| 64 |
|
| 65 |
|
| 66 |
class TestValidatorCompat: |
| 67 |
def test_accepted_by_frontmatter_validator(self) -> None: |
| 68 |
"""Round-trip: mint → validate against the schema's ULID regex.""" |
| 69 |
from dlm.doc.schema import DlmFrontmatter |
| 70 |
|
| 71 |
for _ in range(50): |
| 72 |
fm = DlmFrontmatter.model_validate( |
| 73 |
{"dlm_id": mint_ulid(), "base_model": "smollm2-135m"} |
| 74 |
) |
| 75 |
assert fm.dlm_id |