Skip to content

Commit

Permalink
Complete GenerateAssertionsService tests. Added some explicit checks …
Browse files Browse the repository at this point in the history
…in the GenerateAssertionsService for invalid contests and no votes.
  • Loading branch information
vteague committed May 24, 2024
1 parent 3543895 commit d870dec
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ public RaireResultOrError generateAssertions(GenerateAssertionsRequest request)
prefix, request.contestName, request.candidates, request.totalAuditableBallots,
request.timeLimitSeconds));

// Check that the contest exists and is all IRV. Otherwise this is an internal error because
// it should be caught before here.
if(contestRepository.findFirstByName(request.contestName).isEmpty()
|| !contestRepository.isAllIRV(request.contestName)) {
final String msg = String.format("%s Contest %s does not exist or is not all IRV", prefix,
request.contestName);
logger.error(msg + "Throwing a RaireServiceException.");
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}

// Use raire-java to consolidate the votes, collecting all votes with the same ranking
// together and representing that collection as a single ranking with an associated number
// denoting how many votes with that ranking exist.
Expand All @@ -103,14 +113,21 @@ public RaireResultOrError generateAssertions(GenerateAssertionsRequest request)
c -> cvrContestInfoRepository.getCVRs(c.getContestID(), c.getCountyID())).
flatMap(List::stream).toList();

