Esempio n. 1
0
def test_contests_create_get_update_multiple(
    client: FlaskClient,
    election_id: str,
    json_contests: List[JSONDict],
    jurisdiction_ids: List[str],
):
    rv = put_json(client, f"/election/{election_id}/contest", json_contests)
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)
    expected_contests = [{
        **contest, "currentRoundStatus": None
    } for contest in json_contests]
    assert contests == {"contests": expected_contests}

    json_contests[0]["name"] = "Changed name"
    json_contests[1]["isTargeted"] = True
    json_contests[2]["jurisdictionIds"] = jurisdiction_ids[1:]

    rv = put_json(client, f"/election/{election_id}/contest", json_contests)
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)
    expected_contests = [{
        **contest, "currentRoundStatus": None
    } for contest in json_contests]
    assert contests == {"contests": expected_contests}
Esempio n. 2
0
def test_contests_missing_field(client: FlaskClient, election_id: str,
                                jurisdiction_ids: List[str]):
    contest: JSONDict = {
        "id":
        str(uuid.uuid4()),
        "name":
        "Contest 1",
        "isTargeted":
        True,
        "choices": [
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 1",
                "numVotes": 48121,
            },
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 2",
                "numVotes": 38026,
            },
        ],
        "totalBallotsCast":
        86147,
        "numWinners":
        1,
        "votesAllowed":
        1,
        "jurisdictionIds":
        jurisdiction_ids,
    }

    for field in contest:
        invalid_contest = contest.copy()
        del invalid_contest[field]

        rv = put_json(client, f"/election/{election_id}/contest",
                      [invalid_contest])
        assert rv.status_code == 400
        assert json.loads(rv.data) == {
            "errors": [{
                "message": f"'{field}' is a required property",
                "errorType": "Bad Request",
            }]
        }

    for field in contest["choices"][0]:
        invalid_contest = contest.copy()
        invalid_contest_choice = invalid_contest["choices"][0].copy()
        del invalid_contest_choice[field]
        invalid_contest["choices"] = [invalid_contest_choice]

        rv = put_json(client, f"/election/{election_id}/contest",
                      [invalid_contest])
        assert rv.status_code == 400
        assert json.loads(rv.data) == {
            "errors": [{
                "message": f"'{field}' is a required property",
                "errorType": "Bad Request",
            }]
        }
Esempio n. 3
0
def test_contests_create_get_update_one(client, election_id, json_contests):
    contest = json_contests[0]
    rv = put_json(client, f"/election/{election_id}/contest", [contest])
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)
    expected_contest = {**contest, "currentRoundStatus": None}
    assert contests == {"contests": [expected_contest]}

    contest["totalBallotsCast"] = contest["totalBallotsCast"] + 21
    contest["numWinners"] = 2
    contest["choices"].append({
        "id": str(uuid.uuid4()),
        "name": "candidate 3",
        "numVotes": 21,
    })

    rv = put_json(client, f"/election/{election_id}/contest", [contest])
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)
    expected_contest = {**contest, "currentRoundStatus": None}
    assert contests == {"contests": [expected_contest]}
Esempio n. 4
0
def election_settings(client: FlaskClient, election_id: str):
    settings = {
        "electionName": "Test Election",
        "online": True,
        "randomSeed": "1234567890",
        "riskLimit": 10,
        "state": USState.California,
    }
    rv = put_json(client, f"/election/{election_id}/settings", settings)
    assert_ok(rv)
Esempio n. 5
0
def test_invalid_additional_property(client: FlaskClient, election_id: str):
    rv = put_json(
        client,
        f"/election/{election_id}/settings",
        {"electionNameTypo": "An Updated Name"},
    )
    assert rv.status_code == 400, f"unexpected response: {rv.data}"
    assert json.loads(rv.data) == {
        "errors": [{
            "message":
            "Additional properties are not allowed ('electionNameTypo' was unexpected)",
            "errorType": "Bad Request",
        }]
    }
