"""Round-trip tests for content ids, keys, signed operations, and the append-only operation log (``mnema.core``). These bind the wire-format contract from milestone #2: an operation that is created, serialised to a dict / JSONL, and parsed back must keep the same id and still verify; any tampering with the body or the chain must be detected. """ import json import pytest from mnema.core.ids import content_id from mnema.core.keys import KeyPair from mnema.core.log import OperationLog from mnema.core.operations import Operation, verify_operation @pytest.fixture def kp() -> KeyPair: return KeyPair.generate() # --------------------------------------------------------------------------- # Keys # --------------------------------------------------------------------------- class TestKeyPair: def test_generate_produces_distinct_keys(self): a = KeyPair.generate() b = KeyPair.generate() assert a.sign(b"message") != b.sign(b"message") def test_sign_verify_round_trip(self, kp): message = b"what do you know about me and why?" signature = kp.sign(message) assert kp.verify(message, signature) def test_tampered_message_rejected(self, kp): signature = kp.sign(b"hello world") try: ok = kp.verify(b"hello w0rld", signature) except Exception: ok = False # raising on bad signature is an acceptable contract assert not ok def test_signature_from_other_key_rejected(self, kp): other = KeyPair.generate() signature = other.sign(b"hello world") try: ok = kp.verify(b"hello world", signature) except Exception: ok = False assert not ok # --------------------------------------------------------------------------- # Content ids # --------------------------------------------------------------------------- class TestContentId: def test_deterministic_regardless_of_key_order(self): a = content_id({"x": 1, "y": [1, 2], "z": "s"}) b = content_id({"z": "s", "y": [1, 2], "x": 1}) assert a == b def test_distinct_payloads_distinct_ids(self): assert content_id({"x": 1}) != content_id({"x": 2}) def test_id_is_namespaced_string(self): cid = content_id({"x": 1}) assert isinstance(cid, str) # ids carry an algorithm prefix so other implementations can verify assert ":" in cid assert len(cid) > 16 # --------------------------------------------------------------------------- # Signed operations # --------------------------------------------------------------------------- class TestOperation: def test_create_and_verify(self, kp): op = Operation.create( op_type="evidence.append", body={"source": "calendar", "n": 1}, keypair=kp, ) assert op.op_type == "evidence.append" assert op.body == {"source": "calendar", "n": 1} assert op.prev is None assert verify_operation(op) def test_prev_chaining(self, kp): first = Operation.create(op_type="evidence.append", body={"n": 1}, keypair=kp) second = Operation.create( op_type="evidence.append", body={"n": 2}, keypair=kp, prev=first.op_id ) assert second.prev == first.op_id assert verify_operation(second) def test_distinct_operations_have_distinct_ids(self, kp): a = Operation.create(op_type="evidence.append", body={"n": 1}, keypair=kp) b = Operation.create(op_type="evidence.append", body={"n": 2}, keypair=kp) assert a.op_id != b.op_id def test_dict_round_trip_preserves_id_and_verifies(self, kp): op = Operation.create( op_type="claim.derive", body={"predicate": "routine.weekly", "confidence": 0.8}, keypair=kp, ) wire = op.to_dict() # the wire form must be plain JSON encoded = json.dumps(wire) restored = Operation.from_dict(json.loads(encoded)) assert restored.op_id == op.op_id assert restored.op_type == op.op_type assert restored.body == op.body assert verify_operation(restored) def test_tampered_body_rejected(self, kp): op = Operation.create(op_type="claim.derive", body={"confidence": 0.8}, keypair=kp) wire = op.to_dict() wire["body"]["confidence"] = 0.99 # attacker inflates confidence try: restored = Operation.from_dict(wire) ok = verify_operation(restored) except Exception: ok = False # rejecting at parse time is also an acceptable contract assert not ok # --------------------------------------------------------------------------- # Append-only log # --------------------------------------------------------------------------- def _chain(kp: KeyPair, n: int = 3): ops = [] prev = None for i in range(n): op = Operation.create( op_type="evidence.append", body={"seq": i, "source": "notes"}, keypair=kp, prev=prev, ) ops.append(op) prev = op.op_id return ops class TestOperationLog: def test_append_and_iterate_in_order(self, kp): ops = _chain(kp, 3) log = OperationLog() for op in ops: log.append(op) stored = list(log) assert [o.op_id for o in stored] == [o.op_id for o in ops] assert len(stored) == 3 def test_chain_verifies(self, kp): log = OperationLog() for op in _chain(kp, 5): log.append(op) assert log.verify() def test_jsonl_round_trip(self, kp, tmp_path): ops = _chain(kp, 4) log = OperationLog() for op in ops: log.append(op) path = tmp_path / "log.jsonl" with path.open("w", encoding="utf-8") as fh: for op in log: fh.write(json.dumps(op.to_dict()) + "\n") rebuilt = OperationLog() with path.open("r", encoding="utf-8") as fh: for line in fh: rebuilt.append(Operation.from_dict(json.loads(line))) assert [o.op_id for o in rebuilt] == [o.op_id for o in ops] assert rebuilt.verify() def test_tampering_breaks_verification(self, kp): ops = _chain(kp, 3) wires = [op.to_dict() for op in ops] wires[1]["body"]["seq"] = 999 # rewrite history in the middle try: tampered = OperationLog() for wire in wires: tampered.append(Operation.from_dict(wire)) ok = tampered.verify() except Exception: ok = False # raising during parse/append is an acceptable contract assert not ok