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 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_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_two_orgs_same_name(client: FlaskClient): org_id_1, _ = create_org_and_admin(user_email="*****@*****.**") org_id_2, _ = create_org_and_admin(user_email="*****@*****.**") set_logged_in_user(client, UserType.AUDIT_ADMIN, "*****@*****.**") rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id_1, "isMultiJurisdiction": True, }, ) assert rv.status_code == 200 assert json.loads(rv.data)["electionId"] set_logged_in_user(client, UserType.AUDIT_ADMIN, "*****@*****.**") rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id_2, "isMultiJurisdiction": True, }, ) assert rv.status_code == 200 assert json.loads(rv.data)["electionId"]
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_ja_ballots_before_audit_boards_set_up( 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 = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/ballots" ) ballots = json.loads(rv.data)["ballots"] assert len(ballots) == J1_BALLOTS_ROUND_1 compare_json( ballots[0], { "id": assert_is_id, "auditBoard": None, "batch": { "id": assert_is_id, "name": "1", "tabulator": None }, "position": 12, "status": "NOT_AUDITED", "interpretations": [], }, )
def test_in_org_duplicate_audit_name(client: FlaskClient): org_id, _user_id = create_org_and_admin(user_email="*****@*****.**") set_logged_in_user(client, UserType.AUDIT_ADMIN, "*****@*****.**") rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id, "isMultiJurisdiction": True, }, ) assert rv.status_code == 200 assert json.loads(rv.data)["electionId"] rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id, "isMultiJurisdiction": True, }, ) assert rv.status_code == 409 assert json.loads(rv.data) == { "errors": [ { "message": "An audit with name 'Test Audit' already exists within your organization", "errorType": "Conflict", } ] }
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_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_audit_boards_sign_off_missing_name( 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, member_2 = set_up_audit_board(client, election_id, jurisdiction_ids[0], contest_ids[0], audit_board_id) set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_id) for missing_field in ["memberName1", "memberName2"]: sign_off_request_body = { "memberName1": member_1, "memberName2": member_2 } del sign_off_request_body[missing_field] rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_id}/sign-off", sign_off_request_body, ) assert rv.status_code == 400 assert json.loads(rv.data) == { "errors": [{ "errorType": "Bad Request", "message": f"'{missing_field}' is a required property", }] }
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 test_ja_ballot_retrieval_list_bad_round_id( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/invalid-round-id/retrieval-list" ) assert rv.status_code == 404
def test_audit_boards_list_empty( 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 = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board", ) audit_boards = json.loads(rv.data) assert audit_boards == {"auditBoards": []}
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_boards_bad_round_id( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, # pylint: disable=unused-argument ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = post_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/not-a-valid-id/audit-board", [{ "name": "Audit Board #1" }], ) assert rv.status_code == 404
def test_ab_audit_ballot_not_found( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, audit_board_round_1_ids: List[str], ): set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_round_1_ids[0]) rv = put_json( client, f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/not-a-real-ballot-id", {}, ) assert rv.status_code == 404
def test_ballot_manifest_upload_missing_file( 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={}, ) assert rv.status_code == 400 assert json.loads(rv.data) == { "errors": [ { "errorType": "Bad Request", "message": "Missing required file parameter 'manifest'", } ] }
def test_ja_ballot_retrieval_list_round_2( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_2_id: str, audit_board_round_2_ids: str, # pylint: disable=unused-argument ): set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_2_id}/retrieval-list" ) assert rv.status_code == 200 assert "attachment; filename=" in rv.headers["Content-Disposition"] retrieval_list = rv.data.decode("utf-8").replace("\r\n", "\n") assert len(retrieval_list.splitlines()) == J1_BALLOTS_ROUND_2 + 1 assert retrieval_list == EXPECTED_RETRIEVAL_LIST_ROUND_2
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_ja_ballot_retrieval_list_before_audit_boards_set_up( 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 = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/retrieval-list" ) assert rv.status_code == 200 assert "attachment; filename=" in rv.headers["Content-Disposition"] retrieval_list = rv.data.decode("utf-8").replace("\r\n", "\n") assert ( retrieval_list == "Batch Name,Ballot Number,Storage Location,Tabulator,Ticket Numbers,Already Audited,Audit Board\n" )
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 test_in_org_with_logged_in_admin(client: FlaskClient): org_id, _user_id = create_org_and_admin(user_email="*****@*****.**") set_logged_in_user(client, UserType.AUDIT_ADMIN, "*****@*****.**") rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id, "isMultiJurisdiction": True, }, ) response = json.loads(rv.data) election_id = response.get("electionId", None) assert election_id, response rv = client.get(f"/election/{election_id}/audit/status") assert json.loads(rv.data)["organizationId"] == org_id
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_audit_boards_missing_field( 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 #2" }], ) assert rv.status_code == 400 assert json.loads(rv.data) == { "errors": [{ "errorType": "Bad Request", "message": "'name' is a required property", }] }
def test_audit_boards_wrong_round( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_1_id: str, round_2_id: str, # pylint: disable=unused-argument ): 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 rv.status_code == 409 assert json.loads(rv.data) == { "errors": [{ "errorType": "Conflict", "message": "Round 1 is not the current round", }] }
def test_in_org_with_logged_in_jurisdiction_admin(client: FlaskClient): org_id, _user_id = create_org_and_admin(user_email="*****@*****.**") set_logged_in_user(client, UserType.JURISDICTION_ADMIN, "*****@*****.**") rv = post_json( client, "/election/new", { "auditName": "Test Audit", "organizationId": org_id, "isMultiJurisdiction": True, }, ) assert json.loads(rv.data) == { "errors": [ { "message": f"User is not logged in as an audit admin and so does not have access to organization {org_id}", "errorType": "Forbidden", } ] } assert rv.status_code == 403
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_audit_boards_sign_off_before_finished( 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, member_2 = set_up_audit_board(client, election_id, jurisdiction_ids[0], contest_ids[0], audit_board_id) set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_id) # Undo some of the ballot auditing done by set_up_audit_board ballots = SampledBallot.query.filter_by( audit_board_id=audit_board_id).all() for ballot in ballots[:10]: ballot.status = BallotStatus.NOT_AUDITED db.session.commit() 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": member_2 }, ) assert rv.status_code == 409 assert json.loads(rv.data) == { "errors": [{ "errorType": "Conflict", "message": "Audit board is not finished auditing all assigned ballots", }] }
def test_ab_list_ballots_round_2( client: FlaskClient, election_id: str, jurisdiction_ids: List[str], round_2_id: str, audit_board_round_2_ids: List[str], # pylint: disable=unused-argument ): set_logged_in_user(client, UserType.AUDIT_BOARD, audit_board_round_2_ids[0]) rv = client.get( f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_2_id}/audit-board/{audit_board_round_2_ids[0]}/ballots" ) ballots = json.loads(rv.data)["ballots"] assert len(ballots) == AB1_BALLOTS_ROUND_2 compare_json( ballots[0], { "id": assert_is_id, "batch": { "id": assert_is_id, "name": "4", "tabulator": None }, "position": 3, "status": "NOT_AUDITED", "interpretations": [], "auditBoard": { "id": assert_is_id, "name": "Audit Board #1" }, }, ) previously_audited_ballots = [ b for b in ballots if b["status"] == "AUDITED" ] assert len(previously_audited_ballots) == 22