if(votes.size() > request.totalAuditableBallots){
if(votes.size() > request.totalAuditableBallots) {
final String msg = String.format("%s %d votes present for contest %s but a universe size of "
+ "%d specified in the assertion generation request. Throwing a RaireServiceException.",
prefix, votes.size(), request.contestName, request.totalAuditableBallots);
logger.error(msg);
throw new RaireServiceException(msg, RaireErrorCode.INVALID_TOTAL_AUDITABLE_BALLOTS);
}

if(votes.isEmpty()) {
final String msg = String.format("%s No votes present for contest %s.", prefix,
request.contestName);
logger.error(msg + " Throwing a RaireServiceException.");
throw new RaireServiceException(msg, RaireErrorCode.NO_VOTES_PRESENT);
}

logger.debug(String.format("%s Adding all extracted rankings to a consolidator to identify " +
"unique rankings and their number.", prefix));
votes.forEach(consolidator::addVoteNames);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,16 @@ public enum RaireErrorCode {
WRONG_CANDIDATE_NAMES,

/**
* The user has request to retrieve assertions for a contest for which no assertions have
* The user has requested to retrieve assertions for a contest for which no assertions have
* been generated.
*/
NO_ASSERTIONS_PRESENT,

/**
* The user has requested to generate assertions for a contest for which no votes are present.
*/
NO_VOTES_PRESENT,

// Internal errors (that the user can do nothing about)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest;
import au.org.democracydevelopers.raireservice.testUtils;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand All @@ -47,10 +49,11 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra

/**
* Tests for appropriate responses to bad requests to the generate-assertions endpoint. This class
* automatically fires up the RAIRE Microservice on a random port. Currently, we check for
* proper input validation.
* automatically fires up the RAIRE Microservice on a random port. These sorts of errors are _not_
* supposed to happen - they indicate programming errors or problems with databases etc.
* Currently, we check for proper input validation and inconsistent input.
* The list of tests is similar to GenerateAssertionsRequestTests.java, and also to
* GetAssertionsAPITests.java when the same test is relevant to both endpoints.
* GetAssertionsAPIErrorTests.java when the same test is relevant to both endpoints.
* Contests which will be used for validity testing are
* preloaded into the database using src/test/resources/data.sql.
* Tests include:
Expand All @@ -74,6 +77,8 @@ public class GenerateAssertionsAPIErrorTests {
private final static String baseURL = "http://localhost:";
private final static String generateAssertionsEndpoint = "/raire/generate-assertions";

private final static List<String> aliceAndBob = List.of("Alice","Bob");

@LocalServerPort
private int port;

Expand Down Expand Up @@ -132,17 +137,32 @@ public void generateAssertionsWithNonExistentContestIsAnError() {
testUtils.log(logger, "generateAssertionsWithNonExistentContestIsAnError");
String url = baseURL + port + generateAssertionsEndpoint;

String requestAsJson =
"{\"timeLimitSeconds\":10.0,\"totalAuditableBallots\":100,"
+"\"contestName\":\"NonExistentContest\",\"candidates\":[\"Alice\",\"Bob\"]}";

HttpEntity<String> request = new HttpEntity<>(requestAsJson, httpHeaders);
GenerateAssertionsRequest request = new GenerateAssertionsRequest("NonExistentContest",
100, 10, aliceAndBob);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

assertTrue(response.getStatusCode().is4xxClientError());
assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No such contest"));
}

/**
* The generateAssertions endpoint, called with a valid IRV contest for which no votes are present,
* returns a meaningful error.
*/
@Test
public void generateAssertionsFromNoVotesIsAnError() {
testUtils.log(logger, "generateAssertionsFromNoVotesIsAnError");
String url = baseURL + port + generateAssertionsEndpoint;

GenerateAssertionsRequest request = new GenerateAssertionsRequest("No CVR Mayoral", 100,
10, aliceAndBob);

ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

assertTrue(response.getStatusCode().is5xxServerError());
assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No votes present for contest"));
}

/**
* The generateAssertions endpoint, called with a valid plurality contest,
* returns a meaningful error.
Expand All @@ -152,11 +172,8 @@ public void generateAssertionsWithPluralityContestIsAnError() {
testUtils.log(logger, "generateAssertionsWithPluralityContestIsAnError");
String url = baseURL + port + generateAssertionsEndpoint;

String requestAsJson =
"{\"timeLimitSeconds\":10.0,\"totalAuditableBallots\":100,"
+"\"contestName\":\"Valid Plurality Contest\",\"candidates\":[\"Alice\",\"Bob\"]}";

HttpEntity<String> request = new HttpEntity<>(requestAsJson, httpHeaders);
GenerateAssertionsRequest request = new GenerateAssertionsRequest(
"Valid Plurality Contest", 100, 10, aliceAndBob);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

assertTrue(response.getStatusCode().is4xxClientError());
Expand All @@ -172,11 +189,8 @@ public void generateAssertionsWithMixedIRVPluralityContestIsAnError() {
testUtils.log(logger, "generateAssertionsWithMixedIRVPluralityContestIsAnError");
String url = baseURL + port + generateAssertionsEndpoint;

String requestAsJson =
"{\"timeLimitSeconds\":10.0,\"totalAuditableBallots\":100,"
+"\"contestName\":\"Invalid Mixed Contest\",\"candidates\":[\"Alice\",\"Bob\"]}";

HttpEntity<String> request = new HttpEntity<>(requestAsJson, httpHeaders);
GenerateAssertionsRequest request = new GenerateAssertionsRequest("Invalid Mixed Contest",
100,10,aliceAndBob);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

assertTrue(response.getStatusCode().is4xxClientError());
Expand Down Expand Up @@ -400,8 +414,6 @@ public void generateAssertionsWithNegativeAuditableBallotsIsAnError() {
"Non-positive total auditable ballots"));
}



/**
* The generateAssertions endpoint, called with null/missing time limit, returns a meaningful error.
*/
Expand Down Expand Up @@ -478,6 +490,8 @@ public void wrongCandidatesIsAnError() {
assertTrue(response.getStatusCode().is5xxServerError());
assertEquals(WRONG_CANDIDATE_NAMES.toString(),
response.getHeaders().getFirst("error_code"));
assertTrue(StringUtils.containsIgnoreCase(response.getBody(),
"was not on the list of candidates"));
}

}
Loading

0 comments on commit d870dec

Please sign in to comment.