| 1 | #!/usr/bin/env python3
|
|---|
| 2 |
|
|---|
| 3 | # tests/test_flexoid.py
|
|---|
| 4 | """
|
|---|
| 5 | Test suite for id_factory.FlexOID.
|
|---|
| 6 |
|
|---|
| 7 | I verify that FlexOID behaves deterministically, validates itself strictly,
|
|---|
| 8 | and evolves correctly through version and state transitions.
|
|---|
| 9 | """
|
|---|
| 10 |
|
|---|
| 11 | import re
|
|---|
| 12 | from datetime import datetime, date
|
|---|
| 13 | import pytest
|
|---|
| 14 | from logging import Logger
|
|---|
| 15 | from flexoentity import FlexOID, canonical_seed
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 | logger = Logger(__file__)
|
|---|
| 19 |
|
|---|
| 20 | # ──────────────────────────────────────────────
|
|---|
| 21 | # Basic construction and validation
|
|---|
| 22 | # ──────────────────────────────────────────────
|
|---|
| 23 |
|
|---|
| 24 | def test_valid_flexoid_parsing():
|
|---|
| 25 | fid = FlexOID("GEN-I251101-ABCDEF123456@001D")
|
|---|
| 26 | assert isinstance(fid, str)
|
|---|
| 27 | assert fid.domain_id == "GEN"
|
|---|
| 28 | assert fid.entity_type == "I"
|
|---|
| 29 | assert fid.date_str == "251101"
|
|---|
| 30 | assert fid.version == 1
|
|---|
| 31 | assert fid.state_code == "D"
|
|---|
| 32 | assert fid.prefix.endswith("ABCDEF123456")
|
|---|
| 33 | assert str(fid).endswith("@001D")
|
|---|
| 34 |
|
|---|
| 35 |
|
|---|
| 36 | def test_invalid_flexoid_raises():
|
|---|
| 37 | with pytest.raises(ValueError):
|
|---|
| 38 | FlexOID("INVALIDFORMAT")
|
|---|
| 39 | with pytest.raises(ValueError):
|
|---|
| 40 | FlexOID("GEN-I251101-ABCDEF@1D") # bad version width
|
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 | def test_regex_is_strict():
|
|---|
| 44 | pat = FlexOID.OID_PATTERN
|
|---|
| 45 | assert re.match(pat, "GEN-I251101-123456ABCDEF@001A")
|
|---|
| 46 | assert not re.match(pat, "gen-item251101-123456@001A") # lowercase not allowed
|
|---|
| 47 | assert not re.match(pat, "GEN-I251101-123456@01A") # wrong digit count
|
|---|
| 48 |
|
|---|
| 49 |
|
|---|
| 50 | # ──────────────────────────────────────────────
|
|---|
| 51 | # Generation and deterministic hashing
|
|---|
| 52 | # ──────────────────────────────────────────────
|
|---|
| 53 |
|
|---|
| 54 | def test_generate_and_hash_stability(fixed_datetime):
|
|---|
| 55 | # Fix the date so test is stable
|
|---|
| 56 | fid1 = FlexOID.generate("GEN", "I", "D", "test content")
|
|---|
| 57 | fid2 = FlexOID.generate("GEN", "I", "D", "test content")
|
|---|
| 58 | assert fid1 == fid2 # deterministic
|
|---|
| 59 | assert fid1.hash_part == fid2.hash_part
|
|---|
| 60 | assert fid1.domain_id == "GEN"
|
|---|
| 61 | assert fid1.entity_type == "I"
|
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 | def test_safe_generate_collision(monkeypatch):
|
|---|
| 65 | # Fake repo that always returns a conflicting item with different seed
|
|---|
| 66 | class DummyRepo(dict):
|
|---|
| 67 | def get(self, key): return True
|
|---|
| 68 |
|
|---|
| 69 | repo = DummyRepo()
|
|---|
| 70 | fid = FlexOID.safe_generate("GEN", "I", "D", "abc", repo=repo)
|
|---|
| 71 | assert isinstance(fid, FlexOID)
|
|---|
| 72 | assert fid.state_code == "D"
|
|---|
| 73 |
|
|---|
| 74 |
|
|---|
| 75 | # ──────────────────────────────────────────────
|
|---|
| 76 | # State and version transitions
|
|---|
| 77 | # ──────────────────────────────────────────────
|
|---|
| 78 |
|
|---|
| 79 | def test_with_state_creates_new_instance():
|
|---|
| 80 | fid = FlexOID("GEN-I251101-ABCDEF123456@001D")
|
|---|
| 81 | fid2 = fid.with_state("A")
|
|---|
| 82 | assert fid != fid2
|
|---|
| 83 | assert fid.version == fid2.version
|
|---|
| 84 | assert fid2.state_code == "A"
|
|---|
| 85 | assert str(fid2).endswith("@001A")
|
|---|
| 86 |
|
|---|
| 87 |
|
|---|
| 88 | def test_next_version_increments_properly():
|
|---|
| 89 | fid = FlexOID("GEN-I251101-ABCDEF123456@001A")
|
|---|
| 90 | fid2 = FlexOID.next_version(fid)
|
|---|
| 91 | assert fid2.version == 2
|
|---|
| 92 | assert fid2.state_code == fid.state_code
|
|---|
| 93 | assert fid.prefix == fid2.prefix
|
|---|
| 94 |
|
|---|
| 95 |
|
|---|
| 96 | def test_from_oid_and_version_sets_exact_version():
|
|---|
| 97 | fid = FlexOID("GEN-I251101-ABCDEF123456@001A")
|
|---|
| 98 | fid2 = FlexOID.from_oid_and_version(fid, 10)
|
|---|
| 99 | assert fid2.version == 10
|
|---|
| 100 | assert fid2.state_code == "A"
|
|---|
| 101 |
|
|---|
| 102 |
|
|---|
| 103 | def test_clone_new_base_starts_at_one(fixed_datetime):
|
|---|
| 104 | fid = FlexOID.clone_new_base("GEN", "I", "D", "clone text")
|
|---|
| 105 | assert fid.version == 1
|
|---|
| 106 | assert fid.state_code == "D"
|
|---|
| 107 |
|
|---|
| 108 |
|
|---|
| 109 | # ──────────────────────────────────────────────
|
|---|
| 110 | # Canonical seed behavior
|
|---|
| 111 | # ──────────────────────────────────────────────
|
|---|
| 112 |
|
|---|
| 113 | def test_canonical_seed_for_strings_and_dicts():
|
|---|
| 114 | s1 = " Hello world "
|
|---|
| 115 | s2 = "Hello world"
|
|---|
| 116 | assert canonical_seed(s1) == canonical_seed(s2)
|
|---|
| 117 |
|
|---|
| 118 | d1 = {"b": 1, "a": 2}
|
|---|
| 119 | d2 = {"a": 2, "b": 1}
|
|---|
| 120 | assert canonical_seed(d1) == canonical_seed(d2)
|
|---|
| 121 |
|
|---|
| 122 | class Dummy:
|
|---|
| 123 | def __init__(self):
|
|---|
| 124 | self.x = 1
|
|---|
| 125 | self.y = 2
|
|---|
| 126 | obj = Dummy()
|
|---|
| 127 | assert isinstance(canonical_seed(obj), str)
|
|---|
| 128 |
|
|---|
| 129 |
|
|---|
| 130 | # ──────────────────────────────────────────────
|
|---|
| 131 | # Parsed structure and representations
|
|---|
| 132 | # ──────────────────────────────────────────────
|
|---|
| 133 |
|
|---|
| 134 | def test_parsed_returns_expected_dict():
|
|---|
| 135 | fid = FlexOID("GEN-I251101-ABCDEF123456@007S")
|
|---|
| 136 | data = fid.to_dict()
|
|---|
| 137 | assert data["domain_id"] == "GEN"
|
|---|
| 138 | assert data["entity_type"] == "I"
|
|---|
| 139 | assert data["version"] == 7
|
|---|
| 140 | assert data["state"] == "S"
|
|---|
| 141 | assert isinstance(data["date"], date)
|
|---|
| 142 |
|
|---|
| 143 |
|
|---|
| 144 | def test_repr_is_readable():
|
|---|
| 145 | fid = FlexOID("GEN-I251101-ABCDEF123456@001D")
|
|---|
| 146 | s = repr(fid)
|
|---|
| 147 | assert "FlexOID" in s
|
|---|
| 148 | assert "GEN-I" in s
|
|---|