From f79cacd4becbb9d6b65fe0135ede0ca49758263d Mon Sep 17 00:00:00 2001 From: Caroline Mars Date: Mon, 28 Oct 2024 15:28:44 -0700 Subject: [PATCH] refactor server tests to call helper functions to upload files and handle error testing --- server/tests/api/test_activity.py | 31 +- server/tests/api/test_ballot_manifest.py | 135 ++-- server/tests/api/test_ballots.py | 23 +- server/tests/api/test_jurisdictions.py | 34 +- server/tests/api/test_jurisdictions_file.py | 270 ++++---- server/tests/ballot_comparison/conftest.py | 130 ++-- .../test_ballot_comparison.py | 287 ++++----- .../test_ballot_comparison_manifests.py | 171 +++-- .../test_contest_name_standardizations.py | 15 +- server/tests/ballot_comparison/test_cvrs.py | 598 +++++++++--------- .../test_standardized_contests.py | 267 +++----- server/tests/batch_comparison/conftest.py | 91 ++- .../batch_comparison/test_batch_comparison.py | 10 +- .../batch_comparison/test_batch_inventory.py | 450 ++++++------- .../batch_comparison/test_batch_tallies.py | 324 +++++----- server/tests/batch_comparison/test_batches.py | 38 +- .../test_multi_contest_batch_comparison.py | 33 +- ..._sample_extra_batches_by_counting_group.py | 248 ++++---- server/tests/conftest.py | 74 +-- server/tests/hybrid/conftest.py | 84 ++- server/tests/hybrid/test_hybrid.py | 137 ++-- server/tests/hybrid/test_hybrid_manifests.py | 91 ++- server/tests/test_full_hand_tally.py | 54 +- 23 files changed, 1584 insertions(+), 2011 deletions(-) diff --git a/server/tests/api/test_activity.py b/server/tests/api/test_activity.py index ca6c8cb48..5cbf6681e 100644 --- a/server/tests/api/test_activity.py +++ b/server/tests/api/test_activity.py @@ -318,20 +318,16 @@ def test_file_upload_errors( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={"manifest": (io.BytesIO(b"invalid"), "manifest.csv")}, + rv = setup_ballot_manifest_upload( + client, io.BytesIO(b"invalid"), election_id, jurisdiction_ids[0] ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(b"Batch Name,Number of Ballots\n" b"A,1"), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"A,1"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -339,9 +335,8 @@ def test_file_upload_errors( election.audit_type = AuditType.BATCH_COMPARISON db_session.commit() - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-tallies", - data={"batchTallies": (io.BytesIO(b"invalid"), "tallies.csv")}, + rv = setup_batch_tallies_upload( + client, io.BytesIO(b"invalid"), election_id, jurisdiction_ids[0] ) assert rv.status_code == 200 @@ -349,12 +344,8 @@ def test_file_upload_errors( election.audit_type = AuditType.BALLOT_COMPARISON db_session.commit() - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": (io.BytesIO(b""), "cvrs.csv"), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, io.BytesIO(b""), election_id, jurisdiction_ids[0], "DOMINION" ) assert_ok(rv) diff --git a/server/tests/api/test_ballot_manifest.py b/server/tests/api/test_ballot_manifest.py index 428836437..b1cde02c7 100644 --- a/server/tests/api/test_ballot_manifest.py +++ b/server/tests/api/test_ballot_manifest.py @@ -12,16 +12,11 @@ def test_ballot_manifest_upload( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/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"12,100\n" b"6,0\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n" b"12,100\n" b"6,0\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -32,7 +27,7 @@ def test_ballot_manifest_upload( json.loads(rv.data), { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { @@ -62,14 +57,11 @@ def test_ballot_manifest_clear( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n"), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -98,29 +90,21 @@ def test_ballot_manifest_replace_as_audit_admin( ): # Check that AA can also get/put/clear manifest set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) - rv = client.put( - f"/api/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"12,100\n" b"6,0,,\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n" b"12,100\n" b"6,0,,\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) file_id = Jurisdiction.query.get(jurisdiction_ids[0]).manifest_file_id - rv = client.put( - f"/api/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"12,6\n"), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n" b"12,6\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -150,14 +134,14 @@ def test_ballot_manifest_replace_as_audit_admin( assert json.loads(rv.data) == {"file": None, "processing": None} -def test_ballot_manifest_upload_missing_file( +def test_ballot_manifest_upload_missing_file_path( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", + rv = client.post( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest/upload-complete", data={}, ) assert rv.status_code == 400 @@ -165,7 +149,7 @@ def test_ballot_manifest_upload_missing_file( "errors": [ { "errorType": "Bad Request", - "message": "Missing required file parameter 'manifest'", + "message": "Missing required JSON parameter: storagePathKey", } ] } @@ -177,9 +161,24 @@ def test_ballot_manifest_upload_bad_csv( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={"manifest": (io.BytesIO(b"not a CSV file"), "random.txt")}, + rv = client.post( + "/api/file-upload", + data={ + "file": ( + io.BytesIO(b"not a CSV file"), + "random.txt", + ), + "key": "test_dir/random.txt", + }, + ) + assert_ok(rv) + rv = client.post( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest/upload-complete", + data={ + "storagePathKey": "test_dir/random.txt", + "fileName": "random.txt", + "fileType": "text/plain", + }, ) assert rv.status_code == 400 assert json.loads(rv.data) == { @@ -202,14 +201,11 @@ def test_ballot_manifest_upload_missing_field( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(header_row.encode() + b"\n1,2,3"), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(header_row.encode() + b"\n1,2,3"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -220,7 +216,7 @@ def test_ballot_manifest_upload_missing_field( json.loads(rv.data), { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { @@ -239,14 +235,12 @@ def test_ballot_manifest_upload_invalid_num_ballots( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,not a number\n"), - "manifest.csv", - ) - }, + + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,not a number\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -257,7 +251,7 @@ def test_ballot_manifest_upload_invalid_num_ballots( json.loads(rv.data), { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { @@ -276,16 +270,11 @@ def test_ballot_manifest_upload_duplicate_batch_name( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Batch Name,Number of Ballots\n" b"12,23\n" b"12,100\n" b"6,0\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"12,23\n" b"12,100\n" b"6,0\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -296,7 +285,7 @@ def test_ballot_manifest_upload_duplicate_batch_name( json.loads(rv.data), { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { diff --git a/server/tests/api/test_ballots.py b/server/tests/api/test_ballots.py index 387332d40..5cd591efb 100644 --- a/server/tests/api/test_ballots.py +++ b/server/tests/api/test_ballots.py @@ -1248,19 +1248,16 @@ def test_ballots_human_sort_order( "Batch 2", "Batch 10", ] - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - ( - "Batch Name,Number of Ballots\n" - + "\n".join(f"{batch},10" for batch in human_ordered_batches) - ).encode() - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + ( + "Batch Name,Number of Ballots\n" + + "\n".join(f"{batch},10" for batch in human_ordered_batches) + ).encode() + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) diff --git a/server/tests/api/test_jurisdictions.py b/server/tests/api/test_jurisdictions.py index 72c31a8cd..109fa39e5 100644 --- a/server/tests/api/test_jurisdictions.py +++ b/server/tests/api/test_jurisdictions.py @@ -73,14 +73,11 @@ def test_jurisdictions_list_with_manifest( manifest = ( b"Batch Name,Number of Ballots\n" b"1,23\n" b"2,101\n" b"3,122\n" b"4,400" ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(manifest), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(manifest), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -94,7 +91,7 @@ def test_jurisdictions_list_with_manifest( "name": "J1", "ballotManifest": { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { @@ -140,7 +137,9 @@ def test_jurisdictions_list_with_manifest( rv = client.get( f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest/csv" ) - assert rv.headers["Content-Disposition"] == 'attachment; filename="manifest.csv"' + assert rv.headers["Content-Disposition"].startswith( + 'attachment; filename="manifest' + ) assert rv.data == manifest @@ -155,14 +154,11 @@ def test_duplicate_batch_name(client, election_id, jurisdiction_ids): set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/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"1,101\n"), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(b"Batch Name,Number of Ballots\n" b"1,23\n" b"1,101\n"), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -176,7 +172,7 @@ def test_duplicate_batch_name(client, election_id, jurisdiction_ids): "name": "J1", "ballotManifest": { "file": { - "name": "manifest.csv", + "name": asserts_startswith("manifest"), "uploadedAt": assert_is_date, }, "processing": { diff --git a/server/tests/api/test_jurisdictions_file.py b/server/tests/api/test_jurisdictions_file.py index 6929eb53f..950388d9f 100644 --- a/server/tests/api/test_jurisdictions_file.py +++ b/server/tests/api/test_jurisdictions_file.py @@ -6,12 +6,12 @@ def test_missing_file(client: FlaskClient, election_id: str): - rv = client.put(f"/api/election/{election_id}/jurisdiction/file") + rv = client.post(f"/api/election/{election_id}/jurisdiction/file/upload-complete") assert rv.status_code == 400 assert json.loads(rv.data) == { "errors": [ { - "message": "Missing required file parameter 'jurisdictions'", + "message": "Missing required JSON parameter: storagePathKey", "errorType": "Bad Request", } ] @@ -19,9 +19,18 @@ def test_missing_file(client: FlaskClient, election_id: str): def test_bad_csv_file(client: FlaskClient, election_id: str): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={"jurisdictions": (io.BytesIO(b"not a CSV file"), "random.txt")}, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"not a CSV file"), + election_id, + ) + rv = client.post( + f"/api/election/{election_id}/jurisdiction/file/upload-complete", + data={ + "storagePathKey": "test_dir/random.txt", + "fileName": "random.txt", + "fileType": "text/plain", + }, ) assert rv.status_code == 400 assert json.loads(rv.data) == { @@ -35,14 +44,10 @@ def test_bad_csv_file(client: FlaskClient, election_id: str): def test_missing_one_csv_field(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction\nJurisdiction #1"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction\nJurisdiction #1"), + election_id, ) assert_ok(rv) @@ -50,7 +55,10 @@ def test_missing_one_csv_field(client, election_id): compare_json( json.loads(rv.data), { - "file": {"name": "jurisdictions.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("jurisdictions"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.ERRORED, "startedAt": assert_is_date, @@ -66,21 +74,22 @@ def test_jurisdictions_file_metadata(client, election_id): assert json.loads(rv.data) == {"file": None, "processing": None} contents = "Jurisdiction,Admin Email\nJ1,ja@example.com" - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={"jurisdictions": (io.BytesIO(contents.encode()), "jurisdictions.csv")}, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(contents.encode()), + election_id, ) assert_ok(rv) election = Election.query.filter_by(id=election_id).one() - assert election.jurisdictions_file.name == "jurisdictions.csv" + assert election.jurisdictions_file.name.startswith("jurisdictions") assert election.jurisdictions_file.uploaded_at rv = client.get(f"/api/election/{election_id}/jurisdiction/file") response = json.loads(rv.data) file = response["file"] processing = response["processing"] - assert file["name"] == "jurisdictions.csv" + assert file["name"].startswith("jurisdictions") assert file["uploadedAt"] assert processing["status"] == ProcessingStatus.PROCESSED assert processing["startedAt"] @@ -88,26 +97,20 @@ def test_jurisdictions_file_metadata(client, election_id): assert processing["error"] is None rv = client.get(f"/api/election/{election_id}/jurisdiction/file/csv") - assert ( - rv.headers["Content-Disposition"] == 'attachment; filename="jurisdictions.csv"' + assert rv.headers["Content-Disposition"].startswith( + 'attachment; filename="jurisdictions' ) assert rv.data.decode("utf-8") == contents def test_replace_jurisdictions_file(client, election_id): # Create the initial file. - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\n" - b"J1,ja@example.com\n" - b"J2,ja2@example.com" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + b"Jurisdiction,Admin Email\n" b"J1,ja@example.com\n" b"J2,ja2@example.com" + ), + election_id, ) assert_ok(rv) @@ -123,18 +126,12 @@ def test_replace_jurisdictions_file(client, election_id): file_id = election.jurisdictions_file_id # Replace it with another file. - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\n" - b"J2,ja2@example.com\n" - b"J3,ja3@example.com" - ), - "jurisdictions2.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + b"Jurisdiction,Admin Email\n" b"J2,ja2@example.com\n" b"J3,ja3@example.com" + ), + election_id, ) assert_ok(rv) @@ -143,7 +140,7 @@ def test_replace_jurisdictions_file(client, election_id): ) assert rv.status_code == 200 response = json.loads(rv.data) - assert response["file"]["name"] == "jurisdictions2.csv" + assert response["file"]["name"].startswith("jurisdictions") assert File.query.get(file_id) is None, "the old file should have been deleted" @@ -158,14 +155,10 @@ def test_replace_jurisdictions_file(client, election_id): def test_no_jurisdiction(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email"), + election_id, ) assert_ok(rv) @@ -174,14 +167,10 @@ def test_no_jurisdiction(client, election_id): def test_single_jurisdiction_single_admin(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email\nJ1,a1@example.com"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\nJ1,a1@example.com"), + election_id, ) assert_ok(rv) @@ -195,16 +184,10 @@ def test_single_jurisdiction_single_admin(client, election_id): def test_single_jurisdiction_multiple_admins(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\nJ1,a1@example.com\nJ1,a2@example.com" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\nJ1,a1@example.com\nJ1,a2@example.com"), + election_id, ) assert_ok(rv) @@ -219,16 +202,10 @@ def test_single_jurisdiction_multiple_admins(client, election_id): def test_multiple_jurisdictions_single_admin(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\nJ1,a1@example.com\nJ2,a1@example.com" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\nJ1,a1@example.com\nJ2,a1@example.com"), + election_id, ) assert_ok(rv) @@ -247,19 +224,15 @@ def test_download_jurisdictions_file_not_found(client, election_id): def test_convert_emails_to_lowercase(client, election_id): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\n" - b"J1,lowecase@example.com\n" - b"J2,UPPERCASE@EXAMPLE.COM\n" - b"J3,MiXeDcAsE@eXaMpLe.CoM\n" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + b"Jurisdiction,Admin Email\n" + b"J1,lowecase@example.com\n" + b"J2,UPPERCASE@EXAMPLE.COM\n" + b"J3,MiXeDcAsE@eXaMpLe.CoM\n" + ), + election_id, ) assert_ok(rv) @@ -274,14 +247,10 @@ def test_upload_jurisdictions_file_after_audit_starts( election_id: str, round_1_id: str, # pylint: disable=unused-argument ): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,j1@example.com\n"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,j1@example.com\n"), + election_id, ) assert rv.status_code == 409 assert json.loads(rv.data) == { @@ -298,18 +267,12 @@ def test_upload_jurisdictions_file_duplicate_row( client: FlaskClient, election_id: str, ): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email\n" - b"J1,j1@example.com\n" - b"J1,j1@example.com" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + b"Jurisdiction,Admin Email\n" b"J1,j1@example.com\n" b"J1,j1@example.com" + ), + election_id, ) assert_ok(rv) @@ -317,7 +280,10 @@ def test_upload_jurisdictions_file_duplicate_row( compare_json( json.loads(rv.data), { - "file": {"name": "jurisdictions.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("jurisdictions"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.ERRORED, "startedAt": assert_is_date, @@ -340,38 +306,26 @@ def test_jurisdictions_file_dont_clobber_other_elections( ) # Add jurisdictions. - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,j1@example.com\n"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\n" b"J1,j1@example.com\n"), + election_id, ) assert_ok(rv) # Add jurisdictions for other election - rv = client.put( - f"/api/election/{other_election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email\n" b"J2,j2@example.com\n"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\n" b"J2,j2@example.com\n"), + other_election_id, ) assert_ok(rv) # Now change them - rv = client.put( - f"/api/election/{other_election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO(b"Jurisdiction,Admin Email\n" b"J3,j3@example.com\n"), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO(b"Jurisdiction,Admin Email\n" b"J3,j3@example.com\n"), + other_election_id, ) assert_ok(rv) @@ -387,26 +341,22 @@ def test_jurisdictions_file_dont_clobber_other_elections( def test_jurisdictions_file_expected_num_ballots(client: FlaskClient, election_id: str): - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - b"Jurisdiction,Admin Email,Expected Number of Ballots\n" - # If multiple rows for the same jurisdiction, the last one wins - b"J1,a1@example.com,10\n" - b"J1,a2@example.com,20\n" - # Value is optional - b"J2,a1@example.com,\n" - # If no value in some rows for jurisdiction, the last value wins - b"J3,a1@example.com,10\n" - b"J3,a2@example.com,\n" - b"J4,a1@example.com,\n" - b"J4,a2@example.com,500\n" - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + b"Jurisdiction,Admin Email,Expected Number of Ballots\n" + # If multiple rows for the same jurisdiction, the last one wins + b"J1,a1@example.com,10\n" + b"J1,a2@example.com,20\n" + # Value is optional + b"J2,a1@example.com,\n" + # If no value in some rows for jurisdiction, the last value wins + b"J3,a1@example.com,10\n" + b"J3,a2@example.com,\n" + b"J4,a1@example.com,\n" + b"J4,a2@example.com,500\n" + ), + election_id, ) assert_ok(rv) diff --git a/server/tests/ballot_comparison/conftest.py b/server/tests/ballot_comparison/conftest.py index 0410f94f5..284f0b292 100644 --- a/server/tests/ballot_comparison/conftest.py +++ b/server/tests/ballot_comparison/conftest.py @@ -87,36 +87,30 @@ def manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List[str] set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3\n" - b"TABULATOR2,BATCH2,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3\n" + b"TABULATOR2,BATCH2,6" + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3\n" - b"TABULATOR2,BATCH2,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3\n" + b"TABULATOR2,BATCH2,6" + ), + election_id, + jurisdiction_ids[1], ) assert_ok(rv) @@ -127,20 +121,17 @@ def ess_manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List[ set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_id}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"0001,BATCH1,3\n" - b"0001,BATCH2,3\n" - b"0002,BATCH1,3\n" - b"0002,BATCH2,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"0001,BATCH1,3\n" + b"0001,BATCH2,3\n" + b"0002,BATCH1,3\n" + b"0002,BATCH2,6" + ), + election_id, + jurisdiction_id, ) assert_ok(rv) @@ -151,20 +142,17 @@ def hart_manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_id}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH3,3\n" - b"TABULATOR2,BATCH4,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH3,3\n" + b"TABULATOR2,BATCH4,6" + ), + election_id, + jurisdiction_id, ) assert_ok(rv) @@ -179,25 +167,19 @@ def cvrs( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) diff --git a/server/tests/ballot_comparison/test_ballot_comparison.py b/server/tests/ballot_comparison/test_ballot_comparison.py index a2a8290c3..36600fb91 100644 --- a/server/tests/ballot_comparison/test_ballot_comparison.py +++ b/server/tests/ballot_comparison/test_ballot_comparison.py @@ -88,20 +88,15 @@ def test_set_contest_metadata_on_manifest_and_cvr_upload( assert contest["totalBallotsCast"] is None assert contest["votesAllowed"] is None - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3\n" - b"TABULATOR2,BATCH2,6" - ), - "manifest.csv", - ) - }, + file_content = io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3\n" + b"TABULATOR2,BATCH2,6" + ) + rv = setup_ballot_manifest_upload( + client, file_content, election_id, jurisdiction_ids[0] ) assert_ok(rv) @@ -112,20 +107,17 @@ def test_set_contest_metadata_on_manifest_and_cvr_upload( assert contest["totalBallotsCast"] is None assert contest["votesAllowed"] is None - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3\n" - b"TABULATOR2,BATCH2,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3\n" + b"TABULATOR2,BATCH2,6" + ), + election_id, + jurisdiction_ids[1], ) assert_ok(rv) @@ -136,15 +128,12 @@ def test_set_contest_metadata_on_manifest_and_cvr_upload( assert contest["totalBallotsCast"] == 30 assert contest["votesAllowed"] is None - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -155,15 +144,12 @@ def test_set_contest_metadata_on_manifest_and_cvr_upload( assert contest["totalBallotsCast"] == 30 assert contest["votesAllowed"] is None - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -185,32 +171,26 @@ def test_set_contest_metadata_on_manifest_and_cvr_upload( # Contest metadata changes on new manifest/CVR upload # - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3" + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) new_cvr = "\n".join(TEST_CVRS.splitlines()[:10]) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(new_cvr.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(new_cvr.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -255,15 +235,12 @@ def test_cvr_choice_name_validation( contest = json.loads(rv.data)["contests"][0] assert "cvrChoiceNameConsistencyError" not in contest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -271,15 +248,12 @@ def test_cvr_choice_name_validation( contest = json.loads(rv.data)["contests"][0] assert "cvrChoiceNameConsistencyError" not in contest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -288,15 +262,12 @@ def test_cvr_choice_name_validation( assert "cvrChoiceNameConsistencyError" not in contest modified_cvrs = TEST_CVRS.replace("Choice", "CHOICE") - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(modified_cvrs.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(modified_cvrs.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -314,15 +285,12 @@ def test_cvr_choice_name_validation( } modified_cvrs = TEST_CVRS.replace("Choice 1-1", "CHOICE 1-1") - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(modified_cvrs.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(modified_cvrs.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -340,15 +308,12 @@ def test_cvr_choice_name_validation( } modified_cvrs = TEST_CVRS_WITH_CHOICE_REMOVED - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(modified_cvrs.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(modified_cvrs.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -357,15 +322,12 @@ def test_cvr_choice_name_validation( assert "cvrChoiceNameConsistencyError" not in contest modified_cvrs = TEST_CVRS_WITH_EXTRA_CHOICE - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(modified_cvrs.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(modified_cvrs.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) assert_ok(rv) @@ -407,20 +369,16 @@ def test_set_contest_metadata_on_jurisdiction_change( # Upload new jurisdictions, removing J1 set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - ( - "Jurisdiction,Admin Email\n" - f"J2,{default_ja_email(election_id)}\n" - f"J3,j3-{election_id}@example.com\n" - ).encode() - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + ( + "Jurisdiction,Admin Email\n" + f"J2,{default_ja_email(election_id)}\n" + f"J3,j3-{election_id}@example.com\n" + ).encode() + ), + election_id, ) assert_ok(rv) @@ -733,21 +691,16 @@ def test_ballot_comparison_two_rounds( snapshot, ): set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) - # AA uploads standardized contests file - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO( - b"Contest Name,Jurisdictions\n" - b'Contest 1,"J1,J2"\n' - b'Contest 2,"J1,J2"\n' - b"Contest 3,J2\n" - ), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO( + b"Contest Name,Jurisdictions\n" + b'Contest 1,"J1,J2"\n' + b'Contest 2,"J1,J2"\n' + b"Contest 3,J2\n" + ), + election_id, ) assert_ok(rv) @@ -1405,10 +1358,10 @@ def test_ballot_comparison_ess( 13,p,bs,Choice 1-1,Choice 2-3 15,p,bs,Choice 1-1,Choice 2-3 """ - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": [ + rv = setup_cvrs_upload( + client, + zip_cvrs( + [ ( io.BytesIO(ESS_BALLOTS_1.encode()), "ess_ballots_1.csv", @@ -1421,15 +1374,18 @@ def test_ballot_comparison_ess( io.BytesIO(j1_cvr.encode()), "ess_cvr.csv", ), - ], - "cvrFileType": "ESS", - }, + ] + ), + election_id, + jurisdiction_ids[0], + "ESS", + "application/zip", ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": [ + rv = setup_cvrs_upload( + client, + zip_cvrs( + [ ( io.BytesIO(ESS_BALLOTS_1.encode()), "ess_ballots_1.csv", @@ -1442,9 +1398,12 @@ def test_ballot_comparison_ess( io.BytesIO(j2_cvr.encode()), "ess_cvr.csv", ), - ], - "cvrFileType": "ESS", - }, + ] + ), + election_id, + jurisdiction_ids[1], + "ESS", + "application/zip", ) assert_ok(rv) diff --git a/server/tests/ballot_comparison/test_ballot_comparison_manifests.py b/server/tests/ballot_comparison/test_ballot_comparison_manifests.py index 8435b86bd..fe2dfe9bb 100644 --- a/server/tests/ballot_comparison/test_ballot_comparison_manifests.py +++ b/server/tests/ballot_comparison/test_ballot_comparison_manifests.py @@ -19,50 +19,42 @@ def manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List[str] set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Container,Tabulator,Batch Name,Number of Ballots\n" - b"CONTAINER1,TABULATOR1,BATCH1,50\n" - b"CONTAINER1,TABULATOR1,BATCH2,50\n" - b"CONTAINER1,TABULATOR2,BATCH1,50\n" - b"CONTAINER1,TABULATOR2,BATCH2,50\n" - b"CONTAINER2,TABULATOR1,BATCH3,50\n" - b"CONTAINER2,TABULATOR1,BATCH4,50\n" - b"CONTAINER2,TABULATOR2,BATCH3,50\n" - b"CONTAINER2,TABULATOR2,BATCH4,50\n" - b"CONTAINER3,TABULATOR1,BATCH5,50\n" - b"CONTAINER4,TABULATOR1,BATCH6,50\n" - b"CONTAINER5,TABULATOR2,BATCH5,50\n" - b"CONTAINER6,TABULATOR2,BATCH6,50\n" - b"CONTAINER7,TABULATOR1,BATCH7,50\n" - b"CONTAINER8,TABULATOR1,BATCH8,50\n" - b"CONTAINER9,TABULATOR2,BATCH7,50\n" - b"CONTAINER0,TABULATOR2,BATCH8,50\n" - ), - "manifest.csv", - ) - }, + setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Container,Tabulator,Batch Name,Number of Ballots\n" + b"CONTAINER1,TABULATOR1,BATCH1,50\n" + b"CONTAINER1,TABULATOR1,BATCH2,50\n" + b"CONTAINER1,TABULATOR2,BATCH1,50\n" + b"CONTAINER1,TABULATOR2,BATCH2,50\n" + b"CONTAINER2,TABULATOR1,BATCH3,50\n" + b"CONTAINER2,TABULATOR1,BATCH4,50\n" + b"CONTAINER2,TABULATOR2,BATCH3,50\n" + b"CONTAINER2,TABULATOR2,BATCH4,50\n" + b"CONTAINER3,TABULATOR1,BATCH5,50\n" + b"CONTAINER4,TABULATOR1,BATCH6,50\n" + b"CONTAINER5,TABULATOR2,BATCH5,50\n" + b"CONTAINER6,TABULATOR2,BATCH6,50\n" + b"CONTAINER7,TABULATOR1,BATCH7,50\n" + b"CONTAINER8,TABULATOR1,BATCH8,50\n" + b"CONTAINER9,TABULATOR2,BATCH7,50\n" + b"CONTAINER0,TABULATOR2,BATCH8,50\n" + ), + election_id, + jurisdiction_ids[0], ) - assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,50\n" - b"TABULATOR1,BATCH2,50\n" - b"TABULATOR2,BATCH1,50\n" - b"TABULATOR2,BATCH2,50" - ), - "manifest.csv", - ) - }, + setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,50\n" + b"TABULATOR1,BATCH2,50\n" + b"TABULATOR2,BATCH1,50\n" + b"TABULATOR2,BATCH2,50" + ), + election_id, + jurisdiction_ids[1], ) - assert_ok(rv) @pytest.fixture @@ -89,17 +81,13 @@ def cvrs( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(j1_cvr.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + setup_cvrs_upload( + client, + io.BytesIO(j1_cvr.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) - assert_ok(rv) j2_cvr_lines = [ f"TABULATOR{tabulator},BATCH{batch},{ballot},{tabulator}-{batch}-{ballot},x,x,0,0,0,0,0" @@ -115,17 +103,13 @@ def cvrs( [f"{i},{line}" for i, line in enumerate(j2_cvr_lines)] ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(j2_cvr.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + setup_cvrs_upload( + client, + io.BytesIO(j2_cvr.encode()), + election_id, + jurisdiction_ids[1], + "DOMINION", ) - assert_ok(rv) def test_ballot_comparison_container_manifest( @@ -298,25 +282,23 @@ def test_ballot_comparison_manifest_missing_tabulator( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Container,Batch Name,Number of Ballots\n" - b"A,1,20\n" - b"A,2,20\n" - b"A,1,20\n" - b"A,2,20\n" - b"B,3,20\n" - b"B,4,20\n" - b"B,3,20\n" - b"B,4,20" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Container,Batch Name,Number of Ballots\n" + b"A,1,20\n" + b"A,2,20\n" + b"A,1,20\n" + b"A,2,20\n" + b"B,3,20\n" + b"B,4,20\n" + b"B,3,20\n" + b"B,4,20" + ), + election_id, + jurisdiction_ids[0], ) + assert_ok(rv) rv = client.get( @@ -325,7 +307,10 @@ def test_ballot_comparison_manifest_missing_tabulator( compare_json( json.loads(rv.data), { - "file": {"name": "manifest.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, "processing": { "completedAt": assert_is_date, "error": "Missing required column: Tabulator.", @@ -344,17 +329,14 @@ def test_ballot_comparison_manifest_unexpected_cvr_column( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Container,Tabulator,Batch Name,Number of Ballots,CVR\n" - b"CONTAINER1,TABULATOR1,BATCH1,50,Yes\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Container,Tabulator,Batch Name,Number of Ballots,CVR\n" + b"CONTAINER1,TABULATOR1,BATCH1,50,Yes\n" + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -364,7 +346,10 @@ def test_ballot_comparison_manifest_unexpected_cvr_column( compare_json( json.loads(rv.data), { - "file": {"name": "manifest.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, "processing": { "completedAt": assert_is_date, "error": "Found unexpected columns. Allowed columns: Batch Name, Container, Number of Ballots, Tabulator.", diff --git a/server/tests/ballot_comparison/test_contest_name_standardizations.py b/server/tests/ballot_comparison/test_contest_name_standardizations.py index e39043cb1..79368d5ec 100644 --- a/server/tests/ballot_comparison/test_contest_name_standardizations.py +++ b/server/tests/ballot_comparison/test_contest_name_standardizations.py @@ -237,15 +237,12 @@ def test_standardize_contest_names_cvr_change( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.replace("Contest 1", "Contest A").encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.replace("Contest 1", "Contest A").encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) diff --git a/server/tests/ballot_comparison/test_cvrs.py b/server/tests/ballot_comparison/test_cvrs.py index 2eee07ae5..5d9db2528 100644 --- a/server/tests/ballot_comparison/test_cvrs.py +++ b/server/tests/ballot_comparison/test_cvrs.py @@ -1,10 +1,9 @@ import io, json -from typing import BinaryIO, Dict, List, TypedDict, Tuple +from typing import List, TypedDict, Tuple from flask.testing import FlaskClient from ...models import * # pylint: disable=wildcard-import from ..helpers import * # pylint: disable=wildcard-import -from ...util.file import zip_files from .conftest import TEST_CVRS @@ -34,15 +33,12 @@ def test_dominion_cvr_upload( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -52,11 +48,12 @@ def test_dominion_cvr_upload( rv = client.get( f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs" ) + print(json.loads(rv.data)) compare_json( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -103,7 +100,7 @@ def test_dominion_cvr_upload( jurisdictions[0]["cvrs"], { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -124,7 +121,7 @@ def test_dominion_cvr_upload( f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs/csv" ) assert rv.status_code == 200 - assert rv.headers["Content-Disposition"] == 'attachment; filename="cvrs.csv"' + assert rv.headers["Content-Disposition"].startswith('attachment; filename="cvrs') assert rv.data == TEST_CVRS.encode() @@ -160,15 +157,12 @@ def test_cvrs_counting_group( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(COUNTING_GROUP_CVR.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(COUNTING_GROUP_CVR.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -187,7 +181,7 @@ def test_cvrs_counting_group( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -258,15 +252,12 @@ def test_dominion_cvr_unique_voting_identifier( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(DOMINION_UNIQUE_VOTING_IDENTIFIER_CVR.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(DOMINION_UNIQUE_VOTING_IDENTIFIER_CVR.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -285,7 +276,7 @@ def test_dominion_cvr_unique_voting_identifier( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -354,15 +345,12 @@ def test_dominion_cvrs_with_leading_equal_signs( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(DOMINION_CVRS_WITH_LEADING_EQUAL_SIGNS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(DOMINION_CVRS_WITH_LEADING_EQUAL_SIGNS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -377,11 +365,12 @@ def test_dominion_cvrs_with_leading_equal_signs( rv = client.get( f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs" ) + print(json.loads(rv.data)) compare_json( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -406,15 +395,12 @@ def test_cvrs_clear( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -457,29 +443,23 @@ def test_cvrs_replace_as_audit_admin( ): # Check that AA can also get/put/clear batch tallies set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) file_id = Jurisdiction.query.get(jurisdiction_ids[0]).cvr_file_id - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO("\n".join(TEST_CVRS.splitlines()[:-2]).encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO("\n".join(TEST_CVRS.splitlines()[:-2]).encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -517,8 +497,8 @@ def test_cvrs_upload_missing_file( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", + rv = client.post( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs/upload-complete", data={}, ) assert rv.status_code == 400 @@ -526,7 +506,7 @@ def test_cvrs_upload_missing_file( "errors": [ { "errorType": "Bad Request", - "message": "Missing required file parameter 'cvrs'", + "message": "CVR file type is required", } ] } @@ -541,10 +521,12 @@ def test_cvrs_upload_bad_csv( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", + rv = client.post( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs/upload-complete", data={ - "cvrs": (io.BytesIO(b"not a CSV file"), "random.txt"), + "storagePathKey": "random.txt", + "fileName": "random.txt", + "fileType": "text/plain", "cvrFileType": "DOMINION", }, ) @@ -575,14 +557,12 @@ def test_cvrs_wrong_audit_type( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ) - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert rv.status_code == 409 assert json.loads(rv.data) == { @@ -603,14 +583,12 @@ def test_cvrs_before_manifests( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ) - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert rv.status_code == 409 assert json.loads(rv.data) == { @@ -657,15 +635,12 @@ def test_cvrs_newlines( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(NEWLINE_CVR.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(NEWLINE_CVR.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -684,7 +659,7 @@ def test_cvrs_newlines( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -1006,15 +981,12 @@ def test_invalid_cvrs( ] for invalid_cvr, expected_error, cvr_file_type in invalid_cvrs: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(invalid_cvr.encode()), - "cvrs.csv", - ), - "cvrFileType": cvr_file_type, - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(invalid_cvr.encode()), + election_id, + jurisdiction_ids[0], + cvr_file_type, ) assert_ok(rv) @@ -1025,7 +997,7 @@ def test_invalid_cvrs( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": cvr_file_type, }, @@ -1059,20 +1031,18 @@ def test_cvr_reprocess_after_manifest_reupload( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR2,BATCH2,6\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR2,BATCH2,6\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3" + ), + election_id, + jurisdiction_ids[0], ) + assert_ok(rv) set_logged_in_user(client, UserType.AUDIT_ADMIN, DEFAULT_AA_EMAIL) @@ -1091,7 +1061,7 @@ def test_cvr_reprocess_after_manifest_reupload( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -1115,20 +1085,17 @@ def test_cvr_reprocess_after_manifest_reupload( assert Jurisdiction.query.get(jurisdiction_ids[0]).cvr_contests_metadata is None # Fix the manifest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Tabulator,Batch Name,Number of Ballots\n" - b"TABULATOR1,BATCH1,3\n" - b"TABULATOR1,BATCH2,3\n" - b"TABULATOR2,BATCH1,3\n" - b"TABULATOR2,BATCH2,6" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Tabulator,Batch Name,Number of Ballots\n" + b"TABULATOR1,BATCH1,3\n" + b"TABULATOR1,BATCH2,3\n" + b"TABULATOR2,BATCH1,3\n" + b"TABULATOR2,BATCH2,6" + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -1148,7 +1115,7 @@ def test_cvr_reprocess_after_manifest_reupload( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -1208,15 +1175,12 @@ def test_clearballot_cvr_upload( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(CLEARBALLOT_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "CLEARBALLOT", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(CLEARBALLOT_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "CLEARBALLOT", ) assert_ok(rv) @@ -1235,7 +1199,7 @@ def test_clearballot_cvr_upload( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "CLEARBALLOT", }, @@ -1284,15 +1248,12 @@ def test_clearballot_cvr_upload_invalid( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(CLEARBALLOT_CVRS_INVALID.encode()), - "cvrs.csv", - ), - "cvrFileType": "CLEARBALLOT", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(CLEARBALLOT_CVRS_INVALID.encode()), + election_id, + jurisdiction_ids[0], + "CLEARBALLOT", ) assert_ok(rv) @@ -1312,7 +1273,7 @@ def test_clearballot_cvr_upload_invalid( { "file": { "cvrFileType": "CLEARBALLOT", - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -1495,9 +1456,13 @@ def test_ess_cvr_upload( ) for cvrs in test_cases: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={"cvrs": cvrs, "cvrFileType": "ESS"}, + rv = setup_cvrs_upload( + client, + zip_cvrs(cvrs), + election_id, + jurisdiction_ids[0], + "ESS", + "application/zip", ) assert_ok(rv) @@ -1509,7 +1474,7 @@ def test_ess_cvr_upload( { "file": { "cvrFileType": "ESS", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -1843,9 +1808,13 @@ def replace_line(string: str, line: int, new_line: str) -> str: ) for invalid_cvrs, expected_error in test_cases: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={"cvrs": invalid_cvrs, "cvrFileType": "ESS"}, + rv = setup_cvrs_upload( + client, + zip_cvrs(invalid_cvrs), + election_id, + jurisdiction_ids[0], + "ESS", + "application/zip", ) assert_ok(rv) @@ -1857,7 +1826,7 @@ def replace_line(string: str, line: int, new_line: str) -> str: { "file": { "cvrFileType": "ESS", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -1902,9 +1871,13 @@ def test_ess_cvr_upload_cvr_file_with_tabulator_cvr_column( (io.BytesIO(ESS_BALLOTS_2.encode()), "ess_ballots_2.csv"), ] - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={"cvrs": cvrs, "cvrFileType": "ESS"}, + rv = setup_cvrs_upload( + client, + zip_cvrs(cvrs), + election_id, + jurisdiction_ids[0], + "ESS", + "application/zip", ) assert_ok(rv) @@ -1916,7 +1889,7 @@ def test_ess_cvr_upload_cvr_file_with_tabulator_cvr_column( { "file": { "cvrFileType": "ESS", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -1932,9 +1905,13 @@ def test_ess_cvr_upload_cvr_file_with_tabulator_cvr_column( }, ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={"cvrs": cvrs_with_override_cvr_file_name, "cvrFileType": "ESS"}, + rv = setup_cvrs_upload( + client, + zip_cvrs(cvrs_with_override_cvr_file_name), + election_id, + jurisdiction_ids[0], + "ESS", + "application/zip", ) assert_ok(rv) @@ -1946,7 +1923,7 @@ def test_ess_cvr_upload_cvr_file_with_tabulator_cvr_column( { "file": { "cvrFileType": "ESS", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -2170,16 +2147,6 @@ def build_contest(contest_name: str, choice_names: List[str]): """ -def zip_hart_cvrs(cvrs: List[str]): - files: Dict[str, BinaryIO] = { - f"cvr-{i}.xml": io.BytesIO(cvr.encode()) for i, cvr in enumerate(cvrs) - } - # There's usually a WriteIns directory in the zip file - simulate that to - # make sure it gets skipped - files["WriteIns"] = io.BytesIO() - return io.BytesIO(zip_files(files).read()) - - def test_hart_cvr_upload( client: FlaskClient, election_id: str, @@ -2188,12 +2155,13 @@ def test_hart_cvr_upload( snapshot, ): # Upload CVRs - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": [(zip_hart_cvrs(HART_CVRS), "cvrs.zip")], - "cvrFileType": "HART", - }, + rv = setup_cvrs_upload( + client, + zip_hart_cvrs(HART_CVRS), + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", ) assert_ok(rv) @@ -2212,7 +2180,7 @@ def test_hart_cvr_upload( json.loads(rv.data), { "file": { - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "HART", }, @@ -2366,21 +2334,25 @@ class TestCase(TypedDict): ] for test_case in test_cases: + print("on test case ", test_case) scanned_ballot_information_files = [ (string_to_bytes_io(file_contents), f"scanned-ballot-information-{i}.csv") for i, file_contents in enumerate( test_case["scanned_ballot_information_file_contents"] ) ] - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": [ + rv = setup_cvrs_upload( + client, + zip_cvrs( + [ (zip_hart_cvrs(HART_CVRS), "cvrs.zip"), *scanned_ballot_information_files, - ], - "cvrFileType": "HART", - }, + ] + ), + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", ) assert_ok(rv) @@ -2392,7 +2364,7 @@ class TestCase(TypedDict): { "file": { "cvrFileType": "HART", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -2453,7 +2425,7 @@ def test_hart_cvr_upload_with_duplicate_batch_names( ) class TestCase(TypedDict): - files: List[Tuple[BinaryIO, str]] + files: List[Tuple[io.BytesIO, str]] expected_processing_status: ProcessingStatus expected_processing_error: Optional[str] @@ -2540,12 +2512,13 @@ class TestCase(TypedDict): ] for test_case in test_cases: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": test_case["files"], - "cvrFileType": "HART", - }, + rv = setup_cvrs_upload( + client, + zip_cvrs(test_case["files"]), + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", ) assert_ok(rv) @@ -2557,7 +2530,7 @@ class TestCase(TypedDict): { "file": { "cvrFileType": "HART", - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, }, "processing": { @@ -2623,12 +2596,14 @@ def test_hart_cvr_upload_no_batch_match( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) for invalid_cvr, expected_error in invalid_cvrs: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": [(zip_hart_cvrs(invalid_cvr), "cvrs.zip")], - "cvrFileType": "HART", - }, + rv = setup_cvrs_upload( + client, + zip_hart_cvrs(invalid_cvr), + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", + "cvrs.zip", ) assert_ok(rv) @@ -2639,7 +2614,7 @@ def test_hart_cvr_upload_no_batch_match( json.loads(rv.data), { "file": { - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "HART", }, @@ -2706,12 +2681,13 @@ def test_hart_cvr_upload_no_tabulator_plus_batch_match( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) for cvr_upload, expected_error in cvr_uploads: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": cvr_upload, - "cvrFileType": "HART", - }, + rv = setup_cvrs_upload( + client, + zip_cvrs(cvr_upload), + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", ) assert_ok(rv) @@ -2722,7 +2698,7 @@ def test_hart_cvr_upload_no_tabulator_plus_batch_match( json.loads(rv.data), { "file": { - "name": "cvr-files.zip", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "HART", }, @@ -2749,80 +2725,122 @@ def test_hart_cvr_upload_basic_input_validation( ) class TestCase(TypedDict): - cvrs: List + cvrs: io.BytesIO + file_type: str expected_status_code: int expected_response: Any test_cases: List[TestCase] = [ { - "cvrs": [(zip_hart_cvrs(HART_CVRS), "cvrs.csv")], + "cvrs": zip_hart_cvrs(HART_CVRS), + "file_type": "text/csv", "expected_status_code": 400, "expected_response": { "errors": [ { "errorType": "Bad Request", - "message": "Please submit at least one ZIP file.", + "message": "Please submit a valid ZIP file.", } ] }, }, { - "cvrs": [ - (zip_hart_cvrs(HART_CVRS), "cvrs.csv"), - ( - string_to_bytes_io(HART_SCANNED_BALLOT_INFORMATION), - "scanned-ballot-information.csv", - ), - ], - "expected_status_code": 400, - "expected_response": { - "errors": [ - { - "errorType": "Bad Request", - "message": "Please submit at least one ZIP file.", - } - ] - }, + "cvrs": zip_hart_cvrs(HART_CVRS), + "file_type": "application/x-zip-compressed", # Verify that the Windows ZIP mimetype works + "expected_status_code": 200, + "expected_response": {"status": "ok"}, }, + ] + + for test_case in test_cases: + rv = setup_cvrs_upload( + client, + test_case["cvrs"], + election_id, + jurisdiction_ids[0], + "HART", + test_case["file_type"], + ) + assert rv.status_code == test_case["expected_status_code"] + assert json.loads(rv.data) == test_case["expected_response"] + + +def test_hart_cvr_upload_processing_validation( + client: FlaskClient, + election_id: str, + jurisdiction_ids: List[str], + hart_manifests, # pylint: disable=unused-argument +): + set_logged_in_user( + client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) + ) + + class TestCase(TypedDict): + cvrs: io.BytesIO + expected_status_code: int + expected_response: Any + + test_cases: List[TestCase] = [ { - "cvrs": [ - (zip_hart_cvrs(HART_CVRS), "cvrs.zip"), - ( - string_to_bytes_io(HART_SCANNED_BALLOT_INFORMATION), - "scanned-ballot-information.jpg", - ), - ], - "expected_status_code": 400, - "expected_response": { - "errors": [ - { - "errorType": "Bad Request", - "message": "Please submit only ZIP files and CSVs.", - } + "cvrs": zip_cvrs( + [ + (zip_hart_cvrs(HART_CVRS), "cvrs.csv"), + ( + string_to_bytes_io(HART_SCANNED_BALLOT_INFORMATION), + "scanned-ballot-information.csv", + ), ] - }, + ), + "expected_status_code": 400, + "expected_response": "Expected first line of scanned ballot information CSV to contain '#FormatVersion'.", }, { - "cvrs": [ - ( - zip_hart_cvrs(HART_CVRS), - "cvrs.zip", - # Verify that the Windows ZIP mimetype works - "application/x-zip-compressed", - ), - ], - "expected_status_code": 200, - "expected_response": {"status": "ok"}, + "cvrs": zip_cvrs( + [ + (zip_hart_cvrs(HART_CVRS), "cvrs.zip"), + ( + string_to_bytes_io(HART_SCANNED_BALLOT_INFORMATION), + "scanned-ballot-information.jpg", + ), + ] + ), + "expected_status_code": 400, + "expected_response": "Unsupported file type. Expected either a ZIP file or a CSV file, but found scanned-ballot-information.jpg.", }, ] for test_case in test_cases: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={"cvrFileType": "HART", "cvrs": test_case["cvrs"]}, + rv = setup_cvrs_upload( + client, + test_case["cvrs"], + election_id, + jurisdiction_ids[0], + "HART", + "application/zip", + ) + # these test cases will fail when being processed not uploaded + assert_ok(rv) + rv = client.get( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs" + ) + compare_json( + json.loads(rv.data), + { + "file": { + "name": asserts_startswith("cvrs"), + "uploadedAt": assert_is_date, + "cvrFileType": "HART", + }, + "processing": { + "status": ProcessingStatus.ERRORED, + "startedAt": assert_is_date, + "completedAt": assert_is_date, + "error": test_case["expected_response"], + "workProgress": 0, + "workTotal": assert_is_int, + }, + }, ) - assert rv.status_code == test_case["expected_status_code"] - assert json.loads(rv.data) == test_case["expected_response"] def test_cvrs_unexpected_error( @@ -2847,15 +2865,12 @@ def test_cvrs_unexpected_error( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(cvrs.encode()), - "cvrs.csv", - ), - "cvrFileType": "DOMINION", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(cvrs.encode()), + election_id, + jurisdiction_ids[0], + "DOMINION", ) assert_ok(rv) @@ -2866,7 +2881,7 @@ def test_cvrs_unexpected_error( json.loads(rv.data), { "file": { - "name": "cvrs.csv", + "name": asserts_startswith("cvrs"), "uploadedAt": assert_is_date, "cvrFileType": "DOMINION", }, @@ -2899,15 +2914,12 @@ def test_cvr_invalid_file_type( set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/cvrs", - data={ - "cvrs": ( - io.BytesIO(TEST_CVRS.encode()), - "cvrs.csv", - ), - "cvrFileType": "WRONG", - }, + rv = setup_cvrs_upload( + client, + io.BytesIO(TEST_CVRS.encode()), + election_id, + jurisdiction_ids[0], + "WRONG", ) assert rv.status_code == 400 assert json.loads(rv.data) == { diff --git a/server/tests/ballot_comparison/test_standardized_contests.py b/server/tests/ballot_comparison/test_standardized_contests.py index ca7e078e9..637928071 100644 --- a/server/tests/ballot_comparison/test_standardized_contests.py +++ b/server/tests/ballot_comparison/test_standardized_contests.py @@ -15,14 +15,10 @@ def test_upload_standardized_contests( 'Contest 2,"J1, J3"\n' "Contest 3,J2 \n" ) - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(standardized_contests_file.encode()), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(standardized_contests_file.encode()), + election_id, ) assert_ok(rv) @@ -31,7 +27,7 @@ def test_upload_standardized_contests( json.loads(rv.data), { "file": { - "name": "standardized-contests.csv", + "name": asserts_startswith("standardizedContests"), "uploadedAt": assert_is_date, }, "processing": { @@ -54,9 +50,8 @@ def test_upload_standardized_contests( ] rv = client.get(f"/api/election/{election_id}/standardized-contests/file/csv") - assert ( - rv.headers["Content-Disposition"] - == 'attachment; filename="standardized-contests.csv"' + assert rv.headers["Content-Disposition"].startswith( + 'attachment; filename="standardizedContests' ) assert rv.data.decode("utf-8") == standardized_contests_file @@ -71,19 +66,15 @@ def test_download_standardized_contests_file_before_upload( def test_standardized_contests_replace( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO( - b"Contest Name,Jurisdictions\n" - b"Contest 1,all\n" - b'Contest 2,"J1, J3"\n' - b"Contest 3,J2 \n" - ), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO( + b"Contest Name,Jurisdictions\n" + b"Contest 1,all\n" + b'Contest 2,"J1, J3"\n' + b"Contest 3,J2 \n" + ), + election_id, ) assert_ok(rv) @@ -91,14 +82,10 @@ def test_standardized_contests_replace( file_id = election.standardized_contests_file_id standardized_contests = election.standardized_contests - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 4,all\n"), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 4,all\n"), + election_id, ) assert_ok(rv) @@ -120,17 +107,13 @@ def test_standardized_contests_bad_jurisdiction( election_id: str, jurisdiction_ids: List[str], # pylint: disable=unused-argument ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO( - b"Contest Name,Jurisdictions\n" - b'Contest 1,"J1,not a real jurisdiction,another bad one"\n"' - ), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO( + b"Contest Name,Jurisdictions\n" + b'Contest 1,"J1,not a real jurisdiction,another bad one"\n"' + ), + election_id, ) assert_ok(rv) @@ -139,7 +122,7 @@ def test_standardized_contests_bad_jurisdiction( json.loads(rv.data), { "file": { - "name": "standardized-contests.csv", + "name": asserts_startswith("standardizedContests"), "uploadedAt": assert_is_date, }, "processing": { @@ -160,14 +143,10 @@ def test_standardized_contests_no_jurisdictions( election_id: str, jurisdiction_ids: List[str], # pylint: disable=unused-argument ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,"), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,"), + election_id, ) assert_ok(rv) @@ -176,7 +155,7 @@ def test_standardized_contests_no_jurisdictions( json.loads(rv.data), { "file": { - "name": "standardized-contests.csv", + "name": asserts_startswith("standardizedContests"), "uploadedAt": assert_is_date, }, "processing": { @@ -197,8 +176,8 @@ def test_standardized_contests_missing_file( election_id: str, jurisdiction_ids: List[str], # pylint: disable=unused-argument ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", + rv = client.post( + f"/api/election/{election_id}/standardized-contests/file/upload-complete", data={}, ) assert rv.status_code == 400 @@ -206,7 +185,7 @@ def test_standardized_contests_missing_file( "errors": [ { "errorType": "Bad Request", - "message": "Missing required file parameter 'standardized-contests'", + "message": "Missing required JSON parameter: storagePathKey", } ] } @@ -217,13 +196,12 @@ def test_standardized_contests_bad_csv( election_id: str, jurisdiction_ids: List[str], # pylint: disable=unused-argument ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", + rv = client.post( + f"/api/election/{election_id}/standardized-contests/file/upload-complete", data={ - "standardized-contests": ( - io.BytesIO(b"not a csv"), - "standardized-contests.txt", - ) + "storagePathKey": "test_dir/random.txt", + "fileName": "random.txt", + "fileType": "text/plain", }, ) assert rv.status_code == 400 @@ -249,14 +227,10 @@ def test_standardized_contests_wrong_audit_type( db_session.add(election) db_session.commit() - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,all\n"), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,all\n"), + election_id, ) assert rv.status_code == 409 assert json.loads(rv.data) == { @@ -272,14 +246,10 @@ def test_standardized_contests_wrong_audit_type( def test_standardized_contests_before_jurisdictions( client: FlaskClient, election_id: str ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,all\n"), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(b"Contest Name,Jurisdictions\n" b"Contest 1,all\n"), + election_id, ) assert rv.status_code == 409 assert json.loads(rv.data) == { @@ -295,19 +265,15 @@ def test_standardized_contests_before_jurisdictions( def test_standardized_contests_newlines( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO( - b"Contest Name,Jurisdictions\n" - b'"Contest\r\n1",all\n' - b'Contest 2,"J1, J3"\n' - b"Contest 3,J2\n" - ), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO( + b"Contest Name,Jurisdictions\n" + b'"Contest\r\n1",all\n' + b'Contest 2,"J1, J3"\n' + b"Contest 3,J2\n" + ), + election_id, ) assert_ok(rv) @@ -325,19 +291,15 @@ def test_standardized_contests_newlines( def test_standardized_contests_dominion_vote_for( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ): - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO( - b"Contest Name,Jurisdictions\n" - b'"Contest\r\n1 (Vote For=2)",all\n' - b'Contest 2,"J1, J3"\n' - b"Contest 3,J2\n" - ), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO( + b"Contest Name,Jurisdictions\n" + b'"Contest\r\n1 (Vote For=2)",all\n' + b'Contest 2,"J1, J3"\n' + b"Contest 3,J2\n" + ), + election_id, ) assert_ok(rv) @@ -361,14 +323,10 @@ def test_standardized_contests_change_jurisdictions_file( 'Contest 2,"J1, J3"\n' "Contest 3,all \n" ) - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(standardized_contests_file.encode()), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(standardized_contests_file.encode()), + election_id, ) assert_ok(rv) @@ -388,20 +346,16 @@ def test_standardized_contests_change_jurisdictions_file( assert_ok(rv) # Remove a jurisdiction that isn't referenced directly in standardized contests - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - ( - "Jurisdiction,Admin Email\n" - f"J3,j3-{election_id}@example.com\n" - f"J1,{default_ja_email(election_id)}\n" - ).encode() - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + ( + "Jurisdiction,Admin Email\n" + f"J3,j3-{election_id}@example.com\n" + f"J1,{default_ja_email(election_id)}\n" + ).encode() + ), + election_id, ) assert_ok(rv) @@ -423,19 +377,14 @@ def test_standardized_contests_change_jurisdictions_file( ] # Now remove a jurisdiction that is referenced directly in standardized contests - rv = client.put( - f"/api/election/{election_id}/jurisdiction/file", - data={ - "jurisdictions": ( - io.BytesIO( - ( - "Jurisdiction,Admin Email\n" - f"J1,{default_ja_email(election_id)}\n" - ).encode() - ), - "jurisdictions.csv", - ) - }, + rv = setup_jurisdictions_upload( + client, + io.BytesIO( + ( + "Jurisdiction,Admin Email\n" f"J1,{default_ja_email(election_id)}\n" + ).encode() + ), + election_id, ) assert_ok(rv) @@ -449,7 +398,7 @@ def test_standardized_contests_change_jurisdictions_file( json.loads(rv.data), { "file": { - "name": "standardized-contests.csv", + "name": asserts_startswith("standardizedContests"), "uploadedAt": assert_is_date, }, "processing": { @@ -468,14 +417,10 @@ def test_standardized_contests_parse_all( standardized_contests_file = ( "Contest Name,Jurisdictions\n" + "Contest 1,All\n" + "Contest 2, aLL \n" ) - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(standardized_contests_file.encode()), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(standardized_contests_file.encode()), + election_id, ) assert_ok(rv) @@ -504,14 +449,10 @@ def test_reupload_standardized_contests_after_contests_selected( 'Contest 2,"J1, J3"\n' "Contest 3,J2 \n" ) - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(standardized_contests_file.encode()), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(standardized_contests_file.encode()), + election_id, ) assert_ok(rv) @@ -577,14 +518,10 @@ def test_reupload_standardized_contests_after_contests_selected( standardized_contests_file = ( "Contest Name,Jurisdictions\n" + 'Contest 1,"J1,J2"\n' + "Contest 3,J2 \n" ) - rv = client.put( - f"/api/election/{election_id}/standardized-contests/file", - data={ - "standardized-contests": ( - io.BytesIO(standardized_contests_file.encode()), - "standardized-contests.csv", - ) - }, + rv = setup_standardized_contests_upload( + client, + io.BytesIO(standardized_contests_file.encode()), + election_id, ) assert_ok(rv) diff --git a/server/tests/batch_comparison/conftest.py b/server/tests/batch_comparison/conftest.py index af06e6e10..39fe48ee0 100644 --- a/server/tests/batch_comparison/conftest.py +++ b/server/tests/batch_comparison/conftest.py @@ -66,43 +66,37 @@ def manifests(client: FlaskClient, election_id: str, jurisdiction_ids: List[str] set_logged_in_user( client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Batch Name,Number of Ballots\n" - b"Batch 1,500\n" - b"Batch 2,500\n" - b"Batch 3,500\n" - b"Batch 4,500\n" - b"Batch 5,100\n" - b"Batch 6,100\n" - b"Batch 7,100\n" - b"Batch 8,100\n" - b"Batch 9,100\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Batch Name,Number of Ballots\n" + b"Batch 1,500\n" + b"Batch 2,500\n" + b"Batch 3,500\n" + b"Batch 4,500\n" + b"Batch 5,100\n" + b"Batch 6,100\n" + b"Batch 7,100\n" + b"Batch 8,100\n" + b"Batch 9,100\n" + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO( - b"Batch Name,Number of Ballots\n" - b"Batch 1,500\n" - b"Batch 2,500\n" - b"Batch 3,500\n" - b"Batch 4,500\n" - b"Batch 5,250\n" - b"Batch 6,250\n" - ), - "manifest.csv", - ) - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO( + b"Batch Name,Number of Ballots\n" + b"Batch 1,500\n" + b"Batch 2,500\n" + b"Batch 3,500\n" + b"Batch 4,500\n" + b"Batch 5,250\n" + b"Batch 6,250\n" + ), + election_id, + jurisdiction_ids[1], ) assert_ok(rv) @@ -130,15 +124,13 @@ def batch_tallies( b"Batch 8,100,50,50\n" b"Batch 9,100,50,50\n" ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies_file), - "batchTallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, + io.BytesIO(batch_tallies_file), + election_id, + jurisdiction_ids[0], ) + assert_ok(rv) batch_tallies_file = ( b"Batch Name,candidate 1,candidate 2,candidate 3\n" b"Batch 1,500,250,250\n" @@ -148,14 +140,11 @@ def batch_tallies( b"Batch 5,100,50,50\n" b"Batch 6,100,50,50\n" ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies_file), - "batchTallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, + io.BytesIO(batch_tallies_file), + election_id, + jurisdiction_ids[1], ) assert_ok(rv) diff --git a/server/tests/batch_comparison/test_batch_comparison.py b/server/tests/batch_comparison/test_batch_comparison.py index 6d319d464..6b5658ef6 100644 --- a/server/tests/batch_comparison/test_batch_comparison.py +++ b/server/tests/batch_comparison/test_batch_comparison.py @@ -103,14 +103,8 @@ def test_batch_comparison_too_many_votes( b"Batch 5,100,50,50\n" b"Batch 6,100,50,50\n" ) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies_file), - "batchTallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, io.BytesIO(batch_tallies_file), election_id, jurisdiction_ids[1] ) assert_ok(rv) diff --git a/server/tests/batch_comparison/test_batch_inventory.py b/server/tests/batch_comparison/test_batch_inventory.py index d0eb94d22..dca967000 100644 --- a/server/tests/batch_comparison/test_batch_inventory.py +++ b/server/tests/batch_comparison/test_batch_inventory.py @@ -179,14 +179,11 @@ def test_batch_inventory_happy_path( compare_json(json.loads(rv.data), {"systemType": CvrFileType.DOMINION}) # Upload CVR file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(TEST_CVR.encode()), - "cvrs.csv", - ), - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(TEST_CVR.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -196,7 +193,10 @@ def test_batch_inventory_happy_path( compare_json( json.loads(rv.data), { - "file": {"name": "cvrs.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchInventoryCvr"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -207,14 +207,11 @@ def test_batch_inventory_happy_path( ) # Upload tabulator status file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status", - data={ - "tabulatorStatus": ( - io.BytesIO(TEST_TABULATOR_STATUS.encode()), - "tabulator-status.xml", - ), - }, + rv = setup_batch_inventory_tabulator_status_upload( + client, + io.BytesIO(TEST_TABULATOR_STATUS.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -224,7 +221,10 @@ def test_batch_inventory_happy_path( compare_json( json.loads(rv.data), { - "file": {"name": "tabulator-status.xml", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("tabulator-status"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -271,14 +271,11 @@ def test_batch_inventory_happy_path( snapshot.assert_match(batch_tallies) # Upload manifest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(ballot_manifest.encode()), - "ballot-manifest.csv", - ), - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(ballot_manifest.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -288,7 +285,10 @@ def test_batch_inventory_happy_path( compare_json( json.loads(rv.data), { - "file": {"name": "ballot-manifest.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -299,14 +299,11 @@ def test_batch_inventory_happy_path( ) # Upload batch tallies - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies.encode()), - "batch-tallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, + io.BytesIO(batch_tallies.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -316,7 +313,10 @@ def test_batch_inventory_happy_path( compare_json( json.loads(rv.data), { - "file": {"name": "batch-tallies.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchTallies"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -390,14 +390,11 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( compare_json(json.loads(rv.data), {"systemType": CvrFileType.DOMINION}) # Upload CVR file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(TEST_CVRS_WITH_LEADING_EQUAL_SIGNS.encode()), - "cvrs.csv", - ), - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(TEST_CVRS_WITH_LEADING_EQUAL_SIGNS.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -407,7 +404,10 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( compare_json( json.loads(rv.data), { - "file": {"name": "cvrs.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchInventoryCvr"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -418,14 +418,11 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( ) # Upload tabulator status file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status", - data={ - "tabulatorStatus": ( - io.BytesIO(TEST_TABULATOR_STATUS.encode()), - "tabulator-status.xml", - ), - }, + rv = setup_batch_inventory_tabulator_status_upload( + client, + io.BytesIO(TEST_TABULATOR_STATUS.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -435,7 +432,10 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( compare_json( json.loads(rv.data), { - "file": {"name": "tabulator-status.xml", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("tabulator-status"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -482,14 +482,11 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( snapshot.assert_match(batch_tallies) # Upload manifest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(ballot_manifest.encode()), - "ballot-manifest.csv", - ), - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(ballot_manifest.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -499,7 +496,10 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( compare_json( json.loads(rv.data), { - "file": {"name": "ballot-manifest.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -510,14 +510,11 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( ) # Upload batch tallies - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies.encode()), - "batch-tallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, + io.BytesIO(batch_tallies.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -527,7 +524,10 @@ def test_batch_inventory_happy_path_cvrs_with_leading_equal_signs( compare_json( json.loads(rv.data), { - "file": {"name": "batch-tallies.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchTallies"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -601,14 +601,11 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( compare_json(json.loads(rv.data), {"systemType": CvrFileType.DOMINION}) # Upload CVR file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(TEST_CVR.encode()), - "cvrs.csv", - ), - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(TEST_CVR.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -618,7 +615,10 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( compare_json( json.loads(rv.data), { - "file": {"name": "cvrs.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchInventoryCvr"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -629,14 +629,11 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( ) # Upload tabulator status file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status", - data={ - "tabulatorStatus": ( - io.BytesIO(TEST_TABULATOR_STATUS.encode()), - "tabulator-status.xml", - ), - }, + rv = setup_batch_inventory_tabulator_status_upload( + client, + io.BytesIO(TEST_TABULATOR_STATUS.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -646,7 +643,10 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( compare_json( json.loads(rv.data), { - "file": {"name": "tabulator-status.xml", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("tabulator-status"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -693,14 +693,11 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( snapshot.assert_match(batch_tallies) # Upload manifest - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest", - data={ - "manifest": ( - io.BytesIO(ballot_manifest.encode()), - "ballot-manifest.csv", - ), - }, + rv = setup_ballot_manifest_upload( + client, + io.BytesIO(ballot_manifest.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -710,7 +707,10 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( compare_json( json.loads(rv.data), { - "file": {"name": "ballot-manifest.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -721,14 +721,11 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( ) # Upload batch tallies - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-tallies", - data={ - "batchTallies": ( - io.BytesIO(batch_tallies.encode()), - "batch-tallies.csv", - ) - }, + rv = setup_batch_tallies_upload( + client, + io.BytesIO(batch_tallies.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -738,7 +735,10 @@ def test_batch_inventory_happy_path_multi_contest_batch_comparison( compare_json( json.loads(rv.data), { - "file": {"name": "batch-tallies.csv", "uploadedAt": assert_is_date}, + "file": { + "name": asserts_startswith("batchTallies"), + "uploadedAt": assert_is_date, + }, "processing": { "status": ProcessingStatus.PROCESSED, "startedAt": assert_is_date, @@ -826,14 +826,11 @@ def test_batch_inventory_invalid_file_uploads( ), ] for invalid_cvr, expected_error in invalid_cvrs: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(invalid_cvr.encode()), - "cvrs.csv", - ) - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(invalid_cvr.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -850,14 +847,11 @@ def test_batch_inventory_invalid_file_uploads( assert_ok(rv) # Upload valid CVR file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(TEST_CVR.encode()), - "cvrs.csv", - ), - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(TEST_CVR.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -868,18 +862,15 @@ def test_batch_inventory_invalid_file_uploads( assert cvr["processing"]["status"] == ProcessingStatus.PROCESSED # Upload tabulator status file with missing tabulator - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status", - data={ - "tabulatorStatus": ( - io.BytesIO( - TEST_TABULATOR_STATUS.replace( - '', "" - ).encode() - ), - "tabulator-status.xml", - ), - }, + rv = setup_batch_inventory_tabulator_status_upload( + client, + io.BytesIO( + TEST_TABULATOR_STATUS.replace( + '', "" + ).encode() + ), + election_id, + jurisdiction_ids[0], ) rv = client.get( f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status" @@ -897,25 +888,22 @@ def test_batch_inventory_invalid_file_uploads( ) assert_ok(rv) - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO( - TEST_CVR.replace( - """1,TABULATOR1,BATCH1,1,1-1-1,Election Day,12345,COUNTY,0,1,1,1,0 + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO( + TEST_CVR.replace( + """1,TABULATOR1,BATCH1,1,1-1-1,Election Day,12345,COUNTY,0,1,1,1,0 2,TABULATOR1,BATCH1,2,1-1-2,Election Day,12345,COUNTY,1,0,1,0,1 3,TABULATOR1,BATCH1,3,1-1-3,Election Day,12345,COUNTY,0,1,1,1,0 4,TABULATOR1,BATCH2,1,1-2-1,Election Day,12345,COUNTY,1,0,1,0,1 5,TABULATOR1,BATCH2,2,1-2-2,Election Day,12345,COUNTY,0,1,1,1,0 6,TABULATOR1,BATCH2,3,1-2-3,Election Day,12345,COUNTY,1,0,1,0,1 """, - "", - ).encode() - ), - "cvrs.csv", - ), - }, + "", + ).encode() + ), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -967,14 +955,11 @@ def test_batch_inventory_missing_data_multi_contest_batch_comparison( ), ] for invalid_cvr, expected_error in invalid_cvrs: - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(invalid_cvr.encode()), - "cvrs.csv", - ) - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(invalid_cvr.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) @@ -1010,24 +995,19 @@ def test_batch_inventory_wrong_tabulator_status_file( assert_ok(rv) # Upload CVR file - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/cvr", - data={ - "cvr": ( - io.BytesIO(TEST_CVR.encode()), - "cvrs.csv", - ), - }, + rv = setup_batch_inventory_cvr_upload( + client, + io.BytesIO(TEST_CVR.encode()), + election_id, + jurisdiction_ids[0], ) assert_ok(rv) # Upload tabulator status "To Excel" version - rv = client.put( - f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/batch-inventory/tabulator-status", - data={ - "tabulatorStatus": ( - io.BytesIO( - b""" + rv = setup_batch_inventory_tabulator_status_upload( + client, + io.BytesIO( + b"""