#764 Stop Removing Duplicate Contests in Report (#767)

* Add test reproducing error with duplicate section names

* Stop removing duplicate contests from report

* Test to ensure duplicates aren't removed in selections

* fix linting issues
This commit is contained in:
Lee Richardson 2022-08-23 12:39:54 -04:00 коммит произвёл GitHub
Родитель 3cbbcccede
Коммит c79b021fb3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 189 добавлений и 11 удалений

Просмотреть файл

@ -7,13 +7,26 @@ from electionguard_gui.models.election_dto import ElectionDto
def get_plaintext_ballot_report(
election: ElectionDto, plaintext_ballot: PlaintextTally
) -> dict[str, Any]:
) -> list:
manifest = election.get_manifest()
selection_names = manifest.get_selection_names("en")
contest_names = manifest.get_contest_names()
selection_write_ins = _get_candidate_write_ins(manifest)
parties = _get_selection_parties(manifest)
tally_report = {}
tally_report = _get_tally_report(
plaintext_ballot, selection_names, contest_names, selection_write_ins, parties
)
return tally_report
def _get_tally_report(
plaintext_ballot: PlaintextTally,
selection_names: dict[str, str],
contest_names: dict[str, str],
selection_write_ins: dict[str, bool],
parties: dict[str, str],
) -> list:
tally_report = []
contests = plaintext_ballot.contests.values()
for tally_contest in contests:
selections = list(tally_contest.selections.values())
@ -21,7 +34,12 @@ def get_plaintext_ballot_report(
selections, selection_names, selection_write_ins, parties
)
contest_name = contest_names.get(tally_contest.object_id, "n/a")
tally_report[contest_name] = contest_details
tally_report.append(
{
"name": contest_name,
"details": contest_details,
}
)
return tally_report

Просмотреть файл

