Пример #1
0
def manifests(client: FlaskClient, election_id: str,
              jurisdiction_ids: List[str]):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(b"Batch Name,Number of Ballots\n"
                           b"1,23\n"
                           b"2,101\n"
                           b"3,122\n"
                           b"4,400"),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[1]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(b"Batch Name,Number of Ballots\n"
                           b"1,20\n"
                           b"2,10\n"
                           b"3,220\n"
                           b"4,40"),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)
    bgcompute_update_ballot_manifest_file()
Пример #2
0
def test_ballot_manifest_upload_bad_csv(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={"manifest": (io.BytesIO(b"not a CSV file"), "random.txt")},
    )
    assert_ok(rv)

    bgcompute_update_ballot_manifest_file()

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    compare_json(
        json.loads(rv.data),
        {
            "file": {"name": "random.txt", "uploadedAt": assert_is_date,},
            "processing": {
                "status": ProcessingStatus.ERRORED,
                "startedAt": assert_is_date,
                "completedAt": assert_is_date,
                "error": "Please submit a valid CSV file with columns separated by commas.",
            },
        },
    )
Пример #3
0
def test_ballot_manifest_upload_invalid_num_ballots(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"1,not a number,Bin 2,Tabulator 1\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    bgcompute_update_ballot_manifest_file()

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    compare_json(
        json.loads(rv.data),
        {
            "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,},
            "processing": {
                "status": ProcessingStatus.ERRORED,
                "startedAt": assert_is_date,
                "completedAt": assert_is_date,
                "error": "Expected a number in column Number of Ballots, row 1. Got: not a number.",
            },
        },
    )
Пример #4
0
def test_ballot_manifest_replace(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"1,23,Bin 2,Tabulator 1\n"
                    b"12,100,Bin 3,Tabulator 2\n"
                    b"6,0,,\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    num_files = File.query.count()

    bgcompute_update_ballot_manifest_file()

    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"1,23,Bin 2,Tabulator 1\n"
                    b"12,6,Bin 6,Tabulator 2\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    # The old file should have been deleted
    assert File.query.count() == num_files

    bgcompute_update_ballot_manifest_file()

    jurisdiction = Jurisdiction.query.get(jurisdiction_ids[0])
    assert jurisdiction.manifest_num_batches == 2
    assert jurisdiction.manifest_num_ballots == 29
    assert len(jurisdiction.batches) == 2
    assert jurisdiction.batches[0].name == "1"
    assert jurisdiction.batches[0].num_ballots == 23
    assert jurisdiction.batches[0].storage_location == "Bin 2"
    assert jurisdiction.batches[0].tabulator == "Tabulator 1"
    assert jurisdiction.batches[1].name == "12"
    assert jurisdiction.batches[1].num_ballots == 6
    assert jurisdiction.batches[1].storage_location == "Bin 6"
    assert jurisdiction.batches[1].tabulator == "Tabulator 2"
Пример #5
0
def test_ballot_manifest_upload_duplicate_batch_name(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"12,23,Bin 2,Tabulator 1\n"
                    b"12,100,Bin 3,Tabulator 2\n"
                    b"6,0,,\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    bgcompute_update_ballot_manifest_file()

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    compare_json(
        json.loads(rv.data),
        {
            "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,},
            "processing": {
                "status": ProcessingStatus.ERRORED,
                "startedAt": assert_is_date,
                "completedAt": assert_is_date,
                "error": "Values in column Batch Name must be unique. Found duplicate value: 12.",
            },
        },
    )
Пример #6
0
def test_ballot_manifest_clear(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"1,23,Bin 2,Tabulator 1\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    num_files = File.query.count()

    bgcompute_update_ballot_manifest_file()

    rv = client.delete(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
    )
    assert_ok(rv)

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    assert json.loads(rv.data) == {"file": None, "processing": None}

    jurisdiction = Jurisdiction.query.get(jurisdiction_ids[0])
    assert jurisdiction.manifest_num_batches is None
    assert jurisdiction.manifest_num_ballots is None
    assert jurisdiction.batches == []
    assert File.query.count() == num_files - 1
Пример #7
0
def test_ballot_manifest_upload_missing_field(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    for missing_field in ["Batch Name", "Number of Ballots"]:
        headers = ["Batch Name", "Number of Ballots", "Storage Location", "Tabulator"]
        header_row = ",".join(h for h in headers if h != missing_field)

        set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
        rv = client.put(
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
            data={
                "manifest": (
                    io.BytesIO(header_row.encode() + b"\n1,2,3"),
                    "manifest.csv",
                )
            },
        )
        assert_ok(rv)

        bgcompute_update_ballot_manifest_file()

        rv = client.get(
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
        )
        compare_json(
            json.loads(rv.data),
            {
                "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,},
                "processing": {
                    "status": ProcessingStatus.ERRORED,
                    "startedAt": assert_is_date,
                    "completedAt": assert_is_date,
                    "error": f"Missing required column: {missing_field}.",
                },
            },
        )
Пример #8
0
def test_jurisdictions_list_with_manifest(client: FlaskClient,
                                          election_id: str,
                                          jurisdiction_ids: List[str]):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    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"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={"manifest": (
            io.BytesIO(manifest),
            "manifest.csv",
        )},
    )
    assert_ok(rv)
    assert bgcompute_update_ballot_manifest_file() == 1

    rv = client.get(f"/election/{election_id}/jurisdiction")
    jurisdictions = json.loads(rv.data)
    expected = {
        "jurisdictions": [
            {
                "id": jurisdiction_ids[0],
                "name": "J1",
                "ballotManifest": {
                    "file": {
                        "name": "manifest.csv",
                        "uploadedAt": assert_is_date,
                    },
                    "processing": {
                        "status": "PROCESSED",
                        "startedAt": assert_is_date,
                        "completedAt": assert_is_date,
                        "error": None,
                    },
                    "numBallots": 23 + 101 + 122 + 400,
                    "numBatches": 4,
                },
                "currentRoundStatus": None,
            },
            {
                "id": jurisdiction_ids[1],
                "name": "J2",
                "ballotManifest": {
                    "file": None,
                    "processing": None,
                    "numBallots": None,
                    "numBatches": None,
                },
                "currentRoundStatus": None,
            },
            {
                "id": jurisdiction_ids[2],
                "name": "J3",
                "ballotManifest": {
                    "file": None,
                    "processing": None,
                    "numBallots": None,
                    "numBatches": None,
                },
                "currentRoundStatus": None,
            },
        ]
    }
    compare_json(jurisdictions, expected)

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest/csv"
    )
    assert rv.headers[
        "Content-Disposition"] == 'attachment; filename="manifest.csv"'
    assert rv.data == manifest
Пример #9
0
def test_duplicate_batch_name(client, election_id, jurisdiction_ids):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(b"Batch Name,Number of Ballots\n"
                           b"1,23\n"
                           b"1,101\n"),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    bgcompute_update_ballot_manifest_file()

    rv = client.get(f"/election/{election_id}/jurisdiction")
    jurisdictions = json.loads(rv.data)
    expected = {
        "jurisdictions": [
            {
                "id": jurisdiction_ids[0],
                "name": "J1",
                "ballotManifest": {
                    "file": {
                        "name": "manifest.csv",
                        "uploadedAt": assert_is_date,
                    },
                    "processing": {
                        "status":
                        "ERRORED",
                        "startedAt":
                        assert_is_date,
                        "completedAt":
                        assert_is_date,
                        "error":
                        "Values in column Batch Name must be unique. Found duplicate value: 1.",
                    },
                    "numBallots": None,
                    "numBatches": None,
                },
                "currentRoundStatus": None,
            },
            {
                "id": jurisdiction_ids[1],
                "name": "J2",
                "ballotManifest": {
                    "file": None,
                    "processing": None,
                    "numBallots": None,
                    "numBatches": None,
                },
                "currentRoundStatus": None,
            },
            {
                "id": jurisdiction_ids[2],
                "name": "J3",
                "ballotManifest": {
                    "file": None,
                    "processing": None,
                    "numBallots": None,
                    "numBatches": None,
                },
                "currentRoundStatus": None,
            },
        ]
    }
    compare_json(jurisdictions, expected)
Пример #10
0
def test_ballot_manifest_upload(
    client: FlaskClient, election_id: str, jurisdiction_ids: List[str]
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest",
        data={
            "manifest": (
                io.BytesIO(
                    b"Batch Name,Number of Ballots,Storage Location,Tabulator\n"
                    b"1,23,Bin 2,Tabulator 1\n"
                    b"12,100,Bin 3,Tabulator 2\n"
                    b"6,0,,\n"
                ),
                "manifest.csv",
            )
        },
    )
    assert_ok(rv)

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    compare_json(
        json.loads(rv.data),
        {
            "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,},
            "processing": {
                "status": ProcessingStatus.READY_TO_PROCESS,
                "startedAt": None,
                "completedAt": None,
                "error": None,
            },
        },
    )

    bgcompute_update_ballot_manifest_file()

    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/ballot-manifest"
    )
    compare_json(
        json.loads(rv.data),
        {
            "file": {"name": "manifest.csv", "uploadedAt": assert_is_date,},
            "processing": {
                "status": ProcessingStatus.PROCESSED,
                "startedAt": assert_is_date,
                "completedAt": assert_is_date,
                "error": None,
            },
        },
    )

    jurisdiction = Jurisdiction.query.get(jurisdiction_ids[0])
    assert jurisdiction.manifest_num_batches == 3
    assert jurisdiction.manifest_num_ballots == 123
    assert len(jurisdiction.batches) == 3
    assert jurisdiction.batches[0].name == "1"
    assert jurisdiction.batches[0].num_ballots == 23
    assert jurisdiction.batches[0].storage_location == "Bin 2"
    assert jurisdiction.batches[0].tabulator == "Tabulator 1"
    assert jurisdiction.batches[1].name == "12"
    assert jurisdiction.batches[1].num_ballots == 100
    assert jurisdiction.batches[1].storage_location == "Bin 3"
    assert jurisdiction.batches[1].tabulator == "Tabulator 2"
    assert jurisdiction.batches[2].name == "6"
    assert jurisdiction.batches[2].num_ballots == 0
    assert jurisdiction.batches[2].storage_location is None
    assert jurisdiction.batches[2].tabulator is None
