def test_ballot_manifest_upload_bad_csv( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={"manifest": (io.BytesIO(b"not a CSV file"), "random.txt")}, ) assert_ok(rv) bgcompute_update_ballot_manifest_file() rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest" ) compare_json( json.loads(rv.data), { "file": {"name": "random.txt", "uploadedAt": assert_is_date,}, "processing": { "status": ProcessingStatus.ERRORED, "startedAt": assert_is_date, "completedAt": assert_is_date, "error": "Please submit a valid CSV file with columns separated by commas.", }, }, )
def test_ballot_manifest_upload_invalid_num_ballots( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO( b"Batch Name,Number of Ballots,Storage Location,Tabulator\n" b"1,not a number,Bin 2,Tabulator 1\n" ), "manifest.csv", ) }, ) assert_ok(rv) bgcompute_update_ballot_manifest_file() rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest" ) compare_json( json.loads(rv.data), { "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,}, "processing": { "status": ProcessingStatus.ERRORED, "startedAt": assert_is_date, "completedAt": assert_is_date, "error": "Expected a number in column Number of Ballots, row 1. Got: not a number.", }, }, )
def manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List[str]): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n" b"2,101\n" b"3,122\n" b"4,400"), "manifest.csv", ) }, ) assert_ok(rv) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest", data={ "manifest": ( io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,20\n" b"2,10\n" b"3,220\n" b"4,40"), "manifest.csv", ) }, ) assert_ok(rv) bgcompute_update_ballot_manifest_file()
def test_contests_create_get_update_one(client, election_id, json_contests): contest = json_contests[0] rv = put_json(client, f"/election/{election_id}/contest", [contest]) assert_ok(rv) rv = client.get(f"/election/{election_id}/contest") contests = json.loads(rv.data) expected_contest = {**contest, "currentRoundStatus": None} assert contests == {"contests": [expected_contest]} contest["totalBallotsCast"] = contest["totalBallotsCast"] + 21 contest["numWinners"] = 2 contest["choices"].append({ "id": str(uuid.uuid4()), "name": "candidate 3", "numVotes": 21, }) rv = put_json(client, f"/election/{election_id}/contest", [contest]) assert_ok(rv) rv = client.get(f"/election/{election_id}/contest") contests = json.loads(rv.data) expected_contest = {**contest, "currentRoundStatus": None} assert contests == {"contests": [expected_contest]}
def test_contests_create_get_update_multiple( client: FlaskClient, election_id: str, json_contests: List[JSONDict], jurisdiction_ids: List[str], ): rv = put_json(client, f"/election/{election_id}/contest", json_contests) assert_ok(rv) rv = client.get(f"/election/{election_id}/contest") contests = json.loads(rv.data) expected_contests = [{ **contest, "currentRoundStatus": None } for contest in json_contests] assert contests == {"contests": expected_contests} json_contests[0]["name"] = "Changed name" json_contests[1]["isTargeted"] = True json_contests[2]["jurisdictionIds"] = jurisdiction_ids[1:] rv = put_json(client, f"/election/{election_id}/contest", json_contests) assert_ok(rv) rv = client.get(f"/election/{election_id}/contest") contests = json.loads(rv.data) expected_contests = [{ **contest, "currentRoundStatus": None } for contest in json_contests] assert contests == {"contests": expected_contests}
def test_replace_jurisdictions_file(client, election_id): # Create the initial file. rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,[email protected]"), "jurisdictions.csv", ) }, ) assert_ok(rv) assert File.query.count() == 1, "the file should exist before a response is sent" # Replace it with another file. rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( io.BytesIO(b"Jurisdiction,Admin Email\n" b"J2,[email protected]"), "jurisdictions2.csv", ) }, ) assert_ok(rv) assert File.query.count() == 1, "the old file should have been deleted"
def audit_board_round_2_ids( client: FlaskClient, election_id: str, jurisdiction_ids: str, round_2_id: str, ) -> List[str]: set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_2_id}/audit-board", [ { "name": "Audit Board #1" }, { "name": "Audit Board #2" }, { "name": "Audit Board #3" }, ], ) assert_ok(rv) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_2_id}/audit-board" ) audit_boards = json.loads(rv.data)["auditBoards"] return [ab["id"] for ab in audit_boards]
def test_audit_board_only_one_member_sign_off_happy_path( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], contest_ids: List[str], round_1_id: str, audit_board_round_1_ids: List[str], ): audit_board_id = audit_board_round_1_ids[0] member_1, _ = set_up_audit_board( client, election_id, jurisdiction_ids[0], contest_ids[0], audit_board_id, only_one_member=True, ) set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_id) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_id}/sign-off", { "memberName1": member_1, "memberName2": "" }, ) assert_ok(rv)
def test_single_jurisdiction_multiple_admins(client, election_id): rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( io.BytesIO( b"Jurisdiction,Admin Email\nJ1,[email protected]\nJ1,[email protected]" ), "jurisdictions.csv", ) }, ) assert_ok(rv) # Process the file in the background. assert bgcompute_update_election_jurisdictions_file() == 1 election = Election.query.filter_by(id=election_id).one() assert [j.name for j in election.jurisdictions] == ["J1"] jurisdiction = election.jurisdictions[0] assert [a.user.email for a in jurisdiction.jurisdiction_administrations] == [ "*****@*****.**", "*****@*****.**", ]
def test_audit_boards_already_created( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board", [{ "name": "Audit Board #1" }], ) assert_ok(rv) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board", [{ "name": "Audit Board #2" }], ) assert rv.status_code == 409 assert json.loads(rv.data) == { "errors": [{ "errorType": "Conflict", "message": "Audit boards already created for round 1", }] }
def test_audit_boards_create_round_2( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_2_id: str, ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_2_id}/audit-board", [ { "name": "Audit Board #1" }, { "name": "Audit Board #2" }, { "name": "Audit Board #3" }, ], ) assert_ok(rv) assert_ballots_got_assigned_correctly( jurisdiction_ids[0], round_2_id, expected_num_audit_boards=3, expected_num_samples=J1_SAMPLES_ROUND_2, )
def test_ballot_list_jurisdiction_two_rounds(client, election_id): ( url_prefix, contest_id, candidate_id_1, candidate_id_2, jurisdiction_id, _audit_board_id_1, _audit_board_id_2, num_ballots, ) = setup_whole_audit(client, election_id, "Primary 2019", 10, "12345678901234567890") # Get the sample size and round id rv = client.get(f"/election/{election_id}/audit/status") status = json.loads(rv.data) sample_size = status["rounds"][0]["contests"][0]["sampleSize"] round_id = status["rounds"][0]["id"] # Retrieve the ballot list rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_id}/round/{round_id}/ballot-list" ) ballot_list = json.loads(rv.data)["ballots"] assert ballot_list assert len(ballot_list) == sample_size # Post results for round 1 with 50/50 split, which should trigger a second round. num_for_winner = int(num_ballots * 0.5) num_for_loser = num_ballots - num_for_winner rv = post_json( client, "{}/jurisdiction/{}/1/results".format(url_prefix, jurisdiction_id), { "contests": [{ "id": contest_id, "results": { candidate_id_1: num_for_winner, candidate_id_2: num_for_loser, }, }] }, ) assert_ok(rv) bgcompute.bgcompute() # Get the sample size and round id for the second round rv = client.get(f"/election/{election_id}/audit/status") status = json.loads(rv.data) sample_size = status["rounds"][1]["contests"][0]["sampleSize"] round_id = status["rounds"][1]["id"] # Retrieve the ballot list rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_id}/round/{round_id}/ballot-list" ) ballot_list = json.loads(rv.data)["ballots"] assert ballot_list assert len(ballot_list) == sample_size
def test_ballot_manifest_replace( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO( b"Batch Name,Number of Ballots,Storage Location,Tabulator\n" b"1,23,Bin 2,Tabulator 1\n" b"12,100,Bin 3,Tabulator 2\n" b"6,0,,\n" ), "manifest.csv", ) }, ) assert_ok(rv) num_files = File.query.count() bgcompute_update_ballot_manifest_file() set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO( b"Batch Name,Number of Ballots,Storage Location,Tabulator\n" b"1,23,Bin 2,Tabulator 1\n" b"12,6,Bin 6,Tabulator 2\n" ), "manifest.csv", ) }, ) assert_ok(rv) # The old file should have been deleted assert File.query.count() == num_files bgcompute_update_ballot_manifest_file() jurisdiction = Jurisdiction.query.get(jurisdiction_ids[0]) assert jurisdiction.manifest_num_batches == 2 assert jurisdiction.manifest_num_ballots == 29 assert len(jurisdiction.batches) == 2 assert jurisdiction.batches[0].name == "1" assert jurisdiction.batches[0].num_ballots == 23 assert jurisdiction.batches[0].storage_location == "Bin 2" assert jurisdiction.batches[0].tabulator == "Tabulator 1" assert jurisdiction.batches[1].name == "12" assert jurisdiction.batches[1].num_ballots == 6 assert jurisdiction.batches[1].storage_location == "Bin 6" assert jurisdiction.batches[1].tabulator == "Tabulator 2"
def set_up_audit_board( client: FlaskClient, election_id: str, jurisdiction_id: str, contest_id: str, audit_board_id: str, only_one_member=False, ) -> Tuple[str, str]: silly_names = [ "Joe Schmo", "Jane Plain", "Derk Clerk", "Bubbikin Republican", "Clem O'Hat Democrat", ] rand = random.Random(12345) member_1 = rand.choice(silly_names) member_2 = rand.choice(silly_names) member_names = [ { "name": member_1, "affiliation": "DEM" }, { "name": "" if only_one_member else member_2, "affiliation": "" }, ] # Set up the audit board rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_id}/audit-board/{audit_board_id}", {"members": member_names}, ) assert_ok(rv) # Fake auditing all the ballots # We iterate over the ballot draws so that we can ensure the computed # results are counting based on the samples, not the ballots. ballot_draws = (SampledBallotDraw.query.join(SampledBallot).filter_by( audit_board_id=audit_board_id).join(Batch).order_by( Batch.name, SampledBallot.ballot_position).all()) choices = (ContestChoice.query.filter_by(contest_id=contest_id).order_by( ContestChoice.name).all()) for draw in ballot_draws[:CHOICE_1_VOTES]: audit_ballot(draw.sampled_ballot, contest_id, Interpretation.VOTE, choices[0].id) for draw in ballot_draws[CHOICE_1_VOTES:CHOICE_1_VOTES + CHOICE_2_VOTES]: audit_ballot(draw.sampled_ballot, contest_id, Interpretation.VOTE, choices[1].id) for draw in ballot_draws[CHOICE_1_VOTES + CHOICE_2_VOTES:]: audit_ballot(draw.sampled_ballot, contest_id, Interpretation.BLANK) db.session.commit() return member_1, member_2
def election_settings(client: FlaskClient, election_id: str): settings = { "electionName": "Test Election", "online": True, "randomSeed": "1234567890", "riskLimit": 10, "state": USState.California, } rv = put_json(client, f"/election/{election_id}/settings", settings) assert_ok(rv)
def test_metadata(client, election_id): rv = client.get(f"/election/{election_id}/jurisdiction/file") assert json.loads(rv.data) == {"file": None, "processing": None} rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,[email protected]"), "jurisdictions.csv", ) }, ) assert_ok(rv) election = Election.query.filter_by(id=election_id).one() assert election.jurisdictions_file.contents == ( "Jurisdiction,Admin Email\n" "J1,[email protected]" ) assert election.jurisdictions_file.name == "jurisdictions.csv" assert election.jurisdictions_file.uploaded_at # Get the file data before processing. rv = client.get(f"/election/{election_id}/jurisdiction/file") response = json.loads(rv.data) file = response["file"] processing = response["processing"] assert file["name"] == "jurisdictions.csv" assert file["uploadedAt"] assert processing["status"] == ProcessingStatus.READY_TO_PROCESS assert processing["startedAt"] is None assert processing["completedAt"] is None assert processing["error"] is None # Actually process the file. assert bgcompute_update_election_jurisdictions_file() == 1 # Now there should be data. rv = client.get(f"/election/{election_id}/jurisdiction/file") response = json.loads(rv.data) file = response["file"] processing = response["processing"] assert file["name"] == "jurisdictions.csv" assert file["uploadedAt"] assert processing["status"] == ProcessingStatus.PROCESSED assert processing["startedAt"] assert processing["completedAt"] assert processing["error"] is None rv = client.get(f"/election/{election_id}/jurisdiction/file/csv") assert ( rv.headers["Content-Disposition"] == 'attachment; filename="jurisdictions.csv"' ) assert rv.data.decode("utf-8") == election.jurisdictions_file.contents
def test_election_reset(client, election_id): rv = client.post(f"/election/{election_id}/audit/reset") assert_ok(rv) rv = client.get(f"/election/{election_id}/audit/status") status = json.loads(rv.data) assert status["riskLimit"] is None assert status["randomSeed"] is None assert status["contests"] == [] assert status["jurisdictions"] == [] assert status["rounds"] == []
def run_audit_board_flow(jurisdiction_id: str, audit_board_id: str): member_1, member_2 = set_up_audit_board(client, election_id, jurisdiction_id, contest_ids[0], audit_board_id) set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_id) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_id}/round/{round_1_id}/audit-board/{audit_board_id}/sign-off", { "memberName1": member_1, "memberName2": member_2 }, ) assert_ok(rv)
def test_audit_basic_update_sets_default_for_contest_is_targeted( client, election_id): contest_id = str(uuid.uuid4()) candidate_id_1 = str(uuid.uuid4()) candidate_id_2 = str(uuid.uuid4()) rv = post_json( client, f"/election/{election_id}/audit/basic", { "name": "Create Contest", "riskLimit": 10, "randomSeed": "a1234567890987654321b", "online": False, "contests": [{ "id": contest_id, "name": "Contest 1", "choices": [ { "id": candidate_id_1, "name": "Candidate 1", "numVotes": 1325 }, { "id": candidate_id_2, "name": "Candidate 2", "numVotes": 792 }, ], "totalBallotsCast": 2123, "numWinners": 1, "votesAllowed": 1, }], }, ) assert_ok(rv) rv = client.get(f"/election/{election_id}/audit/status") assert json.loads(rv.data)["contests"][0]["isTargeted"] is True
def setup_audit_board(client, election_id, jurisdiction_id, audit_board_id): rv = post_json( client, "/election/{}/jurisdiction/{}/audit-board/{}".format( election_id, jurisdiction_id, audit_board_id ), { "name": "Audit Board #1", "members": [ {"name": "Joe Schmo", "affiliation": "REP"}, {"name": "Jane Plain", "affiliation": ""}, ], }, ) assert_ok(rv)
def test_audit_board_contests_list_empty( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, audit_board_round_1_ids: List[str], json_contests: List[JSONDict], ): rv = put_json(client, f"/election/{election_id}/contest", [json_contests[1]]) assert_ok(rv) set_logged_in_user(client, UserType.AUDIT_BOARD, user_key=audit_board_round_1_ids[0]) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/contest" ) assert json.loads(rv.data) == {"contests": []}
def test_no_jurisdiction(client, election_id): rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( io.BytesIO(b"Jurisdiction,Admin Email"), "jurisdictions.csv", ) }, ) assert_ok(rv) # Process the file in the background. assert bgcompute_update_election_jurisdictions_file() == 1 election = Election.query.filter_by(id=election_id).one() assert election.jurisdictions == [] assert JurisdictionAdministration.query.count() == 0 assert User.query.count() == 0
def jurisdiction_ids(client: FlaskClient, election_id: str) -> List[str]: rv = client.put( f"/election/{election_id}/jurisdiction/file", data={ "jurisdictions": ( # We expect the API to order the jurisdictions by name, so we # upload them out of order. io.BytesIO(("Jurisdiction,Admin Email\n" f"J2,{DEFAULT_JA_EMAIL}\n" "J3,[email protected]\n" f"J1,{DEFAULT_JA_EMAIL}\n").encode()), "jurisdictions.csv", ) }, ) assert_ok(rv) bgcompute_update_election_jurisdictions_file() jurisdictions = (Jurisdiction.query.filter_by( election_id=election_id).order_by(Jurisdiction.name).all()) return [j.id for j in jurisdictions]
def round_2_id( client: FlaskClient, election_id: str, contest_ids: str, round_1_id: str, audit_board_round_1_ids: List[str], # pylint: disable=unused-argument ) -> str: run_audit_round(round_1_id, contest_ids[0], 0.5) set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) rv = post_json( client, f"/election/{election_id}/round", {"roundNum": 2}, ) assert_ok(rv) rv = client.get(f"/election/{election_id}/round", ) rounds = json.loads(rv.data)["rounds"] return str(rounds[1]["id"])
def run_whole_audit_flow(client, election_id, name, risk_limit, random_seed): ( url_prefix, contest_id, candidate_id_1, candidate_id_2, jurisdiction_id, _audit_board_id_1, _audit_board_id_2, num_ballots, ) = setup_whole_audit(client, election_id, name, risk_limit, random_seed) # post results for round 1 num_for_winner = int(num_ballots * 0.56) num_for_loser = num_ballots - num_for_winner rv = post_json( client, "{}/jurisdiction/{}/1/results".format(url_prefix, jurisdiction_id), { "contests": [ { "id": contest_id, "results": { candidate_id_1: num_for_winner, candidate_id_2: num_for_loser, }, } ] }, ) assert_ok(rv) rv = client.get("{}/audit/status".format(url_prefix)) status = json.loads(rv.data) round_contest = status["rounds"][0]["contests"][0] assert round_contest["id"] == contest_id assert round_contest["results"][candidate_id_1] == num_for_winner assert round_contest["results"][candidate_id_2] == num_for_loser assert round_contest["endMeasurements"]["isComplete"] assert math.floor(round_contest["endMeasurements"]["pvalue"] * 100) <= 5
def round_1_id( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], # pylint: disable=unused-argument contest_ids: str, # pylint: disable=unused-argument election_settings, # pylint: disable=unused-argument manifests, # pylint: disable=unused-argument ) -> str: set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) rv = post_json( client, f"/election/{election_id}/round", { "roundNum": 1, "sampleSize": SAMPLE_SIZE_ROUND_1 }, ) assert_ok(rv) rv = client.get(f"/election/{election_id}/round", ) rounds = json.loads(rv.data)["rounds"] return str(rounds[0]["id"])
def test_update_election(client: FlaskClient, election_id: str): # Get the existing data. rv = client.get(f"/election/{election_id}/settings") # Update the values. election = json.loads(rv.data) election["electionName"] = "An Updated Name" election["online"] = True election["randomSeed"] = "a new random seed" election["riskLimit"] = 15 election["state"] = USState.Mississippi rv = put_json(client, f"/election/{election_id}/settings", election) assert_ok(rv) election_record = Election.query.filter_by(id=election_id).one() assert election_record.election_name == "An Updated Name" assert election_record.online is True assert election_record.random_seed == "a new random seed" assert election_record.risk_limit == 15 assert election_record.state == USState.Mississippi
def test_audit_board_contests_list( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, audit_board_round_1_ids: List[str], json_contests: List[JSONDict], ): rv = put_json(client, f"/election/{election_id}/contest", json_contests) assert_ok(rv) set_logged_in_user(client, UserType.AUDIT_BOARD, user_key=audit_board_round_1_ids[0]) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/contest" ) contests = json.loads(rv.data) expected_contests = [{ **contest, "currentRoundStatus": None } for contest in [json_contests[0], json_contests[2]]] assert contests == {"contests": expected_contests}
def test_ballot_manifest_upload_duplicate_batch_name( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO( b"Batch Name,Number of Ballots,Storage Location,Tabulator\n" b"12,23,Bin 2,Tabulator 1\n" b"12,100,Bin 3,Tabulator 2\n" b"6,0,,\n" ), "manifest.csv", ) }, ) assert_ok(rv) bgcompute_update_ballot_manifest_file() rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest" ) compare_json( json.loads(rv.data), { "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,}, "processing": { "status": ProcessingStatus.ERRORED, "startedAt": assert_is_date, "completedAt": assert_is_date, "error": "Values in column Batch Name must be unique. Found duplicate value: 12.", }, }, )
def test_ballot_manifest_clear( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.put( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", data={ "manifest": ( io.BytesIO( b"Batch Name,Number of Ballots,Storage Location,Tabulator\n" b"1,23,Bin 2,Tabulator 1\n" ), "manifest.csv", ) }, ) assert_ok(rv) num_files = File.query.count() bgcompute_update_ballot_manifest_file() rv = client.delete( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", ) assert_ok(rv) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest" ) assert json.loads(rv.data) == {"file": None, "processing": None} jurisdiction = Jurisdiction.query.get(jurisdiction_ids[0]) assert jurisdiction.manifest_num_batches is None assert jurisdiction.manifest_num_ballots is None assert jurisdiction.batches == [] assert File.query.count() == num_files - 1