Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

173 generate assertions status response #102

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,44 +95,47 @@ public ResponseEntity<GenerateAssertionsResponse> serve(@RequestBody GenerateAss
request.Validate(contestRepository);
logger.debug(String.format("%s Assertion generation request successfully validated.",prefix));

// Call raire-java to generate assertions, and check if it was able to do so successfully.
// Call raire-java to generate assertions.
logger.debug(String.format("%s Calling raire-java with assertion generation request.",prefix));
RaireResultOrError solution = generateAssertionsService.generateAssertions(request);

// Save the result (whether error or success) to the database.
generateAssertionsService.persistAssertionsOrErrors(solution, request);

// Check if assertion generation was successful, return appropriate response.

// Assertion generation succeeded.
if (solution.Ok != null) {
// Generation of assertions was successful - return a GenerateAssertionsResponse.
logger.debug(String.format("%s Assertion generation successful: %d assertions " +
"generated in %ss.", prefix, solution.Ok.assertions.length,
solution.Ok.time_to_find_assertions.seconds));

logger.debug(String.format("%s Assertions stored in database for contest %s.",
prefix, request.contestName));

// Form and return response.
GenerateAssertionsResponse response = new GenerateAssertionsResponse(request.contestName,
request.candidates.get(solution.Ok.winner));

logger.debug(String.format("%s Assertion generation and storage complete.", prefix));
return new ResponseEntity<>(response, HttpStatus.OK);

// Form and return a success response.
return new ResponseEntity<>(
new GenerateAssertionsResponse(request.contestName, solution.Ok.warning_trim_timed_out),
HttpStatus.OK);
}

// raire-java was not able to generate assertions successfully, but did not return an error.
// This is not supposed to happen.
if(solution.Err == null){
final String msg = "An error occurred in raire-java, yet no error information was returned.";
logger.error(String.format("%s %s", prefix, msg));
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
// Assertion generation failed and returned error information.
// This is HttpStatus.OK because the http request-and-response was fine, but something was wrong
// with either the data or the generation process.
if(solution.Err != null) {
final String msg = "Assertion generation failed. Error from raire-java: ";
logger.error(String.format("%s %s %s", prefix, msg, solution.Err));
return new ResponseEntity<>(
new GenerateAssertionsResponse(request.contestName, solution.Err), HttpStatus.OK);
}

// raire-java returned error information. Form and throw an exception using that data. (Note:
// we need to create the exception first to get a human readable message to log).
RaireServiceException ex = new RaireServiceException(solution.Err, request.candidates);
final String msg = "An error occurred in raire-java: " + ex.getMessage();
// raire-java was not able to generate assertions successfully, but did not return an error.
// This is not supposed to happen.
final String msg = "An error occurred in raire-java, yet no error information was returned.";
logger.error(String.format("%s %s", prefix, msg));
throw ex;
throw new RaireServiceException(msg, RaireErrorCode.INTERNAL_ERROR);
}


Expand All @@ -144,8 +147,6 @@ public ResponseEntity<GenerateAssertionsResponse> serve(@RequestBody GenerateAss
* @return the assertions, as JSON (in the case of success) or an error.
* @throws RequestValidationException for invalid requests, such as non-existent, null, or
* non-IRV contest names.
* @throws RequestValidationException for invalid requests, such as non-existent, null, or non-IRV
* contest names.
* @throws RaireServiceException if the request is valid but assertion retrieval fails, for example
* if there are no assertions for the contest.
* These exceptions are handled by ControllerExceptionHandler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,48 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra

package au.org.democracydevelopers.raireservice.response;

import au.org.democracydevelopers.raire.RaireError;

import java.beans.ConstructorProperties;

/**
* The success response to a ContestRequest. This simply returns the winner, as
* calculated by raire, along with the name of the contest for which the initial request was made.
*
* The response to a ContestRequest, describing the status of assertion generation.
* All four states of the two booleans are possible - for example, generation may succeed, but
* receive a TIME_OUT_TRIMMING_ASSERTIONS warning, in which case retry will be true.
* @param contestName The name of the contest.
* @param winner The winner of the contest, as calculated by raire.
* @param succeeded Whether assertion generation succeeded.
* @param retry Whether it is worth retrying assertion generation.
*/
public record GenerateAssertionsResponse(String contestName, String winner) {
public record GenerateAssertionsResponse(String contestName, boolean succeeded, boolean retry) {

/**
* All args constructor.
*
* @param contestName the name of the contest.
* @param winner the name of the winner.
* All args constructor, for deserialization.
* @param contestName The name of the contest.
* @param succeeded Whether assertion generation succeeded.
* @param retry Whether it is worth retrying assertion generation.
*/
@ConstructorProperties({"contestName", "winner"})
@ConstructorProperties({"contestName", "succeeded", "retry"})
public GenerateAssertionsResponse {
}

/**
* Failure. If the error is one of the timeouts, it is worth retrying.
* @param contestName The name of the contest.
* @param error The raire error, used to determine whether retry should be recommended.
* Retry is set to true if the error is one of the timeouts, otherwise false.
*/
public GenerateAssertionsResponse(String contestName, RaireError error) {
this(contestName, false,
error instanceof RaireError.TimeoutCheckingWinner
|| error instanceof RaireError.TimeoutFindingAssertions
|| error instanceof RaireError.TimeoutTrimmingAssertions
);
}

// Success. Retry only if the TIME_OUT_TRIMMING_ASSERTIONS flag is true.
public GenerateAssertionsResponse(String contestName, boolean timeOutTrimmingAssertions) {

// If time out trimming assertions is true, then retry should be true.
this(contestName, true, timeOutTrimmingAssertions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public void persistAssertionsOrErrors(RaireResultOrError solution, ContestReques
// A summary is already present in the database - we will update this.
summary = OptSummary.get();
} else {
// There is no summary for ths contest - make a new blank summary.
// There is no summary for this contest - make a new blank summary.
summary = new GenerateAssertionsSummary(request.contestName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public class RaireServiceException extends Exception {
public static String ERROR_CODE_KEY = "error_code";

/**
* The error code - an enum used to describe what went wrong. Returned in the http response for
* colorado-rla to interpret for the user.
* The error code - an enum used to describe what went wrong. Stored in the GenerateAssertionsSummary
* for colorado-rla to read.
*/
public RaireErrorCode errorCode;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;

import au.org.democracydevelopers.raire.RaireSolution;
import au.org.democracydevelopers.raire.algorithm.RaireResult;
Expand Down Expand Up @@ -147,10 +148,11 @@ public void testGuideToRairePart2Example1() {
ResponseEntity<GenerateAssertionsResponse> response = restTemplate.postForEntity(generateUrl,
request, GenerateAssertionsResponse.class);

// Check that generation is successful and we got the right winner.
// Check that generation is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), "Chuan");
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(guideToRaireExample1, 27,
Expand Down Expand Up @@ -191,10 +193,11 @@ void testGuideToRairePart2Example2() {
ResponseEntity<GenerateAssertionsResponse> response
= restTemplate.postForEntity(generateUrl, request, GenerateAssertionsResponse.class);

// Check that the response is successful and we got the right winner.
// Check that the response is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), "Chuan");
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(guideToRaireExample2, 41,
Expand Down Expand Up @@ -243,10 +246,11 @@ public void simpleContestSingleCounty() {
ResponseEntity<GenerateAssertionsResponse> response
= restTemplate.postForEntity(generateUrl, request, GenerateAssertionsResponse.class);

// Check that the response is successful and we got the right winner.
// Check that the response is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), "Alice");
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(simpleContest, 147,
Expand Down Expand Up @@ -287,10 +291,11 @@ public void simpleContestCrossCounty() {
ResponseEntity<GenerateAssertionsResponse> response
= restTemplate.postForEntity(generateUrl, request, GenerateAssertionsResponse.class);

// Check that the response is successful and we got the right winner.
// Check that the response is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), "Alice");
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(crossCountySimpleContest, 5,
Expand Down Expand Up @@ -331,19 +336,20 @@ public void simpleContestSingleCountyDoubleBallots() {
String generateUrl = baseURL + port + generateAssertionsEndpoint;
String getUrl = baseURL + port + getAssertionsEndpoint;

// Tell raire that the totalAuditableBallots is double the number in the database
// for this contest.
GenerateAssertionsRequest request = new GenerateAssertionsRequest(simpleContest,
// Tell raire that the totalAuditableBallots is double the number in the database
// for this contest.
GenerateAssertionsRequest request = new GenerateAssertionsRequest(simpleContest,
10, 5, Arrays.stream(aliceChuanBob).toList());

// Request for the assertions to be generated.
ResponseEntity<GenerateAssertionsResponse> response
// Request for the assertions to be generated.
ResponseEntity<GenerateAssertionsResponse> response
= restTemplate.postForEntity(generateUrl, request, GenerateAssertionsResponse.class);

// Check that the response is successful and we got the right winner.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), "Alice");
// Check that the response is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(simpleContest, 10,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra
import static au.org.democracydevelopers.raireservice.testUtils.baseURL;
import static au.org.democracydevelopers.raireservice.testUtils.generateAssertionsEndpoint;
import static au.org.democracydevelopers.raireservice.testUtils.getAssertionsJSONEndpoint;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

import au.org.democracydevelopers.raire.RaireSolution;
import au.org.democracydevelopers.raireservice.NSWValues.Expected;
Expand Down Expand Up @@ -96,10 +94,11 @@ public void checkAllNSWByAPI() {
ResponseEntity<GenerateAssertionsResponse> response = restTemplate.postForEntity(generateUrl,
generateRequest, GenerateAssertionsResponse.class);

// Check that generation is successful and we got the right winner.
// Check that generation is successful, with no retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertEquals(response.getBody().winner(), expected.winner());
assertTrue(response.getBody().succeeded());
assertFalse(response.getBody().retry());

// Request the assertions
GetAssertionsRequest getRequest = new GetAssertionsRequest(expected.contestName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra

package au.org.democracydevelopers.raireservice.controller;

import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.TIED_WINNERS;
import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.TIMEOUT_CHECKING_WINNER;
import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.TIMEOUT_FINDING_ASSERTIONS;
import static au.org.democracydevelopers.raireservice.service.RaireServiceException.ERROR_CODE_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest;
import au.org.democracydevelopers.raireservice.response.GenerateAssertionsResponse;
import au.org.democracydevelopers.raireservice.testUtils;
import java.util.List;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -118,12 +114,14 @@ void tiedWinnersGivesTiedWinnersError() {
String generateUrl = baseURL + port + generateAssertionsEndpoint;

// Request for the assertions to be generated.
ResponseEntity<String> response = restTemplate.postForEntity(generateUrl, tiedWinnersRequest,
String.class);

// Check that generation is successful and we got the right winner.
assertTrue(response.getStatusCode().is5xxServerError());
assertEquals(TIED_WINNERS.toString(), response.getHeaders().getFirst(ERROR_CODE_KEY));
ResponseEntity<GenerateAssertionsResponse> response = restTemplate.postForEntity(generateUrl, tiedWinnersRequest,
GenerateAssertionsResponse.class);

// Check that assertion generation failed and did not recommend retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertFalse(response.getBody().succeeded());
assertFalse(response.getBody().retry());
}

/**
Expand All @@ -138,12 +136,14 @@ void twentyTiedWinnersGivesTimeOutCheckingWinnersError() {
String generateUrl = baseURL + port + generateAssertionsEndpoint;

// Request for the assertions to be generated.
ResponseEntity<String> response = restTemplate.postForEntity(generateUrl,
checkingWinnersTimeoutRequest, String.class);

// Check that generation is successful and we got the right winner.
assertTrue(response.getStatusCode().is5xxServerError());
assertEquals(TIMEOUT_CHECKING_WINNER.toString(), response.getHeaders().getFirst(ERROR_CODE_KEY));
ResponseEntity<GenerateAssertionsResponse> response = restTemplate.postForEntity(generateUrl,
checkingWinnersTimeoutRequest, GenerateAssertionsResponse.class);

// Check that assertion generation failed and *did* recommend retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertFalse(response.getBody().succeeded());
assertTrue(response.getBody().retry());
}

/**
Expand All @@ -156,11 +156,13 @@ void ByronWithShortTimeoutGivesTimeoutGeneratingAssertionsError() {
String generateUrl = baseURL + port + generateAssertionsEndpoint;

// Request for the assertions to be generated.
ResponseEntity<String> response = restTemplate.postForEntity(generateUrl,
ByronShortTimeoutRequest, String.class);

// Check that generation is successful and we got the right winner.
assertTrue(response.getStatusCode().is5xxServerError());
assertEquals(TIMEOUT_FINDING_ASSERTIONS.toString(), response.getHeaders().getFirst(ERROR_CODE_KEY));
ResponseEntity<GenerateAssertionsResponse> response = restTemplate.postForEntity(generateUrl,
ByronShortTimeoutRequest, GenerateAssertionsResponse.class);

// Check that assertion generation failed and *did* recommend retry.
assertTrue(response.getStatusCode().is2xxSuccessful());
assertNotNull(response.getBody());
assertFalse(response.getBody().succeeded());
assertTrue(response.getBody().retry());
}
}
Loading