@ -3,8 +3,8 @@ export default {
ballot: Object,
},
template: /*html*/ `
<div v-for="(contestContents, contestName) in ballot" class="mb-5">
<h2>{{contestName}}</h2>
<div v-for="contest in ballot" class="mb-5">
<h2>{{contest.name}}</h2>
<table class="table table-striped">
<thead>
<tr>
@ -15,7 +15,7 @@ export default {
</tr>
</thead>
<tbody>
<tr v-for="contestInfo in contestContents.selections">
<tr v-for="contestInfo in contest.details.selections">
<td>{{contestInfo.name}}</td>
<td>{{contestInfo.party}}</td>
<td class="text-end">{{contestInfo.tally}}</td>
@ -24,13 +24,13 @@ export default {
<tr class="table-secondary">
<td></td>
<td></td>
<td class="text-end"><strong>{{contestContents.nonWriteInTotal}}</strong></td>
<td class="text-end"><strong>{{contest.details.nonWriteInTotal}}</strong></td>
<td class="text-end"><strong>100.00%</strong></td>
</tr>
<tr v-if="contestContents.writeInTotal !== null">
<tr v-if="contest.details.writeInTotal !== null">
<td></td>
<td class="text-end">Write-Ins</td>
<td class="text-end">{{contestContents.writeInTotal}}</td>
<td class="text-end">{{contest.details.writeInTotal}}</td>
<td class="text-end"></td>
</tr>
</tbody>

Просмотреть файл

@ -1,12 +1,121 @@
from unittest.mock import MagicMock, patch
from electionguard.tally import PlaintextTallySelection
from electionguard_gui.services.plaintext_ballot_service import _get_contest_details
from electionguard.tally import PlaintextTally, PlaintextTallySelection
from electionguard_gui.services.plaintext_ballot_service import (
_get_contest_details,
_get_tally_report,
)
from tests.base_test_case import BaseTestCase
class TestPlaintextBallotService(BaseTestCase):
"""Test the ElectionDto class"""
def test_get_tally_report_with_no_contests(self) -> None:
# ARRANGE
plaintext_ballot = PlaintextTally("tally", {})
selection_names: dict[str, str] = {}
selection_write_ins: dict[str, bool] = {}
parties: dict[str, str] = {}
contest_names: dict[str, str] = {}
# ACT
result = _get_tally_report(
plaintext_ballot,
selection_names,
contest_names,
selection_write_ins,
parties,
)
# ASSERT
self.assertEqual(0, len(result))
@patch("electionguard.tally.PlaintextTallySelection")
def test_given_one_contest_with_valid_name_when_get_tally_report_then_name_returned(
self, plaintext_tally_selection: MagicMock
) -> None:
# ARRANGE
plaintext_tally_selection.object_id = "c-1"
plaintext_ballot = PlaintextTally("tally", {"c-1": plaintext_tally_selection})
selection_names: dict[str, str] = {}
selection_write_ins: dict[str, bool] = {}
parties: dict[str, str] = {}
contest_names: dict[str, str] = {"c-1": "Contest 1"}
# ACT
result = _get_tally_report(
plaintext_ballot,
selection_names,
contest_names,
selection_write_ins,
parties,
)
# ASSERT
self.assertEqual(1, len(result))
self.assertEqual("Contest 1", result[0]["name"])
@patch("electionguard.tally.PlaintextTallySelection")
def test_given_one_contest_with_invalid_name_when_get_tally_report_then_name_is_na(
self, plaintext_tally_selection: MagicMock
) -> None:
# ARRANGE
plaintext_tally_selection.object_id = "c-1"
plaintext_ballot = PlaintextTally("tally", {"c-1": plaintext_tally_selection})
selection_names: dict[str, str] = {}
selection_write_ins: dict[str, bool] = {}
parties: dict[str, str] = {}
contest_names: dict[str, str] = {}
# ACT
result = _get_tally_report(
plaintext_ballot,
selection_names,
contest_names,
selection_write_ins,
parties,
)
# ASSERT
self.assertEqual(1, len(result))
self.assertEqual("n/a", list(result)[0]["name"])
@patch("electionguard.tally.PlaintextTallySelection")
@patch("electionguard.tally.PlaintextTallySelection")
def test_given_two_contests_with_duplicate_names_when_get_tally_report_then_both_names_returned(
self,
plaintext_tally_selection1: MagicMock,
plaintext_tally_selection2: MagicMock,
) -> None:
# ARRANGE
plaintext_tally_selection1.object_id = "c-1"
plaintext_tally_selection2.object_id = "c-2"
plaintext_ballot = PlaintextTally(
"tally",
{
"c-1": plaintext_tally_selection1,
"c-2": plaintext_tally_selection2,
},
)
selection_names: dict[str, str] = {}
selection_write_ins: dict[str, bool] = {}
parties: dict[str, str] = {}
contest_names: dict[str, str] = {"c-1": "My Contest", "c-2": "My Contest"}
# ACT
result = _get_tally_report(
plaintext_ballot,
selection_names,
contest_names,
selection_write_ins,
parties,
)
# ASSERT
self.assertEqual(2, len(result))
self.assertEqual("My Contest", list(result)[0]["name"])
self.assertEqual("My Contest", list(result)[1]["name"])
def test_zero_sections(self) -> None:
# ARRANGE
selections: list[PlaintextTallySelection] = []
@ -55,6 +164,57 @@ class TestPlaintextBallotService(BaseTestCase):
self.assertEqual("National Union Party", selection["party"])
self.assertEqual(1, selection["percent"])
@patch("electionguard.tally.PlaintextTallySelection")
@patch("electionguard.tally.PlaintextTallySelection")
def test_duplicate_section_names(
self,
plaintext_tally_selection1: MagicMock,
plaintext_tally_selection2: MagicMock,
) -> None:
# ARRANGE
plaintext_tally_selection1.object_id = "S1"
plaintext_tally_selection1.tally = 1
plaintext_tally_selection2.object_id = "S2"
plaintext_tally_selection2.tally = 9
selections: list[PlaintextTallySelection] = [
plaintext_tally_selection1,
plaintext_tally_selection2,
]
selection_names: dict[str, str] = {
"S1": "Abraham Lincoln",
"S2": "Abraham Lincoln",
}
selection_write_ins: dict[str, bool] = {
"S1": False,
"S2": False,
}
parties: dict[str, str] = {
"S1": "National Union Party",
"S2": "National Union Party",
}
# ACT
result = _get_contest_details(
selections, selection_names, selection_write_ins, parties
)
# ASSERT
self.assertEqual(10, result["nonWriteInTotal"])
self.assertEqual(None, result["writeInTotal"])
self.assertEqual(2, len(result["selections"]))
selection = result["selections"][0]
self.assertEqual("Abraham Lincoln", selection["name"])
self.assertEqual(1, selection["tally"])
self.assertEqual("National Union Party", selection["party"])
self.assertEqual(0.1, selection["percent"])
selection = result["selections"][1]
self.assertEqual("Abraham Lincoln", selection["name"])
self.assertEqual(9, selection["tally"])
self.assertEqual("National Union Party", selection["party"])
self.assertEqual(0.9, selection["percent"])
@patch("electionguard.tally.PlaintextTallySelection")
def test_one_write_in(self, plaintext_tally_selection: MagicMock) -> None:
# ARRANGE