""" Stress tests for the Flex-O ID lifecycle. Focus: collision avoidance, version ceiling, reproducibility. """ import pytest import random import logging from flexoentity import FlexOID, EntityType, EntityState from builder.questions import RadioQuestion, AnswerOption logger = logging.getLogger(__name__) def test_bulk_generation_uniqueness(domain): """ Generate 100,000 IDs and ensure uniqueness using safe_generate(). If a collision occurs, safe_generate() must resolve it automatically via salt + date adjustment. """ etype = EntityType.QUESTION estate = EntityState.DRAFT seeds = [f"question {i}" for i in range(100000)] # Simulate a simple in-memory repository for collision detection repo = {} def repo_get(oid_str): return repo.get(str(oid_str)) # Generate IDs using safe_generate ids = [] for seed in seeds: oid = FlexOID.safe_generate(domain.domain, etype, estate, seed, repo=repo) assert isinstance(oid, FlexOID) ids.append(str(oid)) repo[str(oid)] = oid # register for future collision detection unique_count = len(set(ids)) total_count = len(ids) collisions = total_count - unique_count logger.info(f"Generated {total_count} IDs ({collisions} collisions handled).") # Assert that safe_generate avoided duplicates assert total_count == unique_count, f"Unexpected duplicate IDs ({collisions} found)" # Sanity check: IDs should look canonical assert all(id_str.startswith("SIG-") for id_str in ids) assert all("@" in id_str for id_str in ids) def test_id_generation_is_deterministic(domain): """ Generating the same entity twice with same inputs yields identical ID. (No runtime disambiguation; IDs are deterministic by design.) """ etype = EntityType.QUESTION estate = EntityState.DRAFT text = "identical question text" id1 = FlexOID.generate(domain.domain, etype, estate, text) id2 = FlexOID.generate(domain.domain, etype, estate, text) # IDs must be identical because generation is deterministic assert id1 == id2 def test_id_reproducibility_across_runs(domain): """ The same seed on a new process (fresh _seen_hashes) should yield the same base ID (without suffix). """ etype = EntityType.CATALOG estate = EntityState.DRAFT seed = "reproducibility test seed" id1 = FlexOID.generate(domain.domain, etype, estate, seed) FlexOID._seen_hashes.clear() id2 = FlexOID.generate(domain.domain, etype, estate, seed) assert id1 == id2 def test_version_ceiling_enforcement(radio_question): """Simulate approaching @999 to trigger obsolescence guard.""" q = radio_question q.approve() # artificially bump version number to near ceiling q.flexo_id = FlexOID.from_oid_and_version(q.flexo_id, 998) # 998 → 999 is allowed q.sign() assert q.flexo_id.version == 999 # 999 → 1000 should raise RuntimeError with pytest.raises(RuntimeError): q.sign() def test_massive_lifecycle_simulation(domain): """ Generate 100 random RadioQuestions, simulate multiple edits and state transitions, ensure all final IDs and fingerprints are unique and valid. """ entities = [ RadioQuestion( domain=domain, etype=EntityType.QUESTION, state=EntityState.DRAFT, text=f"random question {i}", options=[ AnswerOption(id="opt4", text="HF (3–30 MHz)", points=1), AnswerOption(id="opt5", text="VHF (30–300 MHz)", points=0), ], ) for i in range(100) ] for e in entities: # random edit e.text += " updated" e._update_fingerprint() # lifecycle transitions e.approve() if random.random() > 0.3: e.sign() if random.random() > 0.6: e.publish() flexoids = [e.flexo_id for e in entities] assert len(flexoids) == len(set(flexoids)), "Duplicate FlexOIDs after lifecycle simulation"