tests/test_commitment.py
3.3 KB · 96 lines · python Raw
1 """Tests for CommitmentBuilder, TrainingCommitment, CommitmentSigner."""
2
3 from __future__ import annotations
4
5 import pytest
6 from quantumshield.core.keys import _BACKEND
7
8 from pqc_training_data import (
9 CommitmentBuilder,
10 CommitmentSigner,
11 DataRecord,
12 TrainingCommitment,
13 )
14
15
16 def test_builder_populates_commitment_fields(
17 sample_records: list[DataRecord],
18 ) -> None:
19 builder = CommitmentBuilder("my-dataset", "2.0.1")
20 builder.add_records(sample_records)
21 builder.licenses = ["mit"]
22 builder.tags = ["demo"]
23 builder.extra = {"pipeline": "v3"}
24 commitment = builder.build(description="test corpus")
25
26 assert commitment.dataset_name == "my-dataset"
27 assert commitment.dataset_version == "2.0.1"
28 assert commitment.description == "test corpus"
29 assert commitment.record_count == len(sample_records)
30 assert commitment.root and len(commitment.root) == 64
31 assert commitment.commitment_id.startswith("urn:pqc-td:")
32 assert commitment.licenses == ["mit"]
33 assert commitment.tags == ["demo"]
34 assert commitment.extra == {"pipeline": "v3"}
35 assert commitment.created_at # non-empty iso string
36 # Unsigned at this point
37 assert commitment.signature == ""
38 assert commitment.signer_did == ""
39
40
41 def test_commitment_to_json_from_json_roundtrip(signed_commitment: TrainingCommitment) -> None:
42 blob = signed_commitment.to_json()
43 restored = TrainingCommitment.from_json(blob)
44 assert restored == signed_commitment
45 # And canonical bytes are identical (this is what signatures cover)
46 assert restored.canonical_bytes() == signed_commitment.canonical_bytes()
47
48
49 def test_sign_populates_signature_fields(
50 signer: CommitmentSigner,
51 sample_records: list[DataRecord],
52 ) -> None:
53 builder = CommitmentBuilder("ds", "1.0.0")
54 builder.add_records(sample_records)
55 commitment = builder.build()
56 signed = signer.sign(commitment)
57 assert signed.signature
58 assert signed.signer_did.startswith("did:pqaid:")
59 assert signed.algorithm
60 assert signed.public_key
61 assert signed.signed_at
62
63
64 def test_verify_valid_signature(signed_commitment: TrainingCommitment) -> None:
65 assert CommitmentSigner.verify(signed_commitment) is True
66
67
68 def test_verify_tampered_dataset_name_fails_when_real_pqc_backend(
69 signed_commitment: TrainingCommitment,
70 ) -> None:
71 # Ed25519 fallback does real verification, so this test runs there too.
72 # Only skip for the pure-stub backend (no real signatures at all).
73 if _BACKEND == "stub":
74 pytest.skip("requires real PQC or Ed25519 backend")
75 tampered = TrainingCommitment.from_json(signed_commitment.to_json())
76 tampered.dataset_name = "evil-renamed-dataset"
77 assert CommitmentSigner.verify(tampered) is False
78
79
80 def test_canonical_bytes_deterministic(
81 signer: CommitmentSigner,
82 sample_records: list[DataRecord],
83 ) -> None:
84 builder = CommitmentBuilder("ds", "1.0.0")
85 builder.add_records(sample_records)
86 commitment = builder.build(description="x")
87 cb1 = commitment.canonical_bytes()
88 cb2 = commitment.canonical_bytes()
89 assert cb1 == cb2
90 # Permuting tags/licenses doesn't change canonical output
91 commitment.tags = ["b", "a"]
92 cb3 = commitment.canonical_bytes()
93 commitment.tags = ["a", "b"]
94 cb4 = commitment.canonical_bytes()
95 assert cb3 == cb4
96