"""Tally: quorum, threshold, and the counting rules of Article 5. Vote counts in these tests are chosen so the expected outcome is the same under every sane denominator convention (yes/(yes+no), yes/turnout, yes/eligible) — the suite pins outcomes, not one arithmetic style. """ from __future__ import annotations import pytest from tests.helpers import ( cast_ballot, create_proposal, proposal_id_of, passed_of, quorum_met, run_tally, yes_count, no_count, ) @pytest.fixture() def proposal_id(repo, citizens): prop = create_proposal( repo, proposer_id=citizens[0]["id"], title="Tally unit-test proposal", changes={"constitution/userland/tally-test-module.yaml": "id: tally-test\ntitle: Tally test\n"}, ) return proposal_id_of(prop) def _ballots(repo, citizens, proposal_id, choices): out = [] for citizen, choice in zip(citizens, choices): out.append(cast_ballot(citizen, proposal_id, choice, repo=repo)) return out def test_clear_pass(repo, citizens, eligible_ids, proposal_id): # 4 yes, 1 no, 6 eligible; quorum 0.5, threshold 0.6. ballots = _ballots(repo, citizens, proposal_id, ["yes", "yes", "yes", "yes", "no"]) result = run_tally(ballots, eligible_ids, quorum=0.5, threshold=0.6) assert yes_count(result) == 4 assert no_count(result) == 1 assert quorum_met(result) assert passed_of(result) def test_quorum_failure_blocks(repo, citizens, proposal_id): # One yes ballot against an electorate of ten: 10% turnout, quorum 0.5. ballots = _ballots(repo, citizens[:1], proposal_id, ["yes"]) electorate = [c["id"] for c in citizens] + [f"cit-ghost-{i}" for i in range(4)] assert len(electorate) == 10 result = run_tally(ballots, electorate, quorum=0.5, threshold=0.5) assert not quorum_met(result) assert not passed_of(result) def test_supermajority_threshold_failure_blocks(repo, citizens, eligible_ids, proposal_id): # 3 yes, 3 no out of 6: 50% support fails a 2/3 threshold under any convention. ballots = _ballots(repo, citizens, proposal_id, ["yes", "yes", "yes", "no", "no", "no"]) result = run_tally(ballots, eligible_ids, quorum=0.5, threshold=2 / 3) assert yes_count(result) == 3 assert no_count(result) == 3 assert quorum_met(result) assert not passed_of(result) def test_ineligible_ballots_are_not_counted(repo, citizens, proposal_id): # Five eligible citizens; a sixth ballot arrives from outside the electorate. eligible = [c["id"] for c in citizens[:5]] ballots = _ballots(repo, citizens[:4], proposal_id, ["yes"] * 4) intruder = citizens[5] # in the registry, but excluded from this electorate ballots.append(cast_ballot(intruder, proposal_id, "yes", repo=repo)) result = run_tally(ballots, eligible, quorum=0.5, threshold=0.5) assert yes_count(result) == 4, "a ballot from outside the electorate must not count" def test_unanimous_no_fails(repo, citizens, eligible_ids, proposal_id): ballots = _ballots(repo, citizens, proposal_id, ["no"] * 6) result = run_tally(ballots, eligible_ids, quorum=0.5, threshold=0.5) assert yes_count(result) == 0 assert not passed_of(result) def test_zero_ballots_fails(repo, eligible_ids): result = run_tally([], eligible_ids, quorum=0.5, threshold=0.5) assert not passed_of(result) assert not quorum_met(result)