Esempio n. 6
0
def test_ab_audit_ballot_not_found(
    client: FlaskClient,
    election_id: str,
    jurisdiction_ids: List[str],
    round_1_id: str,
    audit_board_round_1_ids: List[str],
):
    set_logged_in_user(client, UserType.AUDIT_BOARD,
                       audit_board_round_1_ids[0])
    rv = put_json(
        client,
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/not-a-real-ballot-id",
        {},
    )
    assert rv.status_code == 404
Esempio n. 7
0
def test_audit_board_contests_list_empty(
    client: FlaskClient,
    election_id: str,
    jurisdiction_ids: List[str],
    round_1_id: str,
    audit_board_round_1_ids: List[str],
    json_contests: List[JSONDict],
):
    rv = put_json(client, f"/election/{election_id}/contest",
                  [json_contests[1]])
    assert_ok(rv)
    set_logged_in_user(client,
                       UserType.AUDIT_BOARD,
                       user_key=audit_board_round_1_ids[0])
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/contest"
    )
    assert json.loads(rv.data) == {"contests": []}
Esempio n. 8
0
def test_invalid_risk_limit(client: FlaskClient, election_id: str):
    # Get the existing data.
    rv = client.get(f"/election/{election_id}/settings")

    # Set an invalid state.
    election = json.loads(rv.data)
    election["riskLimit"] = -1

    # Attempt to write invalid data.
    rv = put_json(client, f"/election/{election_id}/settings", election)

    assert rv.status_code == 400, f"unexpected response: {rv.data}"
    compare_json(
        json.loads(rv.data),
        {
            "errors": [{
                "message": "-1 is less than the minimum of 1",
                "errorType": "Bad Request",
            }]
        },
    )
Esempio n. 9
0
def test_update_election(client: FlaskClient, election_id: str):
    # Get the existing data.
    rv = client.get(f"/election/{election_id}/settings")

    # Update the values.
    election = json.loads(rv.data)
    election["electionName"] = "An Updated Name"
    election["online"] = True
    election["randomSeed"] = "a new random seed"
    election["riskLimit"] = 15
    election["state"] = USState.Mississippi

    rv = put_json(client, f"/election/{election_id}/settings", election)
    assert_ok(rv)

    election_record = Election.query.filter_by(id=election_id).one()
    assert election_record.election_name == "An Updated Name"
    assert election_record.online is True
    assert election_record.random_seed == "a new random seed"
    assert election_record.risk_limit == 15
    assert election_record.state == USState.Mississippi
Esempio n. 10
0
def test_invalid_state(client: FlaskClient, election_id: str):
    # Get the existing data.
    rv = client.get(f"/election/{election_id}/settings")

    # Set an invalid state.
    election = json.loads(rv.data)
    election["state"] = "XX"

    # Attempt to write invalid data.
    rv = put_json(client, f"/election/{election_id}/settings", election)

    assert rv.status_code == 400, f"unexpected response: {rv.data}"
    compare_json(
        json.loads(rv.data),
        {
            "errors":
            [{
                "message": asserts_startswith("'XX' is not one of ['AL',"),
                "errorType": "Bad Request",
            }]
        },
    )
Esempio n. 11
0
def test_audit_board_contests_list(
    client: FlaskClient,
    election_id: str,
    jurisdiction_ids: List[str],
    round_1_id: str,
    audit_board_round_1_ids: List[str],
    json_contests: List[JSONDict],
):
    rv = put_json(client, f"/election/{election_id}/contest", json_contests)
    assert_ok(rv)

    set_logged_in_user(client,
                       UserType.AUDIT_BOARD,
                       user_key=audit_board_round_1_ids[0])
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/contest"
    )
    contests = json.loads(rv.data)
    expected_contests = [{
        **contest, "currentRoundStatus": None
    } for contest in [json_contests[0], json_contests[2]]]
    assert contests == {"contests": expected_contests}
