tests/test_proposal.py
2.5 KB · 83 lines · python Raw
1 """Tests for GovernanceProposal."""
2
3 from __future__ import annotations
4
5 import time
6
7 from pqc_ai_governance import GovernanceProposal, ProposalKind, ProposalStatus
8
9
10 def test_create_populates_fields() -> None:
11 prop = GovernanceProposal.create(
12 kind=ProposalKind.AUTHORIZE_MODEL,
13 subject_id="did:pqaid:abc",
14 title="Authorize abc",
15 proposer_did="did:pqaid:alice",
16 )
17 assert prop.proposal_id.startswith("urn:pqc-gov-prop:")
18 assert prop.kind == ProposalKind.AUTHORIZE_MODEL
19 assert prop.status == ProposalStatus.OPEN
20 assert prop.created_at != ""
21 assert prop.expires_at != ""
22 assert prop.signature == ""
23
24
25 def test_proposal_hash_is_deterministic() -> None:
26 prop = GovernanceProposal.create(
27 kind=ProposalKind.UPDATE_POLICY,
28 subject_id="policy-1",
29 title="Raise rate limit",
30 proposer_did="did:pqaid:alice",
31 parameters={"max_rate_qps": 100, "window": "1m"},
32 )
33 h1 = prop.proposal_hash()
34 h2 = prop.proposal_hash()
35 assert h1 == h2
36 assert len(h1) == 64
37
38
39 def test_is_expired_after_ttl_zero() -> None:
40 prop = GovernanceProposal.create(
41 kind=ProposalKind.EMERGENCY_FREEZE,
42 subject_id="*",
43 title="freeze now",
44 proposer_did="did:pqaid:alice",
45 ttl_seconds=0,
46 )
47 time.sleep(0.01)
48 assert prop.is_expired() is True
49
50
51 def test_to_dict_from_dict_roundtrip() -> None:
52 original = GovernanceProposal.create(
53 kind=ProposalKind.AUTHORIZE_AGENT,
54 subject_id="did:pqaid:agent-x",
55 title="Authorize agent x",
56 proposer_did="did:pqaid:alice",
57 description="grant scope",
58 parameters={"scope": ["read", "write"]},
59 )
60 d = original.to_dict()
61 restored = GovernanceProposal.from_dict(d)
62 assert restored.proposal_id == original.proposal_id
63 assert restored.kind == original.kind
64 assert restored.subject_id == original.subject_id
65 assert restored.parameters == original.parameters
66 assert restored.proposal_hash() == original.proposal_hash()
67
68
69 def test_canonical_bytes_is_deterministic() -> None:
70 prop = GovernanceProposal.create(
71 kind=ProposalKind.AUTHORIZE_MODEL,
72 subject_id="did:pqaid:abc",
73 title="x",
74 proposer_did="did:pqaid:alice",
75 parameters={"b": 2, "a": 1},
76 )
77 b1 = prop.canonical_bytes()
78 b2 = prop.canonical_bytes()
79 assert b1 == b2
80 # Keys should be alphabetised deterministically
81 assert b'"a":1' in b1
82 assert b1.index(b'"a":1') < b1.index(b'"b":2')
83