diff --git a/src/main/java/au/org/democracydevelopers/raireservice/controller/AssertionController.java b/src/main/java/au/org/democracydevelopers/raireservice/controller/AssertionController.java index 63e4b1f..4bb9720 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/controller/AssertionController.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/controller/AssertionController.java @@ -20,21 +20,10 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.controller; -import au.org.democracydevelopers.raireservice.persistence.entity.Contest; import au.org.democracydevelopers.raireservice.persistence.repository.ContestRepository; import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.request.RequestValidationException; -import au.org.democracydevelopers.raireservice.response.GenerateAssertionsResponse; -import au.org.democracydevelopers.raireservice.response.GetAssertionsResponse; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -80,7 +69,7 @@ public ResponseEntity serve(@RequestBody GenerateAssertionsRequest reque request.Validate(contestRepository); // For the moment, this is just a dummy "OK" response. Later, it will contain the winner // as a ResponseEntity. - return new ResponseEntity("Placeholder winner", HttpStatus.OK); + return new ResponseEntity<>("Placeholder winner", HttpStatus.OK); } @@ -99,7 +88,7 @@ public ResponseEntity serve(@RequestBody GetAssertionsRequest request) throws RequestValidationException { request.Validate(contestRepository); // For the moment, this is just a dummy "OK" response. Later, it will contain the winner. - return new ResponseEntity("Placeholder assertions", HttpStatus.OK); + return new ResponseEntity<>("Placeholder assertions", HttpStatus.OK); } /** diff --git a/src/main/java/au/org/democracydevelopers/raireservice/request/ContestRequest.java b/src/main/java/au/org/democracydevelopers/raireservice/request/ContestRequest.java index d1bbe7b..57f30d6 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/request/ContestRequest.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/request/ContestRequest.java @@ -21,7 +21,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.request; import au.org.democracydevelopers.raireservice.persistence.repository.ContestRepository; -import io.swagger.v3.oas.annotations.Hidden; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,14 +66,6 @@ public ContestRequest(String contestName, List candidates) { this.candidates = candidates; } - /** - * Return the contest Name. - * @return the name of the contest. - */ - public String getContestName() { - return contestName; - } - /** * Set the contest name. Used for deserialization. * @param contestName the name of the contest. diff --git a/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java b/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java index 50f41da..57d065c 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java @@ -69,7 +69,6 @@ public void setTimeLimitSeconds(float timeLimitSeconds) { this.timeLimitSeconds = timeLimitSeconds; } - /** * No args constructor. Used for serialization. */ diff --git a/src/main/java/au/org/democracydevelopers/raireservice/request/RequestValidationExceptionHandler.java b/src/main/java/au/org/democracydevelopers/raireservice/request/RequestValidationExceptionHandler.java index 84bc9a6..dd4e222 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/request/RequestValidationExceptionHandler.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/request/RequestValidationExceptionHandler.java @@ -20,12 +20,10 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.request; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; /** diff --git a/src/main/java/au/org/democracydevelopers/raireservice/response/ContestResponse.java b/src/main/java/au/org/democracydevelopers/raireservice/response/ContestResponse.java deleted file mode 100644 index f555187..0000000 --- a/src/main/java/au/org/democracydevelopers/raireservice/response/ContestResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2024 Democracy Developers - -The Raire Service is designed to connect colorado-rla and its associated database to -the raire assertion generation engine (https://github.com/DemocracyDevelopers/raire-java). - -This file is part of raire-service. - -raire-service is free software: you can redistribute it and/or modify it under the terms -of the GNU Affero General Public License as published by the Free Software Foundation, either -version 3 of the License, or (at your option) any later version. - -raire-service is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License along with -raire-service. If not, see . -*/ - -package au.org.democracydevelopers.raireservice.response; - -/** - * An abstract class designed to deal with all the errors resulting from failures associated - * with generic contest-name-based requests, i.e. those of a ContestRequest. Examples include - * invalid or blank contest names, invalid or blank candidates, non-existent contests and non-IRV - * contests. - * The non-error responses are dealt with by each appropriate subclass. - */ -public abstract class ContestResponse { - - protected String contestName; - private Exception e; - - public ContestResponse(String contestName) { - this.contestName = contestName; - } -} diff --git a/src/main/java/au/org/democracydevelopers/raireservice/response/GenerateAssertionsResponse.java b/src/main/java/au/org/democracydevelopers/raireservice/response/GenerateAssertionsResponse.java deleted file mode 100644 index d9efe33..0000000 --- a/src/main/java/au/org/democracydevelopers/raireservice/response/GenerateAssertionsResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2024 Democracy Developers - -The Raire Service is designed to connect colorado-rla and its associated database to -the raire assertion generation engine (https://github.com/DemocracyDevelopers/raire-java). - -This file is part of raire-service. - -raire-service is free software: you can redistribute it and/or modify it under the terms -of the GNU Affero General Public License as published by the Free Software Foundation, either -version 3 of the License, or (at your option) any later version. - -raire-service is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License along with -raire-service. If not, see . -*/ - -package au.org.democracydevelopers.raireservice.response; - -public class GenerateAssertionsResponse extends ContestResponse { - - private String winner; - - /** - * Constructor for the happy case in which there is a winner found. - */ - public GenerateAssertionsResponse(String contestName, String winner) { - super(contestName); - this.winner = winner; - } -} diff --git a/src/main/java/au/org/democracydevelopers/raireservice/response/GetAssertionsResponse.java b/src/main/java/au/org/democracydevelopers/raireservice/response/GetAssertionsResponse.java deleted file mode 100644 index ca23c6a..0000000 --- a/src/main/java/au/org/democracydevelopers/raireservice/response/GetAssertionsResponse.java +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2024 Democracy Developers - -The Raire Service is designed to connect colorado-rla and its associated database to -the raire assertion generation engine (https://github.com/DemocracyDevelopers/raire-java). - -This file is part of raire-service. - -raire-service is free software: you can redistribute it and/or modify it under the terms -of the GNU Affero General Public License as published by the Free Software Foundation, either -version 3 of the License, or (at your option) any later version. - -raire-service is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -See the GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License along with -raire-service. If not, see . -*/ - -package au.org.democracydevelopers.raireservice.response; - -public class GetAssertionsResponse extends ContestResponse { - - private String assertions; - - public GetAssertionsResponse(String contestName, String assertions) { - super(contestName); - this.assertions = assertions; - } -} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java index 7df0f61..deb35c6 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java @@ -24,9 +24,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import static org.junit.jupiter.api.Assertions.assertTrue; import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; -import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import com.google.gson.Gson; -import java.math.BigDecimal; import java.util.List; import java.util.Objects; import org.junit.jupiter.api.BeforeAll; @@ -312,4 +310,125 @@ public void generateAssertionsWithWhiteSpaceCandidateNameIsAnError() { assertTrue(response.getStatusCode().is4xxClientError()); assertTrue(containsIgnoreCase(response.getBody(), "Bad candidate list")); } + + /** + * The generateAssertions endpoint, called with null/missing total auditable ballots, returns a + * meaningful error. + */ + @Test + public void generateAssertionsWithNullAuditableBallotsIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + String requestAsJson = + "{\"timeLimitSeconds\":10.0,\"contestName\":\"Ballina Mayoral\",\"candidates\":[\"Alice\",\"Bob\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive total auditable ballots")); + } + + /** + * The generateAssertions endpoint, called with zero total auditable ballots, returns a meaningful error. + */ + @Test + public void generateAssertionsWithZeroAuditableBallotsIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + GenerateAssertionsRequest generateAssertions = new GenerateAssertionsRequest( + ballina, + 0, + 5, + List.of("Alice", "Bob") + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(generateAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive total auditable ballots")); + } + + /** + * The generateAssertions endpoint, called with negative total auditable ballots, returns a meaningful error. + */ + @Test + public void generateAssertionsWithNegativeAuditableBallotsIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + GenerateAssertionsRequest generateAssertions = new GenerateAssertionsRequest( + ballina, + -10, + 5, + List.of("Alice", "Bob") + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(generateAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive total auditable ballots")); + } + + + + /** + * The generateAssertions endpoint, called with null/missing time limit, returns a meaningful error. + */ + @Test + public void generateAssertionsWithNullTimeLimitIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + String requestAsJson = + "{\"totalAuditableBallots\":100,\"contestName\":\"Ballina Mayoral\",\"candidates\":[\"Alice\",\"Bob\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive time limit")); + } + + /** + * The generateAssertions endpoint, called with zero time limit, returns a meaningful error. + */ + @Test + public void generateAssertionsWithZeroTimeLimitIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + GenerateAssertionsRequest generateAssertions = new GenerateAssertionsRequest( + ballina, + 100, + 0, + List.of("Alice", "Bob") + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(generateAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive time limit")); + } + + /** + * The generateAssertions endpoint, called with negative time limit, returns a meaningful error. + */ + @Test + public void generateAssertionsWithNegativeTimeLimitIsAnError() { + String url = baseURL + port + generateAssertionsEndpoint; + + GenerateAssertionsRequest generateAssertions = new GenerateAssertionsRequest( + ballina, + 100, + -5, + List.of("Alice", "Bob") + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(generateAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(),"Non-positive time limit")); + } } \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPITests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPITests.java index 331adf1..ff0e0d0 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPITests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPITests.java @@ -23,7 +23,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import static au.org.democracydevelopers.raireservice.util.StringUtils.containsIgnoreCase; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import com.google.gson.Gson; import java.math.BigDecimal; @@ -304,4 +303,45 @@ public void getAssertionsWithWhitespaceCandidateNameIsAnError() { assertTrue(response.getStatusCode().is4xxClientError()); assertTrue(containsIgnoreCase(response.getBody(), "Bad candidate list")); } -} \ No newline at end of file + + /** + * The getAssertions endpoint, called with a null risk limit, returns a meaningful error. + */ + @Test + public void getAssertionsWithNullRiskLimitIsAnError() { + String url = baseURL + port + getAssertionsEndpoint; + + GetAssertionsRequest getAssertions = new GetAssertionsRequest( + ballina, + List.of("Alice", "Bob"), + null + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(getAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(), "Null or negative risk limit")); + } + + /** + * The getAssertions endpoint, called with a negative risk limit, returns a meaningful error. + * (Note that a value >=1 is vacuously met but not invalid.) + */ + @Test + public void getAssertionsWithNegativeRiskLimitIsAnError() { + String url = baseURL + port + getAssertionsEndpoint; + + GetAssertionsRequest getAssertions = new GetAssertionsRequest( + ballina, + List.of("Alice", "Bob"), + BigDecimal.valueOf(-0.03) + ); + + HttpEntity request = new HttpEntity<>(gson.toJson(getAssertions), httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is4xxClientError()); + assertTrue(containsIgnoreCase(response.getBody(), "Null or negative risk limit")); + } +}