Esempio n. 12
0
def test_ab_audit_ballot_invalid(
    client: FlaskClient,
    election_id: str,
    jurisdiction_ids: List[str],
    contest_ids: List[str],
    round_1_id: str,
    audit_board_round_1_ids: List[str],
):
    set_logged_in_user(client, UserType.AUDIT_BOARD,
                       audit_board_round_1_ids[0])
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots"
    )
    ballots = json.loads(rv.data)["ballots"]
    ballot = ballots[0]

    choice_id = ContestChoice.query.filter_by(
        contest_id=contest_ids[0]).first().id

    for missing_field in ["status", "interpretations"]:
        audit_request = {
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "VOTE",
                "choiceId": choice_id,
                "comment": "blah blah blah",
            }],
        }
        del audit_request[missing_field]
        rv = put_json(
            client,
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/{ballot['id']}",
            audit_request,
        )
        assert rv.status_code == 400
        assert json.loads(rv.data) == {
            "errors": [{
                "errorType": "Bad Request",
                "message": f"'{missing_field}' is a required property",
            }]
        }

    for missing_field in [
            "contestId", "interpretation", "choiceId", "comment"
    ]:
        interpretation = {
            "contestId": contest_ids[0],
            "interpretation": "VOTE",
            "choiceId": choice_id,
            "comment": "blah blah blah",
        }
        del interpretation[missing_field]
        rv = put_json(
            client,
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/{ballot['id']}",
            {
                "status": "AUDITED",
                "interpretations": [interpretation],
            },
        )
        assert rv.status_code == 400
        assert json.loads(rv.data) == {
            "errors": [{
                "errorType": "Bad Request",
                "message": f"'{missing_field}' is a required property",
            }]
        }

    invalid_requests = [
        (
            {
                "status":
                "audited",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            "'audited' is not one of ['NOT_AUDITED', 'AUDITED', 'NOT_FOUND']",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "vote",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            "'vote' is not one of ['BLANK', 'CANT_AGREE', 'VOTE']",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": None,
                    "comment": "blah blah blah",
                }],
            },
            f"Must include choiceId with interpretation VOTE for contest {contest_ids[0]}",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": "",
                    "comment": "blah blah blah",
                }],
            },
            f"Must include choiceId with interpretation VOTE for contest {contest_ids[0]}",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": "12345",
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            "Contest not found: 12345",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": "12345",
                    "comment": "blah blah blah",
                }],
            },
            "Contest choice not found: 12345",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[1],
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            f"Contest choice {choice_id} is not associated with contest {contest_ids[1]}",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "BLANK",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            f"Cannot include choiceId with interpretation BLANK for contest {contest_ids[0]}",
        ),
        (
            {
                "status":
                "AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "CANT_AGREE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            f"Cannot include choiceId with interpretation CANT_AGREE for contest {contest_ids[0]}",
        ),
        (
            {
                "status": "AUDITED",
                "interpretations": [],
            },
            "Must include interpretations with ballot status AUDITED.",
        ),
        (
            {
                "status":
                "NOT_FOUND",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            "Cannot include interpretations with ballot status NOT_FOUND.",
        ),
        (
            {
                "status":
                "NOT_AUDITED",
                "interpretations": [{
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": "blah blah blah",
                }],
            },
            "Cannot include interpretations with ballot status NOT_AUDITED.",
        ),
    ]
    for (invalid_request, expected_message) in invalid_requests:
        rv = put_json(
            client,
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/{ballot['id']}",
            invalid_request,
        )
        assert rv.status_code == 400
        assert json.loads(rv.data) == {
            "errors": [{
                "errorType": "Bad Request",
                "message": expected_message
            }]
        }
Esempio n. 13
0
def test_ja_ballots_round_1(
        client: FlaskClient,
        election_id: str,
        jurisdiction_ids: List[str],
        contest_ids: str,
        round_1_id: str,
        audit_board_round_1_ids: List[str],  # pylint: disable=unused-argument
):
    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/ballots"
    )
    ballots = json.loads(rv.data)["ballots"]

    assert len(ballots) == J1_BALLOTS_ROUND_1
    compare_json(
        ballots[0],
        {
            "id": assert_is_id,
            "auditBoard": {
                "id": assert_is_id,
                "name": "Audit Board #1"
            },
            "batch": {
                "id": assert_is_id,
                "name": "4",
                "tabulator": None
            },
            "position": 8,
            "status": "NOT_AUDITED",
            "interpretations": [],
        },
    )

    ballot_with_wrong_status = next(
        (b for b in ballots if b["status"] != "NOT_AUDITED"), None)
    assert ballot_with_wrong_status is None

    assert ballots == sorted(
        ballots,
        key=lambda b:
        (b["auditBoard"]["name"], b["batch"]["name"], b["position"]),
    )

    # Try auditing one ballot
    set_logged_in_user(client, UserType.AUDIT_BOARD,
                       audit_board_round_1_ids[0])
    choice_id = ContestChoice.query.filter_by(
        contest_id=contest_ids[0]).first().id
    rv = put_json(
        client,
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/{ballots[0]['id']}",
        {
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "VOTE",
                "choiceId": choice_id,
                "comment": "blah blah blah",
            }],
        },
    )
    assert_ok(rv)

    set_logged_in_user(client, UserType.JURISDICTION_ADMIN, DEFAULT_JA_EMAIL)
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/ballots"
    )
    ballots = json.loads(rv.data)["ballots"]

    compare_json(
        ballots[0],
        {
            "id":
            assert_is_id,
            "auditBoard": {
                "id": assert_is_id,
                "name": "Audit Board #1"
            },
            "batch": {
                "id": assert_is_id,
                "name": "4",
                "tabulator": None
            },
            "position":
            8,
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "VOTE",
                "choiceId": choice_id,
                "comment": "blah blah blah",
            }],
        },
    )
