"""Operation construction, signing, serialisation, and verification tests.""" from __future__ import annotations from pathlib import Path from helpers import signalled_failure from pmp.keys import KeyStore from pmp.operations import Operation, build_operation, verify_operation def _device_key(tmp_path: Path): return KeyStore(tmp_path / "keys").create("device") def _evidence_body(text: str) -> dict: return { "source_type": "test", "content": {"note": text}, "provenance": {"adapter": "test", "source_ref": "memory://test"}, } def test_build_operation_is_signed_and_verifies(tmp_path: Path): key = _device_key(tmp_path) op = build_operation( op_type="evidence.observe", body=_evidence_body("hello world"), key=key, prev=None, seq=0, ) assert op.op_id assert op.op_type == "evidence.observe" # Should not raise / not signal failure: assert not signalled_failure(lambda: verify_operation(op)) def test_operation_records_author_key(tmp_path: Path): key = _device_key(tmp_path) op = build_operation( op_type="evidence.observe", body=_evidence_body("authored"), key=key, prev=None, seq=0, ) assert op.author == key.key_id def test_prev_pointer_links_operations(tmp_path: Path): key = _device_key(tmp_path) first = build_operation( op_type="evidence.observe", body=_evidence_body("first"), key=key, prev=None, seq=0, ) second = build_operation( op_type="evidence.observe", body=_evidence_body("second"), key=key, prev=first.op_id, seq=1, ) assert second.prev == first.op_id assert second.op_id != first.op_id def test_distinct_bodies_yield_distinct_op_ids(tmp_path: Path): key = _device_key(tmp_path) ids = set() prev = None for i in range(5): op = build_operation( op_type="evidence.observe", body=_evidence_body(f"entry {i}"), key=key, prev=prev, seq=i, ) ids.add(op.op_id) prev = op.op_id assert len(ids) == 5 def test_dict_roundtrip_preserves_operation_and_signature(tmp_path: Path): key = _device_key(tmp_path) op = build_operation( op_type="evidence.observe", body=_evidence_body("roundtrip"), key=key, prev=None, seq=0, ) restored = Operation.from_dict(op.to_dict()) assert restored.op_id == op.op_id assert restored.op_type == op.op_type assert restored.body == op.body assert restored.author == op.author assert not signalled_failure(lambda: verify_operation(restored)) def test_tampered_body_fails_verification(tmp_path: Path): key = _device_key(tmp_path) op = build_operation( op_type="evidence.observe", body=_evidence_body("the truth"), key=key, prev=None, seq=0, ) forged_dict = op.to_dict() forged_dict["body"]["content"]["note"] = "a forgery" def attempt(): forged = Operation.from_dict(forged_dict) return verify_operation(forged) assert signalled_failure(attempt) def test_tampered_op_type_fails_verification(tmp_path: Path): key = _device_key(tmp_path) op = build_operation( op_type="evidence.observe", body=_evidence_body("typed"), key=key, prev=None, seq=0, ) forged_dict = op.to_dict() forged_dict["op_type"] = "claim.assert" def attempt(): forged = Operation.from_dict(forged_dict) return verify_operation(forged) assert signalled_failure(attempt)