tests/test_node.py
| 1 | """Tests for GovernanceNode and NodeRegistry.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import pytest |
| 6 | from quantumshield.identity.agent import AgentIdentity |
| 7 | |
| 8 | from pqc_ai_governance import ( |
| 9 | GovernanceNode, |
| 10 | GovernanceProposal, |
| 11 | NodeRegistry, |
| 12 | ProposalKind, |
| 13 | UnknownNodeError, |
| 14 | VoteDecision, |
| 15 | ) |
| 16 | |
| 17 | |
| 18 | def _fresh_proposal(proposer_did: str) -> GovernanceProposal: |
| 19 | return GovernanceProposal.create( |
| 20 | kind=ProposalKind.AUTHORIZE_MODEL, |
| 21 | subject_id="did:pqaid:model-x", |
| 22 | title="Authorize model-x", |
| 23 | proposer_did=proposer_did, |
| 24 | ) |
| 25 | |
| 26 | |
| 27 | def test_sign_proposal_populates_signature_fields(alice: GovernanceNode) -> None: |
| 28 | proposal = _fresh_proposal(alice.did) |
| 29 | alice.sign_proposal(proposal) |
| 30 | assert proposal.signer_did == alice.did |
| 31 | assert proposal.algorithm != "" |
| 32 | assert proposal.signature != "" |
| 33 | assert proposal.public_key != "" |
| 34 | |
| 35 | |
| 36 | def test_verify_proposal_true_for_valid(alice: GovernanceNode) -> None: |
| 37 | proposal = _fresh_proposal(alice.did) |
| 38 | alice.sign_proposal(proposal) |
| 39 | assert GovernanceNode.verify_proposal(proposal) is True |
| 40 | |
| 41 | |
| 42 | def test_cast_vote_returns_signed_vote_with_valid_signature( |
| 43 | alice: GovernanceNode, |
| 44 | ) -> None: |
| 45 | proposal = _fresh_proposal(alice.did) |
| 46 | alice.sign_proposal(proposal) |
| 47 | signed = alice.cast_vote(proposal, VoteDecision.APPROVE, rationale="looks good") |
| 48 | assert signed.vote.voter_did == alice.did |
| 49 | assert signed.vote.decision == VoteDecision.APPROVE |
| 50 | assert signed.signature != "" |
| 51 | assert GovernanceNode.verify_vote(signed) is True |
| 52 | |
| 53 | |
| 54 | def test_verify_vote_false_for_tampered(alice: GovernanceNode) -> None: |
| 55 | proposal = _fresh_proposal(alice.did) |
| 56 | alice.sign_proposal(proposal) |
| 57 | signed = alice.cast_vote(proposal, VoteDecision.APPROVE) |
| 58 | # Tamper with the rationale so canonical bytes change |
| 59 | signed.vote.rationale = "TAMPERED" |
| 60 | assert GovernanceNode.verify_vote(signed) is False |
| 61 | |
| 62 | |
| 63 | def test_node_registry_crud_and_weight_sum( |
| 64 | alice: GovernanceNode, bob: GovernanceNode, dave: GovernanceNode |
| 65 | ) -> None: |
| 66 | reg = NodeRegistry() |
| 67 | reg.register(alice) |
| 68 | reg.register(bob) |
| 69 | reg.register(dave) |
| 70 | assert len(reg) == 3 |
| 71 | assert reg.is_member(alice.did) |
| 72 | assert reg.total_weight() == 1 + 1 + 2 |
| 73 | got = reg.get(dave.did) |
| 74 | assert got.did == dave.did |
| 75 | reg.remove(bob.did) |
| 76 | assert not reg.is_member(bob.did) |
| 77 | with pytest.raises(UnknownNodeError): |
| 78 | reg.get("did:pqaid:nobody") |
| 79 | with pytest.raises(UnknownNodeError): |
| 80 | reg.remove("did:pqaid:nobody") |
| 81 | |
| 82 | |
| 83 | def test_new_governance_node_via_agent_identity() -> None: |
| 84 | node = GovernanceNode(identity=AgentIdentity.create("mallory"), name="mallory") |
| 85 | assert node.did.startswith("did:pqaid:") |
| 86 | assert node.weight == 1 |
| 87 | |