Esempio n. 14
0
def test_contest_too_many_votes(client: FlaskClient, election_id: str):
    contest = {
        "id":
        str(uuid.uuid4()),
        "name":
        "Contest 1",
        "isTargeted":
        True,
        "choices": [
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 1",
                "numVotes": 400,
            },
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 2",
                "numVotes": 101,
            },
        ],
        "totalBallotsCast":
        500,
        "numWinners":
        1,
        "votesAllowed":
        1,
        "jurisdictionIds": [],
    }

    rv = put_json(client, f"/election/{election_id}/contest", [contest])
    assert rv.status_code == 400
    assert json.loads(rv.data) == {
        "errors": [{
            "message":
            "Too many votes cast in contest: Contest 1 (501 votes, 500 allowed)",
            "errorType": "Bad Request",
        }]
    }

    contest = {
        "id":
        str(uuid.uuid4()),
        "name":
        "Contest 1",
        "isTargeted":
        True,
        "choices": [
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 1",
                "numVotes": 700,
            },
            {
                "id": str(uuid.uuid4()),
                "name": "candidate 2",
                "numVotes": 301,
            },
        ],
        "totalBallotsCast":
        500,
        "numWinners":
        1,
        "votesAllowed":
        2,
        "jurisdictionIds": [],
    }

    rv = put_json(client, f"/election/{election_id}/contest", [contest])
    assert rv.status_code == 400
    assert json.loads(rv.data) == {
        "errors": [{
            "message":
            "Too many votes cast in contest: Contest 1 (1001 votes, 1000 allowed)",
            "errorType": "Bad Request",
        }]
    }
