""" Persistence and integrity verification tests for Flex-O entities. Ensures fingerprints survive JSON export/import and detect tampering. """ import json import pytest from flexoentity import EntityState from builder.questions import RadioQuestion, AnswerOption # ────────────────────────────────────────────────────────────────────────────── @pytest.fixture def approved_question(domain): """Provide a fully approved and published RadioQuestion for persistence tests.""" q = RadioQuestion( domain=domain, etype=None, # RadioQuestion sets this internally to EntityType.QUESTION state=EntityState.DRAFT, text="What is Ohm’s law?", options=[ AnswerOption(text="U = R × I", points=1), AnswerOption(text="U = I / R", points=0), AnswerOption(text="R = U × I", points=0), ], ) q.approve() q.sign() q.publish() return q @pytest.mark.skip(reason="FlexOIDs regenerated on import; enable once JSON format is stable") def test_json_roundtrip_preserves_integrity(approved_question): """ Export to JSON and reload — ensure fingerprints and signatures remain valid. """ json_str = approved_question.to_json() loaded = RadioQuestion.from_json(json_str) # Fingerprint and state should match — integrity must pass assert RadioQuestion.verify_integrity(loaded) # Metadata should be preserved exactly assert approved_question.signature == loaded.signature assert approved_question.flexo_id == loaded.flexo_id assert loaded.state == approved_question.state # ────────────────────────────────────────────────────────────────────────────── @pytest.mark.skip(reason="FlexOIDs regenerated on import; tampering detection not yet implemented") def test_json_tampering_detection(approved_question): """Tampering with content should invalidate fingerprint verification.""" json_str = approved_question.to_json() tampered = json.loads(json_str) tampered["text"] = "Tampered content injection" tampered_json = json.dumps(tampered) loaded = RadioQuestion.from_json(tampered_json) assert not RadioQuestion.verify_integrity(loaded) # ────────────────────────────────────────────────────────────────────────────── @pytest.mark.skip(reason="FlexOIDs regenerated on import; corruption detection not yet applicable") def test_json_file_corruption(approved_question, tmp_path): """Simulate file corruption — integrity check must fail.""" file = tmp_path / "question.json" json_str = approved_question.to_json() file.write_text(json_str) # Corrupt the file (simulate accidental byte modification) corrupted = json_str.replace("Ohm’s", "Omm’s") file.write_text(corrupted) loaded = RadioQuestion.from_json(file.read_text()) assert not RadioQuestion.verify_integrity(loaded)