Пример #11
0
def test_multi_winner_election(client, election_id):
    contest_id = str(uuid.uuid4())
    candidate_id_1 = str(uuid.uuid4())
    candidate_id_2 = str(uuid.uuid4())
    candidate_id_3 = str(uuid.uuid4())

    rv = post_json(
        client,
        f"/election/{election_id}/audit/basic",
        {
            "name": "Small Multi-winner Test 2019",
            "riskLimit": 10,
            "randomSeed": "a1234567890987654321b",
            "online": False,
            "contests": [
                {
                    "id": contest_id,
                    "name": "Contest 1",
                    "isTargeted": True,
                    "choices": [
                        {"id": candidate_id_1, "name": "Candidate 1", "numVotes": 1000},
                        {"id": candidate_id_2, "name": "Candidate 2", "numVotes": 792},
                        {"id": candidate_id_3, "name": "Candidate 3", "numVotes": 331},
                    ],
                    "totalBallotsCast": 2123,
                    "numWinners": 2,
                    "votesAllowed": 1,
                }
            ],
        },
    )

    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/audit/status")
    status = json.loads(rv.data)

    assert status["name"] == "Small Multi-winner Test 2019"

    jurisdiction_id = str(uuid.uuid4())
    audit_board_id_1 = str(uuid.uuid4())
    audit_board_id_2 = str(uuid.uuid4())

    rv = post_json(
        client,
        f"/election/{election_id}/audit/jurisdictions",
        {
            "jurisdictions": [
                {
                    "id": jurisdiction_id,
                    "name": "County 1",
                    "contests": [contest_id],
                    "auditBoards": [
                        {
                            "id": audit_board_id_1,
                            "name": "Audit Board #1",
                            "members": [],
                        },
                        {
                            "id": audit_board_id_2,
                            "name": "Audit Board #2",
                            "members": [],
                        },
                    ],
                }
            ]
        },
    )

    assert_ok(rv)

    rv = client.post(f"/election/{election_id}/audit/freeze")
    assert_ok(rv)
    bgcompute.bgcompute()

    rv = client.get(f"/election/{election_id}/audit/status")
    status = json.loads(rv.data)

    assert len(status["jurisdictions"]) == 1
    jurisdiction = status["jurisdictions"][0]
    assert jurisdiction["name"] == "County 1"
    assert jurisdiction["auditBoards"][1]["name"] == "Audit Board #2"
    assert jurisdiction["contests"] == [contest_id]

    # choose a sample size
    sample_size_asn = status["rounds"][0]["contests"][0]["sampleSizeOptions"]
    assert len(sample_size_asn) == 1
    sample_size = sample_size_asn[0]["size"]

    # set the sample_size
    rv = post_json(
        client, f"/election/{election_id}/audit/sample-size", {"size": sample_size}
    )

    # upload the manifest
    data = {}
    data["manifest"] = (open(small_manifest_file_path, "rb"), "small-manifest.csv")
    rv = client.put(
        f"/election/{election_id}/jurisdiction/{jurisdiction_id}/manifest",
        data=data,
        content_type="multipart/form-data",
    )

    assert_ok(rv)
    assert bgcompute.bgcompute_update_ballot_manifest_file() == 1

    rv = client.get(f"/election/{election_id}/audit/status")
    status = json.loads(rv.data)
    manifest = status["jurisdictions"][0]["ballotManifest"]

    assert manifest["numBallots"] == 2117
    assert manifest["numBatches"] == 10
    assert manifest["file"]["name"] == "small-manifest.csv"
    assert manifest["file"]["uploadedAt"]

    # get the retrieval list for round 1
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_id}/1/retrieval-list"
    )
    lines = rv.data.decode("utf-8").split("\r\n")
    assert (
        lines[0]
        == "Batch Name,Ballot Number,Storage Location,Tabulator,Ticket Numbers,Already Audited,Audit Board"
    )
    assert "attachment" in rv.headers["Content-Disposition"]

    num_ballots = get_num_ballots_from_retrieval_list(rv)

    # post results for round 1
    num_for_winner = int(num_ballots * 0.61)
    num_for_winner2 = int(num_ballots * 0.3)
    num_for_loser = num_ballots - num_for_winner - num_for_winner2
    rv = post_json(
        client,
        f"/election/{election_id}/jurisdiction/{jurisdiction_id}/1/results",
        {
            "contests": [
                {
                    "id": contest_id,
                    "results": {
                        candidate_id_1: num_for_winner,
                        candidate_id_2: num_for_winner2,
                        candidate_id_3: num_for_loser,
                    },
                }
            ]
        },
    )

    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/audit/status")
    status = json.loads(rv.data)
    round_contest = status["rounds"][0]["contests"][0]
    assert round_contest["id"] == contest_id
    assert round_contest["results"][candidate_id_1] == num_for_winner
    assert round_contest["results"][candidate_id_2] == num_for_winner2
    assert round_contest["results"][candidate_id_3] == num_for_loser
    assert round_contest["endMeasurements"]["isComplete"]
    assert math.floor(round_contest["endMeasurements"]["pvalue"] * 100) <= 9

    rv = client.get(f"/election/{election_id}/report")
    lines = rv.data.decode("utf-8").splitlines()
    assert lines[0] == "######## ELECTION INFO ########"
    assert "attachment" in rv.headers["Content-Disposition"]