Esempio n. 15
0
def test_contests_round_status(
        client: FlaskClient,
        election_id: str,
        json_contests: List[JSONDict],
        election_settings,  # pylint: disable=unused-argument
        manifests,  # pylint: disable=unused-argument
):
    rv = put_json(client, f"/election/{election_id}/contest", json_contests)
    assert_ok(rv)

    rv = post_json(
        client,
        f"/election/{election_id}/round",
        {
            "roundNum": 1,
            "sampleSize": SAMPLE_SIZE_ROUND_1
        },
    )
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)["contests"]

    assert contests[0]["currentRoundStatus"] == {
        "isRiskLimitMet": None,
        "numBallotsSampled": SAMPLE_SIZE_ROUND_1,
    }
    assert contests[1]["currentRoundStatus"] == {
        "isRiskLimitMet": None,
        "numBallotsSampled": 0,
    }
    assert contests[2]["currentRoundStatus"] == {
        "isRiskLimitMet": None,
        "numBallotsSampled": 81,
    }

    # Fake that one opportunistic contest met its risk limit, but the targeted
    # contest did not
    opportunistic_round_contest = RoundContest.query.filter_by(
        contest_id=contests[1]["id"]).one()
    opportunistic_round_contest.is_complete = True
    targeted_round_contest = RoundContest.query.filter_by(
        contest_id=contests[0]["id"]).one()
    targeted_round_contest.is_complete = False
    db.session.commit()

    rv = client.get(f"/election/{election_id}/contest")
    contests = json.loads(rv.data)["contests"]

    assert contests[0]["currentRoundStatus"] == {
        "isRiskLimitMet": False,
        "numBallotsSampled": SAMPLE_SIZE_ROUND_1,
    }
    assert contests[1]["currentRoundStatus"] == {
        "isRiskLimitMet": True,
        "numBallotsSampled": 0,
    }
    assert contests[2]["currentRoundStatus"] == {
        "isRiskLimitMet": None,
        "numBallotsSampled": 81,
    }
Esempio n. 16
0
def contest_ids(client: FlaskClient, election_id: str,
                jurisdiction_ids: List[str]) -> List[str]:
    contests = [
        {
            "id":
            str(uuid.uuid4()),
            "name":
            "Contest 1",
            "isTargeted":
            True,
            "choices": [
                {
                    "id": str(uuid.uuid4()),
                    "name": "candidate 1",
                    "numVotes": 600,
                },
                {
                    "id": str(uuid.uuid4()),
                    "name": "candidate 2",
                    "numVotes": 400,
                },
            ],
            "totalBallotsCast":
            1000,
            "numWinners":
            1,
            "votesAllowed":
            1,
            "jurisdictionIds":
            jurisdiction_ids,
        },
        {
            "id":
            str(uuid.uuid4()),
            "name":
            "Contest 2",
            "isTargeted":
            False,
            "choices": [
                {
                    "id": str(uuid.uuid4()),
                    "name": "candidate 1",
                    "numVotes": 200,
                },
                {
                    "id": str(uuid.uuid4()),
                    "name": "candidate 2",
                    "numVotes": 300,
                },
                {
                    "id": str(uuid.uuid4()),
                    "name": "candidate 3",
                    "numVotes": 100,
                },
            ],
            "totalBallotsCast":
            600,
            "numWinners":
            2,
            "votesAllowed":
            2,
            "jurisdictionIds":
            jurisdiction_ids[:2],
        },
    ]
    rv = put_json(client, f"/election/{election_id}/contest", contests)
    assert_ok(rv)
    return [str(c["id"]) for c in contests]
