148 lines
4.4 KiB
Python
148 lines
4.4 KiB
Python
"""Tests for voting and consensus logic."""
|
|
|
|
import pytest
|
|
from discussions.voting import (
|
|
VotingConfig,
|
|
ConsensusResult,
|
|
calculate_consensus,
|
|
is_human_participant,
|
|
format_vote_summary,
|
|
)
|
|
|
|
|
|
class TestIsHumanParticipant:
|
|
def test_human_names(self):
|
|
assert is_human_participant("Rob") is True
|
|
assert is_human_participant("Alice") is True
|
|
assert is_human_participant("bob_smith") is True
|
|
|
|
def test_ai_names(self):
|
|
assert is_human_participant("AI-Architect") is False
|
|
assert is_human_participant("AI_Security") is False
|
|
assert is_human_participant("ai-moderator") is False
|
|
|
|
def test_empty(self):
|
|
assert is_human_participant("") is False
|
|
assert is_human_participant(None) is False
|
|
|
|
|
|
class TestVotingConfig:
|
|
def test_defaults(self):
|
|
config = VotingConfig()
|
|
assert config.threshold_ready == 0.67
|
|
assert config.threshold_reject == 0.01
|
|
assert config.human_required is True
|
|
|
|
def test_custom_thresholds(self):
|
|
config = VotingConfig(threshold_ready=0.5, threshold_reject=0.2)
|
|
assert config.threshold_ready == 0.5
|
|
assert config.threshold_reject == 0.2
|
|
|
|
def test_invalid_threshold(self):
|
|
with pytest.raises(ValueError):
|
|
VotingConfig(threshold_ready=1.5)
|
|
|
|
with pytest.raises(ValueError):
|
|
VotingConfig(threshold_reject=-0.1)
|
|
|
|
|
|
class TestCalculateConsensus:
|
|
def test_consensus_reached_all_ready(self):
|
|
votes = {
|
|
"AI-Architect": "READY",
|
|
"AI-Security": "READY",
|
|
"AI-Pragmatist": "READY",
|
|
"Rob": "READY",
|
|
}
|
|
config = VotingConfig(human_required=True)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is True
|
|
assert result.outcome == "READY"
|
|
assert result.ready_count == 4
|
|
|
|
def test_consensus_reached_with_changes(self):
|
|
votes = {
|
|
"AI-Architect": "READY",
|
|
"AI-Security": "READY",
|
|
"AI-Pragmatist": "CHANGES",
|
|
"Rob": "READY",
|
|
}
|
|
config = VotingConfig(threshold_ready=0.67, human_required=True)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is True
|
|
assert result.ready_count == 3
|
|
assert result.changes_count == 1
|
|
|
|
def test_blocked_by_reject(self):
|
|
votes = {
|
|
"AI-Architect": "READY",
|
|
"AI-Security": "REJECT",
|
|
"AI-Pragmatist": "READY",
|
|
"Rob": "READY",
|
|
}
|
|
config = VotingConfig(threshold_reject=0.01)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is False
|
|
assert "AI-Security" in result.blocked_by
|
|
assert "REJECT" in result.reason
|
|
|
|
def test_human_required_not_met(self):
|
|
votes = {
|
|
"AI-Architect": "READY",
|
|
"AI-Security": "READY",
|
|
"AI-Pragmatist": "READY",
|
|
}
|
|
config = VotingConfig(human_required=True)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is False
|
|
assert "Human approval required" in result.reason
|
|
|
|
def test_human_required_disabled(self):
|
|
votes = {
|
|
"AI-Architect": "READY",
|
|
"AI-Security": "READY",
|
|
"AI-Pragmatist": "READY",
|
|
}
|
|
config = VotingConfig(human_required=False)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is True
|
|
|
|
def test_insufficient_votes(self):
|
|
votes = {}
|
|
config = VotingConfig(minimum_votes=1)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is False
|
|
assert "Insufficient votes" in result.reason
|
|
|
|
def test_not_enough_ready_votes(self):
|
|
votes = {
|
|
"AI-Architect": "CHANGES",
|
|
"AI-Security": "CHANGES",
|
|
"AI-Pragmatist": "READY",
|
|
"Rob": "READY",
|
|
}
|
|
config = VotingConfig(threshold_ready=0.67, human_required=True)
|
|
result = calculate_consensus(votes, config)
|
|
|
|
assert result.reached is False
|
|
assert "more READY votes" in result.reason
|
|
|
|
|
|
class TestFormatVoteSummary:
|
|
def test_format(self):
|
|
votes = {
|
|
"Alice": "READY",
|
|
"Bob": "CHANGES",
|
|
"Carol": "READY",
|
|
}
|
|
summary = format_vote_summary(votes)
|
|
assert "READY: 2" in summary
|
|
assert "CHANGES: 1" in summary
|
|
assert "REJECT: 0" in summary
|