Python · 2377 bytes Raw Blame History
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