Пример #12
0
def setup_whole_audit(client, election_id, name, risk_limit, random_seed, online=False):
    contest_id = str(uuid.uuid4())
    candidate_id_1 = str(uuid.uuid4())
    candidate_id_2 = str(uuid.uuid4())
    jurisdiction_id = str(uuid.uuid4())
    audit_board_id_1 = str(uuid.uuid4())
    audit_board_id_2 = str(uuid.uuid4())

    url_prefix = "/election/{}".format(election_id)

    rv = post_json(
        client,
        "{}/audit/basic".format(url_prefix),
        {
            "name": name,
            "riskLimit": risk_limit,
            "randomSeed": random_seed,
            "online": online,
            "contests": [
                {
                    "id": contest_id,
                    "name": "contest 1",
                    "isTargeted": True,
                    "choices": [
                        {
                            "id": candidate_id_1,
                            "name": "candidate 1",
                            "numVotes": 48121,
                        },
                        {
                            "id": candidate_id_2,
                            "name": "candidate 2",
                            "numVotes": 38026,
                        },
                    ],
                    "totalBallotsCast": 86147,
                    "numWinners": 1,
                    "votesAllowed": 1,
                }
            ],
        },
    )

    assert_ok(rv)

    rv = client.post(f"{url_prefix}/audit/freeze")
    assert_ok(rv)

    # before background compute, should be null sample size options
    rv = client.get("{}/audit/status".format(url_prefix))
    status = json.loads(rv.data)
    assert status["rounds"][0]["contests"][0]["sampleSizeOptions"] is None
    assert status["online"] == online

    # after background compute
    bgcompute.bgcompute()
    rv = client.get("{}/audit/status".format(url_prefix))
    status = json.loads(rv.data)
    assert len(status["rounds"][0]["contests"][0]["sampleSizeOptions"]) == 4

    assert status["randomSeed"] == random_seed
    assert len(status["contests"]) == 1
    assert status["riskLimit"] == risk_limit
    assert status["name"] == name

    assert status["contests"][0]["choices"][0]["id"] == candidate_id_1

    rv = post_json(
        client,
        "{}/audit/jurisdictions".format(url_prefix),
        {
            "jurisdictions": [
                {
                    "id": jurisdiction_id,
                    "name": "adams county",
                    "contests": [contest_id],
                    "auditBoards": [
                        {
                            "id": audit_board_id_1,
                            "name": "audit board #1",
                            "members": [],
                        },
                        {
                            "id": audit_board_id_2,
                            "name": "audit board #2",
                            "members": [],
                        },
                    ],
                }
            ]
        },
    )

    assert_ok(rv)

    rv = client.get("{}/audit/status".format(url_prefix))
    status = json.loads(rv.data)

    assert len(status["jurisdictions"]) == 1
    jurisdiction = status["jurisdictions"][0]
    assert jurisdiction["name"] == "adams county"
    assert jurisdiction["auditBoards"][1]["name"] == "audit board #2"
    assert jurisdiction["contests"] == [contest_id]

    # choose a sample size
    sample_size_90 = [
        option
        for option in status["rounds"][0]["contests"][0]["sampleSizeOptions"]
        if option["prob"] == 0.9
    ]
    assert len(sample_size_90) == 1
    sample_size = sample_size_90[0]["size"]

    # set the sample_size
    rv = post_json(
        client, "{}/audit/sample-size".format(url_prefix), {"size": sample_size}
    )

    assert_ok(rv)

    # upload the manifest
    data = {}
    data["manifest"] = (open(manifest_file_path, "rb"), "manifest.csv")
    rv = client.put(
        "{}/jurisdiction/{}/manifest".format(url_prefix, jurisdiction_id),
        data=data,
        content_type="multipart/form-data",
    )

    assert_ok(rv)
    assert bgcompute.bgcompute_update_ballot_manifest_file() == 1

    rv = client.get("{}/audit/status".format(url_prefix))
    status = json.loads(rv.data)
    manifest = status["jurisdictions"][0]["ballotManifest"]

    assert manifest["numBallots"] == 86147
    assert manifest["numBatches"] == 484
    assert manifest["file"]["name"] == "manifest.csv"
    assert manifest["file"]["uploadedAt"]

    # delete the manifest and make sure that works
    rv = client.delete(
        "{}/jurisdiction/{}/manifest".format(url_prefix, jurisdiction_id)
    )
    assert_ok(rv)

    rv = client.get("{}/audit/status".format(url_prefix))
    status = json.loads(rv.data)
    manifest = status["jurisdictions"][0]["ballotManifest"]

    assert manifest["file"] is None

    # upload the manifest again
    data = {}
    data["manifest"] = (open(manifest_file_path, "rb"), "manifest.csv")
    rv = client.put(
        "{}/jurisdiction/{}/manifest".format(url_prefix, jurisdiction_id),
        data=data,
        content_type="multipart/form-data",
    )

    assert_ok(rv)
    assert bgcompute.bgcompute_update_ballot_manifest_file() == 1

    setup_audit_board(client, election_id, jurisdiction_id, audit_board_id_1)

    # get the retrieval list for round 1
    rv = client.get(
        "{}/jurisdiction/{}/1/retrieval-list".format(url_prefix, jurisdiction_id)
    )
    lines = rv.data.decode("utf-8").splitlines()
    assert (
        lines[0]
        == "Batch Name,Ballot Number,Storage Location,Tabulator,Ticket Numbers,Already Audited,Audit Board"
    )
    assert len(lines) > 5
    assert "attachment" in rv.headers["content-disposition"]

    num_ballots = get_num_ballots_from_retrieval_list(rv)

    return (
        url_prefix,
        contest_id,
        candidate_id_1,
        candidate_id_2,
        jurisdiction_id,
        audit_board_id_1,
        audit_board_id_2,
        num_ballots,
    )