Esempio n. 17
0
def test_jurisdictions_round_status_offline(
        client: FlaskClient,
        election_id: str,
        jurisdiction_ids: List[str],
        contest_ids: List[str],  # pylint: disable=unused-argument
        election_settings,  # pylint: disable=unused-argument
        manifests,  # pylint: disable=unused-argument
):
    # Change the settings to offline
    settings = {
        "electionName": "Test Election",
        "online": False,
        "randomSeed": "1234567890",
        "riskLimit": 10,
        "state": USState.California,
    }
    rv = put_json(client, f"/election/{election_id}/settings", settings)
    assert_ok(rv)

    rv = post_json(
        client,
        f"/election/{election_id}/round",
        {
            "roundNum": 1,
            "sampleSize": SAMPLE_SIZE_ROUND_1
        },
    )
    assert_ok(rv)

    rv = client.get(f"/election/{election_id}/jurisdiction")
    jurisdictions = json.loads(rv.data)["jurisdictions"]

    assert jurisdictions[0]["currentRoundStatus"] == {
        "status": "NOT_STARTED",
        "numBallotsSampled": J1_SAMPLES_ROUND_1,
        "numBallotsAudited": 0,
    }

    # Simulate creating an audit board
    rv = client.get(f"/election/{election_id}/round")
    round = json.loads(rv.data)["rounds"][0]

    ballots = (SampledBallot.query.join(SampledBallotDraw).filter_by(
        round_id=round["id"]).all())
    audit_board_1 = AuditBoard(
        id=str(uuid.uuid4()),
        jurisdiction_id=jurisdiction_ids[0],
        round_id=round["id"],
        sampled_ballots=ballots[:AB1_SAMPLES + 1],
    )
    db.session.add(audit_board_1)
    db.session.commit()

    rv = client.get(f"/election/{election_id}/jurisdiction")
    jurisdictions = json.loads(rv.data)["jurisdictions"]

    assert jurisdictions[0]["currentRoundStatus"] == {
        "status": "IN_PROGRESS",
        "numBallotsSampled": J1_SAMPLES_ROUND_1,
        "numBallotsAudited": 0,
    }

    # Simulate the audit board signing off
    audit_board_1 = db.session.merge(audit_board_1)  # Reload into the session
    audit_board_1.signed_off_at = datetime.utcnow()
    db.session.commit()

    rv = client.get(f"/election/{election_id}/jurisdiction")
    jurisdictions = json.loads(rv.data)["jurisdictions"]

    assert jurisdictions[0]["currentRoundStatus"] == {
        "status": "COMPLETE",
        "numBallotsSampled": J1_SAMPLES_ROUND_1,
        "numBallotsAudited": J1_SAMPLES_ROUND_1,
    }
Esempio n. 18
0
def test_ab_audit_ballot_happy_path(
    client: FlaskClient,
    election_id: str,
    jurisdiction_ids: List[str],
    contest_ids: List[str],
    round_1_id: str,
    audit_board_round_1_ids: List[str],
):
    set_logged_in_user(client, UserType.AUDIT_BOARD,
                       audit_board_round_1_ids[0])
    rv = client.get(
        f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots"
    )
    ballots = json.loads(rv.data)["ballots"]
    ballot = ballots[0]

    choice_id = ContestChoice.query.filter_by(
        contest_id=contest_ids[0]).first().id
    audit_requests: List[JSONDict] = [
        {
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "VOTE",
                "choiceId": choice_id,
                "comment": "blah blah blah",
            }],
        },
        {
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "BLANK",
                "choiceId": None,
                "comment": None,
            }],
        },
        {
            "status":
            "AUDITED",
            "interpretations": [{
                "contestId": contest_ids[0],
                "interpretation": "CANT_AGREE",
                "choiceId": None,
                "comment": None,
            }],
        },
        {
            "status": "NOT_AUDITED",
            "interpretations": [],
        },
        {
            "status": "NOT_FOUND",
            "interpretations": [],
        },
        {
            "status":
            "AUDITED",
            "interpretations": [
                {
                    "contestId": contest_ids[0],
                    "interpretation": "VOTE",
                    "choiceId": choice_id,
                    "comment": None,
                },
                {
                    "contestId": contest_ids[1],
                    "interpretation": "CANT_AGREE",
                    "choiceId": None,
                    "comment": "weird scribble",
                },
            ],
        },
    ]

    for audit_request in audit_requests:
        rv = put_json(
            client,
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots/{ballot['id']}",
            audit_request,
        )
        assert_ok(rv)

        rv = client.get(
            f"/election/{election_id}/jurisdiction/{jurisdiction_ids[0]}/round/{round_1_id}/audit-board/{audit_board_round_1_ids[0]}/ballots"
        )
        ballots = json.loads(rv.data)["ballots"]

        ballots[0]["interpretations"] = sorted(ballots[0]["interpretations"],
                                               key=lambda i: i["contestId"])
        audit_request["interpretations"] = sorted(
            audit_request["interpretations"], key=lambda i: i["contestId"])

        assert ballots[0] == {**ballot, **audit_request}