import os import sys import pytest sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from flexoentity import FlexOID, FlexoEntity, EntityType, EntityState @pytest.fixture def question(): """Fresh question entity in draft state.""" return FlexoEntity("AF", EntityType.QUESTION, "What is Ohm’s law?", EntityState.DRAFT) # ────────────────────────────────────────────────────────────────────────────── def test_initial_state(question): assert question.state == EntityState.DRAFT assert question.flexo_id.version == 1 assert len(question.flexo_id.signature) == 16 # blake2s digest_size=8 → 16 hex assert FlexoEntity.verify_integrity(question) def test_approval_bumps_version(question): question.approve() assert question.state == EntityState.APPROVED assert question.flexo_id.version == 2 def test_signing_bumps_version(question): question.approve() v_before = question.flexo_id question.sign() assert question.state == EntityState.APPROVED_AND_SIGNED assert question.flexo_id != v_before def test_publish_bumps_version(question): question.approve() question.sign() v_before = question.flexo_id.version question.publish() assert question.state == EntityState.PUBLISHED assert question.flexo_id.version == v_before + 1 def test_modify_content_changes_fingerprint(question): old_signature = question.flexo_id.signature question.modify_content("Rephrased Ohm’s law?") assert question.flexo_id.signature != old_signature def test_no_version_bump_on_draft_edits(question): question.modify_content("Draft edit only") assert question.flexo_id.version == 1 def test_version_bump_after_edit_and_sign(question): question.approve() v1 = question.flexo_id question.modify_content("Changed content") question.sign() assert question.flexo_id != v1 def test_integrity_check_passes_and_fails(question): question.approve() assert FlexoEntity.verify_integrity(question) # simulate tampering question.text_seed = "Tampered text" assert not FlexoEntity.verify_integrity(question) def test_obsolete_state(question): question.approve() question.sign() question.publish() question.obsolete() assert question.state == EntityState.OBSOLETE def test_clone_new_base_resets_lineage(question): question.approve() question.sign() question.publish() question.obsolete() old_id = question.flexo_id question.clone_new_base() assert question.flexo_id != old_id assert question.state == EntityState.DRAFT assert question.flexo_id.version == 1 def test_mass_version_increments_until_obsolete(question): question.approve() for _ in range(FlexOID.MAX_VERSION - 2): question.sign() with pytest.raises(RuntimeError, match="mark obsolete"): question.sign()