import pytest
from zap_schema.identity import Did, DidMethod
from zap_schema.agent_consensus import (
Query,
Response,
ConsensusResult,
AgentConsensusVoting,
consensus_decide,
)
def make_did(name: str) -> Did:
return Did(method=DidMethod.LUX, id=f"z6Mk{name}")
class TestQuery:
def test_create_query(self):
submitter = make_did("Alice")
query = Query.create("What is 2+2?", submitter)
assert query.content == "What is 2+2?"
assert query.submitter == submitter
assert len(query.id) == 32 assert query.timestamp > 0
def test_query_id_unique(self):
submitter = make_did("Alice")
q1 = Query.create("What is 2+2?", submitter)
q2 = Query.create("What is 3+3?", submitter)
assert q1.id != q2.id
def test_query_id_deterministic_content(self):
alice = make_did("Alice")
bob = make_did("Bob")
q1 = Query.create("What is 2+2?", alice)
q2 = Query.create("What is 2+2?", bob)
assert q1.id != q2.id
class TestResponse:
def test_create_response(self):
query_id = bytes(32)
responder = make_did("Bob")
response = Response.create(query_id, "4", responder)
assert response.query_id == query_id
assert response.content == "4"
assert response.responder == responder
assert len(response.id) == 32
assert response.timestamp > 0
def test_response_id_unique(self):
query_id = bytes(32)
responder = make_did("Bob")
r1 = Response.create(query_id, "4", responder)
r2 = Response.create(query_id, "5", responder)
assert r1.id != r2.id
class TestAgentConsensusVoting:
@pytest.mark.asyncio
async def test_submit_query(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("What is 2+2?", make_did("Alice"))
query_id = await consensus.submit_query(query)
assert query_id == query.id
@pytest.mark.asyncio
async def test_submit_response(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("What is 2+2?", make_did("Alice"))
await consensus.submit_query(query)
response = Response.create(query.id, "4", make_did("Bob"))
response_id = await consensus.submit_response(response)
assert response_id == response.id
@pytest.mark.asyncio
async def test_submit_response_invalid_query(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
response = Response.create(bytes(32), "4", make_did("Bob"))
with pytest.raises(ValueError, match="Query not found"):
await consensus.submit_response(response)
@pytest.mark.asyncio
async def test_vote(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("What is 2+2?", make_did("Alice"))
await consensus.submit_query(query)
response = Response.create(query.id, "4", make_did("Bob"))
response_id = await consensus.submit_response(response)
await consensus.vote(query.id, response_id, make_did("Voter1"))
assert await consensus.is_finalized(query.id)
@pytest.mark.asyncio
async def test_vote_double_vote_prevented(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=2)
query = Query.create("Test", make_did("Alice"))
await consensus.submit_query(query)
response = Response.create(query.id, "Answer", make_did("Bob"))
response_id = await consensus.submit_response(response)
voter = make_did("Voter1")
await consensus.vote(query.id, response_id, voter)
with pytest.raises(ValueError, match="Already voted"):
await consensus.vote(query.id, response_id, voter)
@pytest.mark.asyncio
async def test_vote_invalid_query(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
with pytest.raises(ValueError, match="Query not found"):
await consensus.vote(bytes(32), bytes(32), make_did("Voter"))
@pytest.mark.asyncio
async def test_vote_invalid_response(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("Test", make_did("Alice"))
await consensus.submit_query(query)
with pytest.raises(ValueError, match="Response not found"):
await consensus.vote(query.id, bytes(32), make_did("Voter"))
@pytest.mark.asyncio
async def test_consensus_threshold(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=2, min_votes=3)
query = Query.create("Best language?", make_did("Alice"))
await consensus.submit_query(query)
r1 = Response.create(query.id, "Rust", make_did("Bob"))
r1_id = await consensus.submit_response(r1)
r2 = Response.create(query.id, "Python", make_did("Carol"))
r2_id = await consensus.submit_response(r2)
await consensus.vote(query.id, r1_id, make_did("V1"))
await consensus.vote(query.id, r1_id, make_did("V2"))
await consensus.vote(query.id, r2_id, make_did("V3"))
assert await consensus.is_finalized(query.id)
result = await consensus.get_result(query.id)
assert result is not None
assert result.response.content == "Rust"
assert result.votes == 2
assert result.total_voters == 3
@pytest.mark.asyncio
async def test_no_consensus_below_threshold(self):
consensus = AgentConsensusVoting(threshold=0.6, min_responses=3, min_votes=3)
query = Query.create("Test", make_did("Alice"))
await consensus.submit_query(query)
r1 = Response.create(query.id, "A", make_did("Bob"))
r1_id = await consensus.submit_response(r1)
r2 = Response.create(query.id, "B", make_did("Carol"))
r2_id = await consensus.submit_response(r2)
r3 = Response.create(query.id, "C", make_did("Dave"))
r3_id = await consensus.submit_response(r3)
await consensus.vote(query.id, r1_id, make_did("V1"))
await consensus.vote(query.id, r2_id, make_did("V2"))
await consensus.vote(query.id, r3_id, make_did("V3"))
assert not await consensus.is_finalized(query.id)
@pytest.mark.asyncio
async def test_get_responses(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("Test", make_did("Alice"))
await consensus.submit_query(query)
r1 = Response.create(query.id, "A", make_did("Bob"))
r2 = Response.create(query.id, "B", make_did("Carol"))
await consensus.submit_response(r1)
await consensus.submit_response(r2)
responses = await consensus.get_responses(query.id)
assert responses is not None
assert len(responses) == 2
@pytest.mark.asyncio
async def test_get_vote_counts(self):
consensus = AgentConsensusVoting(threshold=0.5, min_responses=1, min_votes=1)
query = Query.create("Test", make_did("Alice"))
await consensus.submit_query(query)
response = Response.create(query.id, "Answer", make_did("Bob"))
response_id = await consensus.submit_response(response)
await consensus.vote(query.id, response_id, make_did("V1"))
counts = await consensus.get_vote_counts(query.id)
assert counts is not None
assert counts.get(response_id) == 1
class TestConsensusDecide:
@pytest.mark.asyncio
async def test_consensus_decide_simple(self):
result = await consensus_decide(
query="What is 2+2?",
submitter=make_did("Alice"),
responses=[
("4", make_did("Bob")),
("5", make_did("Carol")),
],
votes=[
(0, make_did("V1")), ],
threshold=0.5,
)
assert result is not None
assert result.response.content == "4"
assert result.votes == 1
@pytest.mark.asyncio
async def test_consensus_decide_no_consensus(self):
result = await consensus_decide(
query="Test",
submitter=make_did("Alice"),
responses=[
("A", make_did("Bob")),
("B", make_did("Carol")),
],
votes=[], threshold=0.5,
)
assert result is None