diff --git a/server/api/ballot_manifest.py b/server/api/ballot_manifest.py index c0a4dc580..4067b794e 100644 --- a/server/api/ballot_manifest.py +++ b/server/api/ballot_manifest.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import BinaryIO, Optional import uuid import logging from datetime import datetime @@ -12,6 +12,7 @@ from ..models import * # pylint: disable=wildcard-import from ..auth import restrict_access, UserType, get_loggedin_user, get_support_user from ..worker.tasks import ( + UserError, background_task, create_background_task, ) @@ -57,6 +58,8 @@ class CountingGroup(str, enum.Enum): NUMBER_OF_BALLOTS = "Number of Ballots" CVR = "CVR" +BATCH_INVENTORY_WORKSHEET_UPLOADED_ERROR = 'You have uploaded a Batch Inventory Worksheet. Please upload a ballot manifest file exported from Step 4: "Download Audit Files".' + def all_manifests_uploaded(contest: Contest): return all( @@ -143,6 +146,7 @@ def process() -> None: ) manifest_file = retrieve_file(jurisdiction.manifest_file.storage_path) + validate_is_not_batch_inventory_worksheet(manifest_file) manifest_csv = parse_csv(manifest_file, columns) counting_group_allowlist = [item.value for item in CountingGroup] @@ -229,6 +233,17 @@ def process() -> None: session.commit() +def is_batch_inventory_worksheet(first_line: bytes) -> bool: + return first_line.decode("utf-8").strip() == "Batch Inventory Worksheet" + + +def validate_is_not_batch_inventory_worksheet(file: BinaryIO): + first_line = file.readline() + file.seek(0) + if is_batch_inventory_worksheet(first_line): + raise UserError(BATCH_INVENTORY_WORKSHEET_UPLOADED_ERROR) + + def save_ballot_manifest_file( storage_path: str, file_name: str, jurisdiction: Jurisdiction ): diff --git a/server/tests/api/test_ballot_manifest.py b/server/tests/api/test_ballot_manifest.py index 94c58ebd4..f1b2d7176 100644 --- a/server/tests/api/test_ballot_manifest.py +++ b/server/tests/api/test_ballot_manifest.py @@ -155,6 +155,41 @@ def test_ballot_manifest_upload_missing_file_path( } +def test_ballot_manifest_upload_batch_inventory_worksheet( + client: FlaskClient, election_id: str, jurisdiction_ids: List[str] +): + set_logged_in_user( + client, UserType.JURISDICTION_ADMIN, default_ja_email(election_id) + ) + + rv = upload_ballot_manifest( + client, + io.BytesIO(b"Batch Inventory Worksheet \r\n"), + election_id, + jurisdiction_ids[0], + ) + assert_ok(rv) + + rv = client.get( + f"/api/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest" + ) + compare_json( + json.loads(rv.data), + { + "file": { + "name": asserts_startswith("manifest"), + "uploadedAt": assert_is_date, + }, + "processing": { + "status": ProcessingStatus.ERRORED, + "startedAt": assert_is_date, + "completedAt": assert_is_date, + "error": 'You have uploaded a Batch Inventory Worksheet. Please upload a ballot manifest file exported from Step 4: "Download Audit Files".', + }, + }, + ) + + def test_ballot_manifest_upload_bad_csv( client: FlaskClient, election_id: str, jurisdiction_ids: List[str] ):