| 1 | """
|
|---|
| 2 | Stress tests for the Flex-O ID lifecycle.
|
|---|
| 3 | Focus: collision avoidance, version ceiling, reproducibility.
|
|---|
| 4 | """
|
|---|
| 5 |
|
|---|
| 6 | import copy
|
|---|
| 7 | import logging
|
|---|
| 8 | import random
|
|---|
| 9 |
|
|---|
| 10 | import pytest
|
|---|
| 11 | from uuid import uuid4
|
|---|
| 12 | from flexoentity import FlexOID, EntityType, EntityState, FlexoSignature
|
|---|
| 13 |
|
|---|
| 14 | logger = logging.getLogger(__name__)
|
|---|
| 15 |
|
|---|
| 16 | def test_bulk_generation_uniqueness(sample_domain):
|
|---|
| 17 | """
|
|---|
| 18 | Generate 100,000 IDs and ensure uniqueness using safe_generate().
|
|---|
| 19 | If a collision occurs, safe_generate() must resolve it automatically
|
|---|
| 20 | via salt + date adjustment.
|
|---|
| 21 | """
|
|---|
| 22 | entity_type = EntityType.ITEM
|
|---|
| 23 | estate = EntityState.DRAFT
|
|---|
| 24 | seeds = [f"question {i}" for i in range(100000)]
|
|---|
| 25 |
|
|---|
| 26 | # Simulate a simple in-memory repository for collision detection
|
|---|
| 27 | repo = {}
|
|---|
| 28 |
|
|---|
| 29 | def repo_get(oid_str):
|
|---|
| 30 | return repo.get(str(oid_str))
|
|---|
| 31 |
|
|---|
| 32 | # Generate IDs using safe_generate
|
|---|
| 33 | ids = []
|
|---|
| 34 | for seed in seeds:
|
|---|
| 35 | oid = FlexOID.safe_generate(sample_domain.domain_id, entity_type.value,
|
|---|
| 36 | estate.value, seed, repo=repo)
|
|---|
| 37 | assert isinstance(oid, FlexOID)
|
|---|
| 38 | ids.append(str(oid))
|
|---|
| 39 | repo[str(oid)] = oid # register for future collision detection
|
|---|
| 40 |
|
|---|
| 41 | unique_count = len(set(ids))
|
|---|
| 42 | total_count = len(ids)
|
|---|
| 43 | collisions = total_count - unique_count
|
|---|
| 44 |
|
|---|
| 45 | logger.info(f"Generated {total_count} IDs ({collisions} collisions handled).")
|
|---|
| 46 |
|
|---|
| 47 | # Assert that safe_generate avoided duplicates
|
|---|
| 48 | assert total_count == unique_count, f"Unexpected duplicate IDs ({collisions} found)"
|
|---|
| 49 |
|
|---|
| 50 | # Sanity check: IDs should look canonical
|
|---|
| 51 | # assert all(id_str.startswith("GENERIC") for id_str in ids)
|
|---|
| 52 | assert all("@" in id_str for id_str in ids)
|
|---|
| 53 |
|
|---|
| 54 | def test_id_generation_is_deterministic(sample_domain):
|
|---|
| 55 | """
|
|---|
| 56 | Generating the same entity twice with same inputs yields identical ID.
|
|---|
| 57 | (No runtime disambiguation; IDs are deterministic by design.)
|
|---|
| 58 | """
|
|---|
| 59 | entity_type = EntityType.ITEM
|
|---|
| 60 | estate = EntityState.DRAFT
|
|---|
| 61 | text = "identical question text"
|
|---|
| 62 |
|
|---|
| 63 | id1 = FlexOID.generate(sample_domain.domain_id, entity_type.value, estate.value, text)
|
|---|
| 64 | id2 = FlexOID.generate(sample_domain.domain_id, entity_type.value, estate.value, text)
|
|---|
| 65 | # IDs must be identical because generation is deterministic
|
|---|
| 66 | assert id1 == id2
|
|---|
| 67 |
|
|---|
| 68 | def test_massive_lifecycle_simulation(cert_ref_linux, sample_domain):
|
|---|
| 69 | """
|
|---|
| 70 | Generate 100 random FlexoSignatures, mutate content, run through lifecycle,
|
|---|
| 71 | and ensure all FlexOIDs are unique and valid.
|
|---|
| 72 | """
|
|---|
| 73 | entities = []
|
|---|
| 74 |
|
|---|
| 75 | for i in range(100):
|
|---|
| 76 | sig = FlexoSignature.with_domain_id(
|
|---|
| 77 | domain_id="SIGTEST",
|
|---|
| 78 | signed_entity=sample_domain.flexo_id,
|
|---|
| 79 | signer_id=uuid4(),
|
|---|
| 80 | certificate_reference=cert_ref_linux,
|
|---|
| 81 | comment=f"Initial signature #{i}"
|
|---|
| 82 | )
|
|---|
| 83 | entities.append(sig)
|
|---|
| 84 |
|
|---|
| 85 | # Mutate + lifecycle transitions
|
|---|
| 86 | for i, e in enumerate(entities):
|
|---|
| 87 | # CONTENT CHANGE → fingerprint changes → hash → FlexOID.prefix changes
|
|---|
| 88 | e.comment += f" updated-{i}"
|
|---|
| 89 | e._update_fingerprint()
|
|---|
| 90 |
|
|---|
| 91 | # lifecycle transitions
|
|---|
| 92 | e.approve()
|
|---|
| 93 | if random.random() > 0.3:
|
|---|
| 94 | e.sign()
|
|---|
| 95 | if random.random() > 0.6:
|
|---|
| 96 | e.publish()
|
|---|
| 97 |
|
|---|
| 98 | # Check ID uniqueness
|
|---|
| 99 | flexoids = [str(e.flexo_id) for e in entities]
|
|---|
| 100 | assert len(flexoids) == len(set(flexoids)), "Duplicate FlexOIDs detected"
|
|---|