diff --git a/pom.xml b/pom.xml index 59c41d5a..23bb13bc 100644 --- a/pom.xml +++ b/pom.xml @@ -32,31 +32,16 @@ spring-boot-starter-data-jpa 3.1.3 - - org.springframework.boot - spring-boot-starter-validation - 3.1.3 - org.postgresql postgresql 42.6.1 - - org.springdoc - springdoc-openapi-ui - 1.7.0 - org.springframework.boot spring-boot-starter-test test - - io.rest-assured - rest-assured - test - com.google.code.gson gson diff --git a/src/main/java/au/org/democracydevelopers/raireservice/config/RaireConfig.java b/src/main/java/au/org/democracydevelopers/raireservice/config/RaireConfig.java deleted file mode 100644 index 83b26fb2..00000000 --- a/src/main/java/au/org/democracydevelopers/raireservice/config/RaireConfig.java +++ /dev/null @@ -1,40 +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.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.client.RestTemplate; - -@Configuration -public class RaireConfig { - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } - - @Bean - public ObjectMapper objectMapper() { - return new ObjectMapper(); - } - -} diff --git a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/Assertion.java b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/Assertion.java index a3734fd4..5dab8d5b 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/Assertion.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/Assertion.java @@ -23,6 +23,8 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import static au.org.democracydevelopers.raireservice.util.CSVUtils.escapeThenJoin; import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; +import au.org.democracydevelopers.raireservice.service.RaireServiceException; +import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.ArrayList; @@ -190,7 +192,7 @@ public abstract class Assertion { */ @Column(name = "current_risk", updatable = false, nullable = false) @ReadOnlyProperty - protected BigDecimal currentRisk = new BigDecimal("1.00"); + protected BigDecimal currentRisk = BigDecimal.valueOf(1); /** * Default no-args constructor (required for persistence). @@ -387,7 +389,7 @@ public BigDecimal getCurrentRisk() { * data stored in the assertion. */ public abstract AssertionAndDifficulty convert(List candidates) - throws IllegalArgumentException; + throws RaireServiceException; /** * Return as a list of strings intended for a CSV row, in the same order as the csvHeaders in @@ -397,26 +399,38 @@ public abstract AssertionAndDifficulty convert(List candidates) * Floating-point numbers are formatted to 4 d.p, except the (BigDecimal) current risk, which is * given to its full precision. * @return The assertion data, as a list of csv-escaped strings. - */ - public List asCSVRow() { - var fm = new DecimalFormat("0.0###"); - return List.of( - getAssertionType(), - winner, - loser, - escapeThenJoin(assumedContinuing), - fm.format(difficulty), - margin+"", - fm.format(dilutedMargin), - currentRisk.toString(), - estimatedSamplesToAudit+"", - optimisticSamplesToAudit+"", - twoVoteOverCount+"", - oneVoteOverCount+"", - otherCount+"", - oneVoteUnderCount+"", - twoVoteUnderCount+"" - ); + * @throws RaireServiceException with error code WRONG_CANDIDATE_NAMES if the winner, loser or any of + * the assumed_continuing candidates are not in the input candidate list. + */ + public List asCSVRow(List candidates) throws RaireServiceException { + final String prefix = "[asCSVRow]"; + final DecimalFormat fm = new DecimalFormat("0.0###"); + + if(candidates.contains(winner) && candidates.contains(loser) + && candidates.containsAll(assumedContinuing) ) { + return List.of( + getAssertionType(), + winner, + loser, + escapeThenJoin(assumedContinuing), + fm.format(difficulty), + margin + "", + fm.format(dilutedMargin), + currentRisk.toString(), + estimatedSamplesToAudit + "", + optimisticSamplesToAudit + "", + twoVoteOverCount + "", + oneVoteOverCount + "", + otherCount + "", + oneVoteUnderCount + "", + twoVoteUnderCount + "" + ); + } else { + final String msg = String.format("%s Candidate list provided as parameter is inconsistent " + + "with assertion (winner or loser or some continuing candidate not present).", prefix); + logger.error(msg); + throw new RaireServiceException(msg, RaireErrorCode.WRONG_CANDIDATE_NAMES); + } } /** diff --git a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NEBAssertion.java b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NEBAssertion.java index c7e6956c..647d01ab 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NEBAssertion.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NEBAssertion.java @@ -23,6 +23,8 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; import au.org.democracydevelopers.raire.assertions.NotEliminatedBefore; import au.org.democracydevelopers.raireservice.service.Metadata; +import au.org.democracydevelopers.raireservice.service.RaireServiceException; +import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import java.util.ArrayList; @@ -85,7 +87,7 @@ public NEBAssertion(String contestName, long universeSize, int margin, double di /** * {@inheritDoc} */ - public AssertionAndDifficulty convert(List candidates) throws IllegalArgumentException { + public AssertionAndDifficulty convert(List candidates) throws RaireServiceException { final String prefix = "[convert]"; logger.debug(String.format("%s Constructing a raire-java AssertionAndDifficulty for the " + @@ -107,7 +109,7 @@ public AssertionAndDifficulty convert(List candidates) throws IllegalArg final String msg = String.format("%s Candidate list provided as parameter is inconsistent " + "with assertion (winner or loser not present).", prefix); logger.error(msg); - throw new IllegalArgumentException(msg); + throw new RaireServiceException(msg, RaireErrorCode.WRONG_CANDIDATE_NAMES); } } diff --git a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NENAssertion.java b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NENAssertion.java index b96d8273..6d05fe69 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NENAssertion.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/persistence/entity/NENAssertion.java @@ -23,6 +23,8 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; import au.org.democracydevelopers.raire.assertions.NotEliminatedNext; import au.org.democracydevelopers.raireservice.service.Metadata; +import au.org.democracydevelopers.raireservice.service.RaireServiceException; +import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @@ -93,7 +95,7 @@ public NENAssertion(String contestName, long universeSize, int margin, double di /** * {@inheritDoc} */ - public AssertionAndDifficulty convert(List candidates) throws IllegalArgumentException { + public AssertionAndDifficulty convert(List candidates) throws RaireServiceException { final String prefix = "[convert]"; logger.debug(String.format("%s Constructing a raire-java AssertionAndDifficulty for the " + @@ -119,7 +121,8 @@ public AssertionAndDifficulty convert(List candidates) throws IllegalArg else{ final String msg = String.format("%s Candidate list provided as parameter is inconsistent " + "with assertion (winner or loser or some continuing candidate not present).", prefix); - throw new IllegalArgumentException(msg); + logger.error(msg); + throw new RaireServiceException(msg, RaireErrorCode.WRONG_CANDIDATE_NAMES); } } 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 9b135f98..2f395938 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/request/GenerateAssertionsRequest.java @@ -53,7 +53,7 @@ public class GenerateAssertionsRequest extends ContestRequest { /** * The elapsed time allowed to raire to generate the assertions, in seconds. */ - public final float timeLimitSeconds; + public final double timeLimitSeconds; /** * All args constructor. @@ -64,7 +64,7 @@ public class GenerateAssertionsRequest extends ContestRequest { */ @ConstructorProperties({"contestName", "totalAuditableBallots", "timeLimitSeconds","candidates"}) public GenerateAssertionsRequest(String contestName, int totalAuditableBallots, - float timeLimitSeconds, List candidates) { + double timeLimitSeconds, List candidates) { super(contestName, candidates); this.totalAuditableBallots = totalAuditableBallots; this.timeLimitSeconds = timeLimitSeconds; diff --git a/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCsvService.java b/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCsvService.java index a5d75629..88f37926 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCsvService.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCsvService.java @@ -99,7 +99,7 @@ public String generateCSV(GetAssertionsRequest request) throws RaireServiceExcep String preface = makePreface(request); String extrema = findExtrema(sortedAssertions); String headers = escapeThenJoin(csvHeaders); - String contents = makeContents(sortedAssertions); + String contents = makeContents(sortedAssertions, request.candidates); logger.debug(String.format("%s %d assertions translated to csv.", prefix, assertions.size())); return preface + extrema + "\n\n" + headers + "\n" + contents; @@ -266,14 +266,17 @@ private String makePreface(GetAssertionsRequest request) { * number (not related to the database's index) that begins at 1 and increments by 1 with each row. * @param assertions a list of assertions * @return their concatenated csv rows. + * @throws RaireServiceException if the candidate names in any of the assertions are inconsistent + * with the request's candidate list. */ - private String makeContents(List assertions) { + private String makeContents(List assertions, List candidates) + throws RaireServiceException { int index = 1; List rows = new ArrayList<>(); for (Assertion assertion : assertions) { - rows.add(index++ + "," + escapeThenJoin(assertion.asCSVRow())); + rows.add(index++ + "," + escapeThenJoin(assertion.asCSVRow(candidates))); } return String.join("\n", rows) + "\n"; diff --git a/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonService.java b/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonService.java index 52ea7cef..7b2334d9 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonService.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonService.java @@ -29,6 +29,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -91,8 +92,10 @@ public RaireSolution getRaireSolution(GetAssertionsRequest request) // keeping track of the maximum difficulty and minimum margin. logger.debug(String.format("%s Converting %d assertions into raire-java format.", prefix, assertions.size())); - List translated = assertions.stream().map( - a -> a.convert(request.candidates)).toList(); + List translated = new ArrayList<>(); + for(Assertion a : assertions) { + translated.add(a.convert(request.candidates)); + } logger.debug(String.format("%s %d assertions translated to json.", prefix, assertions.size())); diff --git a/src/main/java/au/org/democracydevelopers/raireservice/service/RaireServiceException.java b/src/main/java/au/org/democracydevelopers/raireservice/service/RaireServiceException.java index 3077c9c5..190116b5 100644 --- a/src/main/java/au/org/democracydevelopers/raireservice/service/RaireServiceException.java +++ b/src/main/java/au/org/democracydevelopers/raireservice/service/RaireServiceException.java @@ -43,6 +43,11 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra */ public class RaireServiceException extends Exception { + /** + * The string "error_code" - used for retrieving it from json etc. + */ + 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. diff --git a/src/test/java/au/org/democracydevelopers/raireservice/NSWValues.java b/src/test/java/au/org/democracydevelopers/raireservice/NSWValues.java new file mode 100644 index 00000000..49fb0a89 --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/NSWValues.java @@ -0,0 +1,351 @@ +/* +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; + +import java.util.Arrays; +import java.util.List; + +/** + * This class summarises the contest names, candidate choices, expected winners and estimated + * difficulties for all NSW 2021 IRV contests. + * Difficulties are taken from raire-java::src/test/java/au/org/democracydevelopers/raire/TestNSW + * which in turn tests against raire-rs. + * Winners are taken from the New South Wales official election results at + * https://pastvtr.elections.nsw.gov.au/LG2101/status/mayoral + * The ballotCounts are derived from the data, but double-checked for exact match with the + * NSWEC website. + * + */ +public class NSWValues { + + // Contest Eurobodalla Mayoral + public static final String nameContest_1 = "Eurobodalla Mayoral"; + public static final List choicesContest_1 = List.of("WORTHINGTON Alison","GRACE David", + "SMITH Gary","HATCHER Mat","HARRISON N (Tubby)","POLLOCK Rob","STARMER Karyn"); + public static final int BallotCount_1 = 25526; + public static final double difficulty_1 = 23.079566003616637; + public static final String winnerContest_1 = "HATCHER Mat"; + + // Contest City of Lake Macquarie Mayoral + public static final String nameContest_2 = "City of Lake Macquarie Mayoral"; + public static final List choicesContest_2 = List.of("FRASER Kay","DAWSON Rosmairi", + "CUBIS Luke","PAULING Jason"); + public static final int BallotCount_2 = 130336; + public static final double difficulty_2 = 3.1113869658629745; + public static final String winnerContest_2 = "FRASER Kay"; + + // Contest City of Coffs Harbour Mayoral + public static final String nameContest_3 = "City of Coffs Harbour Mayoral"; + public static final List choicesContest_3 = List.of("SWAN Tegan","CECATO George", + "ADENDORFF Michael","JUDGE Tony","PRYCE Rodger","PIKE Donna","AMOS Paul","TOWNLEY Sally", + "ARKAN John","CASSELL Jonathan"); + public static final int BallotCount_3 = 45155; + public static final double difficulty_3 = 8.571564160971906; + public static final String winnerContest_3 = "AMOS Paul"; + + // Contest Singleton Mayoral + public static final String nameContest_4 = "Singleton Mayoral"; + public static final List choicesContest_4 = List.of("MOORE Sue","THOMPSON Danny", + "JARRETT Tony","CHARLTON Belinda"); + public static final int BallotCount_4 = 13755; + public static final double difficulty_4 = 12.118942731277533; + public static final String winnerContest_4 = "MOORE Sue"; + + // Contest City of Newcastle Mayoral + public static final String nameContest_5 = "City of Newcastle Mayoral"; + public static final List choicesContest_5 = List.of("CHURCH John","NELMES Nuatali", + "HOLDING Rod","MACKENZIE John","O'BRIEN Steve","BARRIE Jenny"); + public static final int BallotCount_5 = 100275; + public static final double difficulty_5 = 5.913487055493307; + public static final String winnerContest_5 = "NELMES Nuatali"; + + // Contest Nambucca Valley Mayoral + public static final String nameContest_6 = "Nambucca Valley Mayoral"; + public static final List choicesContest_6 = List.of("JENVEY Susan","HOBAN Rhonda"); + public static final int BallotCount_6 = 12482; + public static final double difficulty_6 = 2.7360806663743973; + public static final String winnerContest_6 = "HOBAN Rhonda"; + + // Contest City of Maitland Mayoral + public static final String nameContest_7 = "City of Maitland Mayoral"; + public static final List choicesContest_7 = List.of("BROWN John","MITCHELL Ben", + "BAKER Loretta","PENFOLD Philip","SAFFARI Shahriar (Sean)","COOPER Michael","BURKE Brian"); + public static final int BallotCount_7 = 54181; + public static final double difficulty_7 = 47.072980017376196; + public static final String winnerContest_7 = "PENFOLD Philip"; + + // Contest Kempsey Mayoral + public static final String nameContest_8 = "Kempsey Mayoral"; + public static final List choicesContest_8 = List.of("HAUVILLE Leo","EVANS Andrew", + "BAIN Arthur","CAMPBELL Liz","SAUL Dean","IRWIN Troy","RAEBURN Bruce"); + public static final int BallotCount_8 = 17585; + public static final double difficulty_8 = 45.43927648578811; + public static final String winnerContest_8 = "HAUVILLE Leo"; + + // Contest Canada Bay Mayoral + public static final String nameContest_9 = "Canada Bay Mayoral"; + public static final List choicesContest_9 = List.of("TSIREKAS Angelo","LITTLE Julia", + "MEGNA Michael","JAGO Charles","RAMONDINO Daniela"); + public static final int BallotCount_9 = 48542; + public static final double difficulty_9 = 8.140533288613113; + public static final String winnerContest_9 = "TSIREKAS Angelo"; + + // Contest Richmond Valley Mayoral + public static final String nameContest_10 = "Richmond Valley Mayoral"; + public static final List choicesContest_10 = List.of("MUSTOW Robert","HAYES Robert"); + public static final int BallotCount_10 = 13405; + public static final double difficulty_10 = 2.302868922865487; + public static final String winnerContest_10 = "MUSTOW Robert"; + + // Contest City of Sydney Mayoral + public static final String nameContest_11 = "City of Sydney Mayoral"; + public static final List choicesContest_11 = List.of("VITHOULKAS Angela", + "WELDON Yvonne","SCOTT Linda","JARRETT Shauna","ELLSMORE Sylvie","MOORE Clover"); + public static final int BallotCount_11 = 118511; + public static final double difficulty_11 = 3.6873366521468576; + public static final String winnerContest_11 = "MOORE Clover"; + + // Contest Byron Mayoral + public static final String nameContest_12 = "Byron Mayoral"; + public static final List choicesContest_12 = List.of("HUNTER Alan","CLARKE Bruce", + "COOREY Cate","ANDERSON John","MCILRATH Christopher","LYON Michael","DEY Duncan", + "PUGH Asren","SWIVEL Mark"); + public static final int BallotCount_12 = 18165; + public static final double difficulty_12 = 17.13679245283019; + public static final String winnerContest_12 = "LYON Michael"; + + // Contest City of Broken Hill Mayoral + public static final String nameContest_13 = "City of Broken Hill Mayoral"; + public static final List choicesContest_13 = List.of("TURLEY Darriea","KENNEDY Tom", + "GALLAGHER Dave"); + public static final int BallotCount_13 = 10812; + public static final double difficulty_13 = 3.2773567747802366; + public static final String winnerContest_13 = "KENNEDY Tom"; + + // Contest City of Shellharbour Mayoral + public static final String nameContest_14 = "City of Shellharbour Mayoral"; + public static final List choicesContest_14 = List.of("HOMER Chris","SALIBA Marianne"); + public static final int BallotCount_14 = 46273; + public static final double difficulty_14 = 17.83159922928709; + public static final String winnerContest_14 = "HOMER Chris"; + + // Contest City of Shoalhaven Mayoral + public static final String nameContest_15 = "City of Shoalhaven Mayoral"; + public static final List choicesContest_15 = List.of("GREEN Paul","KITCHENER Mark", + "WHITE Patricia","WATSON Greg","DIGIGLIO Nina","FINDLEY Amanda"); + public static final int BallotCount_15 = 67030; + public static final double difficulty_15 = 41.53035935563817; + public static final String winnerContest_15 = "FINDLEY Amanda"; + + // Contest Mosman Mayoral + public static final String nameContest_16 = "Mosman Mayoral"; + public static final List choicesContest_16 = List.of("MOLINE Libby","BENDALL Roy", + "HARDING Sarah","CORRIGAN Carolyn","MENZIES Simon"); + public static final int BallotCount_16 = 16425; + public static final double difficulty_16 = 4.498767460969598; + public static final String winnerContest_16 = "CORRIGAN Carolyn"; + + // Contest City of Orange Mayoral + public static final String nameContest_17 = "City of Orange Mayoral"; + public static final List choicesContest_17 = List.of("HAMLING Jason","SPALDING Amanda", + "JONES Neil","WHITTON Jeffery","DUFFY Kevin","SMITH Lesley","MILETO Tony"); + public static final int BallotCount_17 = 24355; + public static final double difficulty_17 = 50.01026694045174; + public static final String winnerContest_17 = "HAMLING Jason"; + + // Contest City of Wollongong Mayoral + public static final String nameContest_18 = "City of Wollongong Mayoral"; + public static final List choicesContest_18 = List.of("GLYKIS Marie","DORAHY John", + "BROWN Tania","BRADBERY Gordon","ANTHONY Andrew","COX Mithra"); + public static final int BallotCount_18 = 127240; + public static final double difficulty_18 = 47.72693173293323; + public static final String winnerContest_18 = "BRADBERY Gordon"; + + // Contest Port Stephens Mayoral + public static final String nameContest_19 = "Port Stephens Mayoral"; + public static final List choicesContest_19 = List.of("ANDERSON Leah","PALMER Ryan"); + public static final int BallotCount_19 = 47807; + public static final double difficulty_19 = 84.31569664902999; + public static final String winnerContest_19 = "PALMER Ryan"; + + // Contest Wollondilly Mayoral + public static final String nameContest_20 = "Wollondilly Mayoral"; + public static final List choicesContest_20 = List.of("KHAN Robert","BANASIK Michael", + "DEETH Matthew","LAW Ray","GOULD Matt","HANNAN Judy"); + public static final int BallotCount_20 = 31355; + public static final double difficulty_20 = 24.40077821011673; + public static final String winnerContest_20 = "GOULD Matt"; + + // Contest Hornsby Mayoral + public static final String nameContest_21 = "Hornsby Mayoral"; + public static final List choicesContest_21 = List.of("HEYDE Emma","RUDDOCK Philip"); + public static final int BallotCount_21 = 85656; + public static final double difficulty_21 = 6.866762866762866; + public static final String winnerContest_21 = "RUDDOCK Philip"; + + // Contest Ballina Mayoral + public static final String nameContest_22 = "Ballina Mayoral"; + public static final List choicesContest_22 = List.of("WILLIAMS Keith","JOHNSON Jeff", + "MCCARTHY Steve","JOHNSTON Eoin","CADWALLADER Sharon"); + public static final int BallotCount_22 = 26913; + public static final double difficulty_22 = 7.285598267460747; + public static final String winnerContest_22 = "CADWALLADER Sharon"; + + // Contest Bellingen Mayoral + public static final String nameContest_23 = "Bellingen Mayoral"; + public static final List choicesContest_23 = List.of("ALLAN Steve","WOODWARD Andrew", + "KING Dominic"); + public static final int BallotCount_23 = 8374; + public static final double difficulty_23 = 3.3335987261146496; + public static final String winnerContest_23 = "ALLAN Steve"; + + // Contest City of Lismore Mayoral + public static final String nameContest_24 = "City of Lismore Mayoral"; + public static final List choicesContest_24 = List.of("KRIEG Steve","COOK Darlene", + "HEALEY Patrick","GRINDON-EKINS Vanessa","ROB Big","BIRD Elly"); + public static final int BallotCount_24 = 26474; + public static final double difficulty_24 = 2.929836210712705; + public static final String winnerContest_24 = "KRIEG Steve"; + + // Contest City of Willoughby Mayoral + public static final String nameContest_25 = "City of Willoughby Mayoral"; + public static final List choicesContest_25 = List.of("ROZOS Angelo","CAMPBELL Craig", + "TAYLOR Tanya"); + public static final int BallotCount_25 = 37942; + public static final double difficulty_25 = 14.990912682734097; + public static final String winnerContest_25 = "TAYLOR Tanya"; + + // Contest The Hills Shire Mayoral + public static final String nameContest_26 = "The Hills Shire Mayoral"; + public static final List choicesContest_26 = List.of("SHAHAMAT Vida","GANGEMI Peter", + "ROZYCKI Jerzy (George)","TRACEY Ryan","YAZDANI Ereboni (Alexia)"); + public static final int BallotCount_26 = 105384; + public static final double difficulty_26 = 3.6801229221958374; + public static final String winnerContest_26 = "GANGEMI Peter"; + + // Contest City of Cessnock Mayoral + public static final String nameContest_27 = "City of Cessnock Mayoral"; + public static final List choicesContest_27 = List.of("MURRAY Janet","SUVAAL Jay", + "MOORES John","OLSEN Ian"); + public static final int BallotCount_27 = 36497; + public static final double difficulty_27 = 6.466513111268604; + public static final String winnerContest_27 = "SUVAAL Jay"; + + // Contest City of Griffith Mayoral + public static final String nameContest_28 = "City of Griffith Mayoral"; + public static final List choicesContest_28 = List.of("MERCURI Rina","NAPOLI Anne", + "ZAPPACOSTA Dino","LA ROCCA Mariacarmina (Carmel)","CURRAN Doug"); + public static final int BallotCount_28 = 14179; + public static final double difficulty_28 = 3.9320576816417083; + public static final String winnerContest_28 = "CURRAN Doug"; + + // Contest Port Macquarie-Hastings Mayoral + public static final String nameContest_29 = "Port Macquarie-Hastings Mayoral"; + public static final List choicesContest_29 = List.of("PINSON Peta","GATES Steven", + "SHEPPARD Rachel","INTEMANN Lisa","LIPOVAC Nik"); + public static final int BallotCount_29 = 54499; + public static final double difficulty_29 = 2.8524547262640008; + public static final String winnerContest_29 = "PINSON Peta"; + + // Contest City of Liverpool Mayoral + public static final String nameContest_30 = "City of Liverpool Mayoral"; + public static final List choicesContest_30 = List.of("HAGARTY Nathan","MORSHED Asm", + "ANDJELKOVIC Milomir (Michael)","HARLE Peter","MANNOUN Ned"); + public static final int BallotCount_30 = 115177; + public static final double difficulty_30 = 45.416798107255524; + public static final String winnerContest_30 = "MANNOUN Ned"; + + // Contest Uralla Mayoral + public static final String nameContest_31 = "Uralla Mayoral"; + public static final List choicesContest_31 = List.of("BELL Robert","LEDGER Natasha", + "STRUTT Isabel"); + public static final int BallotCount_31 = 3781; + public static final double difficulty_31 = 1.6297413793103448; + public static final String winnerContest_31 = "BELL Robert"; + + // Contest Hunter's Hill Mayoral + public static final String nameContest_32 = "Hunter's Hill Mayoral"; + public static final List choicesContest_32 = List.of("GUAZZAROTTO David","MILES Zac", + "QUINN Richard","WILLIAMS Ross"); + public static final int BallotCount_32 = 8356; + public static final double difficulty_32 = 38.330275229357795; + public static final String winnerContest_32 = "MILES Zac"; + + // Contest Burwood Mayoral + public static final String nameContest_33 = "Burwood Mayoral"; + public static final List choicesContest_33 = List.of("HULL David","MURRAY Alan", + "CUTCHER Ned","FAKER John"); + public static final int BallotCount_33 = 17797; + public static final double difficulty_33 = 2.5269061479483175; + public static final String winnerContest_33 = "FAKER John"; + + public static List expectedSolutionData = Arrays.asList( + new Expected(nameContest_1, choicesContest_1, BallotCount_1, difficulty_1, winnerContest_1), + new Expected(nameContest_2, choicesContest_2, BallotCount_2, difficulty_2, winnerContest_2), + new Expected(nameContest_3, choicesContest_3, BallotCount_3, difficulty_3, winnerContest_3), + new Expected(nameContest_4, choicesContest_4, BallotCount_4, difficulty_4, winnerContest_4), + new Expected(nameContest_5, choicesContest_5, BallotCount_5, difficulty_5, winnerContest_5), + new Expected(nameContest_6, choicesContest_6, BallotCount_6, difficulty_6, winnerContest_6), + new Expected(nameContest_7, choicesContest_7, BallotCount_7, difficulty_7, winnerContest_7), + new Expected(nameContest_8, choicesContest_8, BallotCount_8, difficulty_8, winnerContest_8), + new Expected(nameContest_9, choicesContest_9, BallotCount_9, difficulty_9, winnerContest_9), + new Expected(nameContest_10, choicesContest_10, BallotCount_10, difficulty_10, winnerContest_10), + new Expected(nameContest_11, choicesContest_11, BallotCount_11, difficulty_11, winnerContest_11), + new Expected(nameContest_12, choicesContest_12, BallotCount_12, difficulty_12, winnerContest_12), + new Expected(nameContest_13, choicesContest_13, BallotCount_13, difficulty_13, winnerContest_13), + new Expected(nameContest_14, choicesContest_14, BallotCount_14, difficulty_14, winnerContest_14), + new Expected(nameContest_15, choicesContest_15, BallotCount_15, difficulty_15, winnerContest_15), + new Expected(nameContest_16, choicesContest_16, BallotCount_16, difficulty_16, winnerContest_16), + new Expected(nameContest_17, choicesContest_17, BallotCount_17, difficulty_17, winnerContest_17), + new Expected(nameContest_18, choicesContest_18, BallotCount_18, difficulty_18, winnerContest_18), + new Expected(nameContest_19, choicesContest_19, BallotCount_19, difficulty_19, winnerContest_19), + new Expected(nameContest_20, choicesContest_20, BallotCount_20, difficulty_20, winnerContest_20), + new Expected(nameContest_21, choicesContest_21, BallotCount_21, difficulty_21, winnerContest_21), + new Expected(nameContest_22, choicesContest_22, BallotCount_22, difficulty_22, winnerContest_22), + new Expected(nameContest_23, choicesContest_23, BallotCount_23, difficulty_23, winnerContest_23), + new Expected(nameContest_24, choicesContest_24, BallotCount_24, difficulty_24, winnerContest_24), + new Expected(nameContest_25, choicesContest_25, BallotCount_25, difficulty_25, winnerContest_25), + new Expected(nameContest_26, choicesContest_26, BallotCount_26, difficulty_26, winnerContest_26), + new Expected(nameContest_27, choicesContest_27, BallotCount_27, difficulty_27, winnerContest_27), + new Expected(nameContest_28, choicesContest_28, BallotCount_28, difficulty_28, winnerContest_28), + new Expected(nameContest_29, choicesContest_29, BallotCount_29, difficulty_29, winnerContest_29), + new Expected(nameContest_30, choicesContest_30, BallotCount_30, difficulty_30, winnerContest_30), + new Expected(nameContest_31, choicesContest_31, BallotCount_31, difficulty_31, winnerContest_31), + new Expected(nameContest_32, choicesContest_32, BallotCount_32, difficulty_32, winnerContest_32), + new Expected(nameContest_33, choicesContest_33, BallotCount_33, difficulty_33, winnerContest_33) + ); + + /** + * Collected data about a contest that raire has solved + * @param contestName the name of the contest + * @param choices the candidate names + * @param ballotCount the total auditable ballots + * @param difficulty the expected difficulty + * @param winner the expected winner + */ + public record Expected ( + String contestName, + List choices, + int ballotCount, + double difficulty, + String winner + ) {} +} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIErrorTests.java similarity index 93% rename from src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java rename to src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIErrorTests.java index 693b4840..aad3bff3 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPITests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIErrorTests.java @@ -20,14 +20,13 @@ 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.TIMEOUT_CHECKING_WINNER; +import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.WRONG_CANDIDATE_NAMES; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import au.org.democracydevelopers.raireservice.testUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,24 +45,30 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ActiveProfiles; - /** - * Tests for generate-assertions endpoint. This class automatically fires up the RAIRE Microservice - * on a random port, then runs a series of (at this stage) very basic tests. Currently, we check for - * proper input validation, and check that one valid trivial request succeeds. + * 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. * The list of tests is similar to GenerateAssertionsRequestTests.java, and also to - * GetAssertionsAPITests.java when the same test is relevant to both endpoints. Note that you have - * to run the *whole class*. Individual tests do not work separately because they don't - * initiate the microservice on their own. Contests which will be used for validity testing are - * pre-loaded into the database using src/test/resources/data.sql. + * GetAssertionsAPITests.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: + * - null, missing or whitespace contest name, + * - non-IRV contests, mixed IRV-plurality contests or contests not in the database, + * - null, missing or whitespace candidate names, + * - candidate names that are valid but do not include all the candidates mentioned in votes in the + * database, + * - missing, negative or zero values for numerical inputs (totalAuditableBallots and + * timeLimitSeconds). */ @ActiveProfiles("test-containers") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GenerateAssertionsAPITests { +public class GenerateAssertionsAPIErrorTests { - private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsAPITests.class); + private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsAPIErrorTests.class); private final static HttpHeaders httpHeaders = new HttpHeaders(); private final static String baseURL = "http://localhost:"; @@ -455,28 +460,23 @@ public void generateAssertionsWithNegativeTimeLimitIsAnError() { } /** - * A test of the error responses. This test is a placeholder, which succeeds with the dummy - * assertionGenerator currently implemented, but will need to be expanded to deal with real - * error cases. - * TODO when real AssertionGenerator class is implemented, write tests of each error state, - * See Issue github.com/DemocracyDevelopers/raire-service/issues/65 - * e.g. tied winners. See Issue. + * A GenerateAssertions request with a candidate list that is valid, but the votes in the database + * contain at least one candidate who is not in the expected candidate list. This is an error. */ @Test - @Disabled - public void testErrorHeaderResponses() { - testUtils.log(logger, "testErrorHeaderResponses"); + public void wrongCandidatesIsAnError() { + testUtils.log(logger, "wrongCandidatesIsAnError"); String url = "http://localhost:" +port + generateAssertionsEndpoint; String requestAsJson = "{\"timeLimitSeconds\":10.0,\"totalAuditableBallots\":100," - +"\"contestName\":\"Ballina Mayoral\",\"candidates\":[\"Alice\",\"Bob\"]}"; + +"\"contestName\":\"Ballina One Vote Contest\",\"candidates\":[\"Alice\",\"Bob\",\"Chuan\"]}"; HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); ResponseEntity response = restTemplate.postForEntity(url, request, String.class); assertTrue(response.getStatusCode().is5xxServerError()); - assertEquals(TIMEOUT_CHECKING_WINNER.toString(), + assertEquals(WRONG_CANDIDATE_NAMES.toString(), response.getHeaders().getFirst("error_code")); } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIKnownTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIKnownTests.java new file mode 100644 index 00000000..1cbdf4b9 --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIKnownTests.java @@ -0,0 +1,453 @@ +/* +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.controller; + +import static au.org.democracydevelopers.raireservice.service.RaireServiceException.ERROR_CODE_KEY; +import static au.org.democracydevelopers.raireservice.testUtils.correctAssertionData; +import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; +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 au.org.democracydevelopers.raire.RaireSolution; +import au.org.democracydevelopers.raire.algorithm.RaireResult; +import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; +import au.org.democracydevelopers.raire.assertions.NotEliminatedBefore; +import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; +import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; +import au.org.democracydevelopers.raireservice.response.GenerateAssertionsResponse; +import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; +import au.org.democracydevelopers.raireservice.testUtils; +import au.org.democracydevelopers.raireservice.util.DoubleComparator; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests to validate the behaviour of the Assertion generation API on a collection of simple contest with + * human-computable assertions. Relevant data is preloaded into the test database from + * src/test/resources/known_testcases_votes.sql. + * The test are a subset of those in GenerateAssertionsServiceKnownTests.java. They include + * - The examples from the Guide To Raire Vol 2. Exact matching for Ex. 2 and some for Ex. 1. + * - A cross-county version of the simple example. + * - A request for the simple example with twice the totalAuditableBallots as ballots in the database, + * to test that the diluted margin and difficulties change by a factor of 2, but absolute margin + * stays the same + * - A request for the simple example with fewer totalAuditableBallots than there are in the database, + * to check that there's an appropriate error response. + * Note for anyone comparing this directly with GenerateAssertionsServiceKnownTests: the + * test for wrong candidate names is in GenerateAssertionsAPIErrorTests, along with various other + * tests involving invalid request data. + * TODO The GenerateAssertionsServiceTest contains tests of proper overwriting when assertion + * generation is requested but assertions are already in the database. This is not yet complete in + * this class, pending a decision about how to block assertion regeneration when appropriate. + * See (https://github.com/DemocracyDevelopers/raire-service/issues/70) + * In each case, the test + * - makes a request for assertion generation through the API, + * - checks for the right winner, + * - requests the assertion data through the get-assertions API (JSON), + * - verifies the expected difficulty, + * and checks for the expected data. + */ +@ActiveProfiles("known-testcases") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class GenerateAssertionsAPIKnownTests { + + private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsAPIKnownTests.class); + private final static String baseURL = "http://localhost:"; + private final static String generateAssertionsEndpoint = "/raire/generate-assertions"; + + // Get assertions endpoint - used for testing that they were generated properly. + private final static String getAssertionsEndpoint = "/raire/get-assertions-json"; + private static final int DEFAULT_TIME_LIMIT = 5; + private static final BigDecimal DEFAULT_RISK_LIMIT = BigDecimal.valueOf(0.03); + private static final DoubleComparator doubleComparator = new DoubleComparator(); + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + /** + * Names of contests, to match preloaded data. + */ + private static final String oneNEBAssertionContest = "Sanity Check NEB Assertion Contest"; + private static final String oneNENAssertionContest = "Sanity Check NEN Assertion Contest"; + private static final String NEBNENAssertionContest = "Sanity Check NEB NEN Assertion Contest"; + private static final String ThreeAssertionContest = "Sanity Check 3 Assertion Contest"; + private static final String guideToRaireExample1 = "Guide To Raire Example 1"; + private static final String guideToRaireExample2 = "Guide To Raire Example 2"; + private static final String simpleContest = "Simple Contest"; + private static final String crossCountySimpleContest = "Cross-county Simple Contest"; + + /** + * Array of candidates: Alice, Bob, Chuan, Diego. + */ + private static final String[] aliceBobChuanDiego = {"Alice", "Bob", "Chuan", "Diego"}; + + /** + * Array of candidates: Alice, Chuan, Bob. + */ + private static final String[] aliceChuanBob = {"Alice", "Chuan", "Bob"}; + + /** + * Test the first example in the Guide to Raire Part 2. + * The test data has 1/500 of the votes, so divide margins by 500. + * The difficulties should be the same, because both numerator and denominator should be divided by 500. + * We do not test the NEN assertions because the ones in the Guide have some redundancy. + * Test assertion: Chuan NEB Bob. + * Margin is 4000, but data is divided by 500, so 8. Difficulty is 3.375 as in the Guide. + * Diluted margin is 8/27 = 0.296... + */ + @Test + @Transactional + public void testGuideToRairePart2Example1() { + testUtils.log(logger, "testGuideToRairePart2Example1"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + String getUrl = baseURL + port + getAssertionsEndpoint; + + GenerateAssertionsRequest request = new GenerateAssertionsRequest(guideToRaireExample1, + 27, DEFAULT_TIME_LIMIT, Arrays.stream(aliceBobChuanDiego).toList()); + + + // Request for the assertions to be generated. + ResponseEntity response = restTemplate.postForEntity(generateUrl, + request, GenerateAssertionsResponse.class); + + // Check that generation is successful and we got the right winner. + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertNotNull(response.getBody()); + assertEquals(response.getBody().winner(), "Chuan"); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(guideToRaireExample1, + Arrays.stream(aliceBobChuanDiego).toList(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check for the right metadata + assertTrue(getResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertTrue(correctMetadata(Arrays.stream(aliceBobChuanDiego).toList(), guideToRaireExample1, + DEFAULT_RISK_LIMIT, getResponse.getBody().metadata, Double.class)); + + // There should be one NEB assertion: Chaun NEB Bob + List assertions = Arrays.stream(getResponse.getBody().solution.Ok.assertions).toList(); + Optional nebMaybeAssertion = assertions.stream() + .filter(a -> a.assertion instanceof NotEliminatedBefore).findFirst(); + assertTrue(nebMaybeAssertion.isPresent()); + assertTrue(correctAssertionData("NEB", 8, 27/8.0, 2,1, List.of(), 1.0, + nebMaybeAssertion.get())); + } + + /** + * Exact matching of the assertions described in the Guide to Raire Example 2. + * The test data has 1/1000 of the votes, so divide margins by 1000. + * The difficulties should be the same, because both numerator and denominator should be divided by 1000. + */ + @Test + @Transactional + void testGuideToRairePart2Example2() { + testUtils.log(logger, "testGuideToRairePart2Example2"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + String getUrl = baseURL + port + getAssertionsEndpoint; + GenerateAssertionsRequest request = new GenerateAssertionsRequest(guideToRaireExample2, + 41, 5, Arrays.stream(aliceChuanBob).toList()); + + // Request for the assertions to be generated. + ResponseEntity 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(), "Chuan"); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(guideToRaireExample2, + Arrays.stream(aliceChuanBob).toList(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check for the right metadata. + assertTrue(getResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertTrue(correctMetadata(Arrays.stream(aliceChuanBob).toList(), guideToRaireExample2, + DEFAULT_RISK_LIMIT, getResponse.getBody().metadata, Double.class)); + + // Check for the right results: two assertions, margin 9 and difficulty 4.6. + RaireResult raireResult = getResponse.getBody().solution.Ok; + AssertionAndDifficulty[] assertions = raireResult.assertions; + assertEquals(9, raireResult.margin); + assertEquals(0, doubleComparator.compare(41.0/9, raireResult.difficulty)); + checkGuideToRaireExample2Assertions(assertions); + } + + /** + * Simple contest. The votes are + * 2 (A,B) + * 2 (B) + * 1 (C,A). + * The assertions should be + * A NEB C + * - Margin 1, diluted margin 1/5 = 0.2, difficulty 5/1 = 5. + * A NEN B | {A,B} continuing. + * - Margin 1, diluted margin 1/5 = 0.2, difficulty 5/1 = 5. + * Note that A NEB B is not true. + * This is the single-county case. + */ + @Test + @Transactional + public void simpleContestSingleCounty() { + testUtils.log(logger, "simpleContestSingleCounty"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + String getUrl = baseURL + port + getAssertionsEndpoint; + + GenerateAssertionsRequest request = new GenerateAssertionsRequest(simpleContest, + 5, 5, Arrays.stream(aliceChuanBob).toList()); + + // Request for the assertions to be generated. + ResponseEntity 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"); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(simpleContest, + Arrays.stream(aliceChuanBob).toList(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check for the right metadata. + assertTrue(getResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertTrue(correctMetadata(Arrays.stream(aliceChuanBob).toList(), simpleContest, + DEFAULT_RISK_LIMIT, getResponse.getBody().metadata, Double.class)); + + // Check for the right results: two assertions, margin 9 and difficulty 4.6. + RaireResult raireResult = getResponse.getBody().solution.Ok; + AssertionAndDifficulty[] assertions = raireResult.assertions; + assertEquals(1, raireResult.margin); + assertEquals(0, doubleComparator.compare(5.0, raireResult.difficulty)); + checkSimpleContestAssertions(assertions, 1); + } + + /** + * The same simple contest, but across two counties. Nothing should change. + */ + @Test + @Transactional + public void simpleContestCrossCounty() { + testUtils.log(logger, "simpleContestCrossCounty"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + String getUrl = baseURL + port + getAssertionsEndpoint; + + GenerateAssertionsRequest request = new GenerateAssertionsRequest(crossCountySimpleContest, + 5, 5, Arrays.stream(aliceChuanBob).toList()); + + // Request for the assertions to be generated. + ResponseEntity 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"); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(crossCountySimpleContest, + Arrays.stream(aliceChuanBob).toList(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check for the right metadata. + assertTrue(getResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertTrue(correctMetadata(Arrays.stream(aliceChuanBob).toList(), crossCountySimpleContest, + DEFAULT_RISK_LIMIT, getResponse.getBody().metadata, Double.class)); + + // Check for the right results: two assertions, margin 9 and difficulty 4.6. + RaireResult raireResult = getResponse.getBody().solution.Ok; + AssertionAndDifficulty[] assertions = raireResult.assertions; + assertEquals(1, raireResult.margin); + assertEquals(0, doubleComparator.compare(5.0, raireResult.difficulty)); + checkSimpleContestAssertions(assertions, 1); + } + + /** + * Single-county simple contest again. + * Doubling the totalAuditableBallots to 10 doubles the difficulty, and halves the diluted margin, + * but does not change the absolute margins. + * The actual test data is still the same, with 5 ballots - we just set totalAuditableBallots in + * the request to 10. + * We now have 10 totalAuditableBallots, so we expect: + * A NEB B: Margin 1, diluted margin 1/10 = 0.1, difficulty 10/1 = 10. + * A NEN B | {A,B} continuing: Margin 1, diluted margin 1/10 = 0.1, difficulty 10/1 = 10. + * Exactly the same as the simpleContest test above, but now we have 10 totalAuditableBallots + * and a difficultyFactor=2 in the call to checkSimpleContestAssertions. + */ + @Test + @Transactional + public void simpleContestSingleCountyDoubleBallots() { + testUtils.log(logger, "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, + 10, 5, Arrays.stream(aliceChuanBob).toList()); + + // Request for the assertions to be generated. + ResponseEntity 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"); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(simpleContest, + Arrays.stream(aliceChuanBob).toList(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check for the right metadata. + assertTrue(getResponse.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertTrue(correctMetadata(Arrays.stream(aliceChuanBob).toList(), simpleContest, + DEFAULT_RISK_LIMIT, getResponse.getBody().metadata, Double.class)); + + // Check for the right results: two assertions, margin 9 and difficulty 4.6. + RaireResult raireResult = getResponse.getBody().solution.Ok; + AssertionAndDifficulty[] assertions = raireResult.assertions; + assertEquals(1, raireResult.margin); + assertEquals(0, doubleComparator.compare(10.0, raireResult.difficulty)); + + // Difficulty factor 2 to match the doubled totalAuditableBallots. + checkSimpleContestAssertions(assertions, 2); + } + + /** + * Insufficient totalAuditableBallots causes the right raire error to be returned. + * This test case has 5 ballots, so 2 totalAuditableBallots is an error. + */ + @Test + @Transactional + public void simpleContestSingleCountyInsufficientBallotsError() { + testUtils.log(logger, "simpleContestSingleCountyInsufficientBallotsError"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + + GenerateAssertionsRequest notEnoughBallotsRequest = new GenerateAssertionsRequest(simpleContest, + 2, 5, Arrays.stream(aliceChuanBob).toList()); + + // Request for the assertions to be generated. + ResponseEntity response + = restTemplate.postForEntity(generateUrl, notEnoughBallotsRequest, String.class); + + assertTrue(response.getStatusCode().is5xxServerError()); + assertEquals(RaireErrorCode.INVALID_TOTAL_AUDITABLE_BALLOTS.toString(), + response.getHeaders().getFirst(ERROR_CODE_KEY)); + } + + /** + * Check the data for the simple contests. Note that the winner and loser indices + * are dependent on the order of the input candidate list, which in all the test + * that use it are Alice, Chuan, Bob. + * @param assertionAndDifficulties the assertions returned by API + * @param difficultyFactor factor by which the difficulty should be multiplied (because of larger + * universe size). + */ + private void checkSimpleContestAssertions(AssertionAndDifficulty[] assertionAndDifficulties, + double difficultyFactor) { + assertEquals(2, assertionAndDifficulties.length); + int nebIndex; + + if(assertionAndDifficulties[0].assertion instanceof NotEliminatedBefore) { + nebIndex = 0; + } else { + nebIndex = 1; + } + + // There should be one NEB assertion: Alice NEB Chuan + // Margin 1, diluted margin 0.2, difficulty 5. + assertTrue(correctAssertionData("NEB", 1, 5*difficultyFactor, + 0,1, List.of(), 1.0, assertionAndDifficulties[nebIndex])); + + // There should be one NEN assertion: Chuan > Bob if only {Chuan,Bob} remain. + // Margin is 9,000, but data is divided by 1000, so 9. Difficulty is 41/9 = 4.5555..., + // rounded to 4.6 in the Guide. + // Diluted margin is 9/41 = 0.219512195... + assertTrue(correctAssertionData("NEN", 1, 5*difficultyFactor, + 0, 2, List.of(0,2), 1.0, assertionAndDifficulties[1-nebIndex])); + } + + /** + * Checks for exact match with the Guide To Raire Part 2, example 2. + * Same as GenerateAssertionsServiceKnownTests::checkGuideToRaireExample2Assertions, except that + * it inputs an array of raire-java::AssertionAndDifficulty. + */ + void checkGuideToRaireExample2Assertions(AssertionAndDifficulty[] assertionAndDifficulties) { + assertEquals(2, assertionAndDifficulties.length); + int nebIndex; + + if(assertionAndDifficulties[0].assertion instanceof NotEliminatedBefore) { + nebIndex = 0; + } else { + nebIndex = 1; + } + + // There should be one NEB assertion: Chaun NEB Alice + // Margin is 10,000, but data is divided by 1000, so 10. Difficulty is 4.1 as in the Guide. + // Diluted Margin is 10/41. + assertTrue(correctAssertionData("NEB", 10, 4.1, + 1,0, List.of(), 1.0, assertionAndDifficulties[nebIndex])); + + // There should be one NEN assertion: Chuan > Bob if only {Chuan,Bob} remain. + // Margin is 9,000, but data is divided by 1000, so 9. Difficulty is 41/9 = 4.5555..., + // rounded to 4.6 in the Guide. + // Diluted margin is 9/41 = 0.219512195... + assertTrue(correctAssertionData("NEN", 9, 41.0/9, + 1, 2, List.of(1,2), 1.0, assertionAndDifficulties[1-nebIndex])); + } +} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPINSWTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPINSWTests.java new file mode 100644 index 00000000..903aa2db --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPINSWTests.java @@ -0,0 +1,119 @@ +/* +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.controller; + +import static au.org.democracydevelopers.raireservice.NSWValues.expectedSolutionData; +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 au.org.democracydevelopers.raire.RaireSolution; +import au.org.democracydevelopers.raireservice.NSWValues.Expected; +import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; +import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; +import au.org.democracydevelopers.raireservice.response.GenerateAssertionsResponse; +import au.org.democracydevelopers.raireservice.testUtils; +import au.org.democracydevelopers.raireservice.util.DoubleComparator; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.EnabledIf; + +/** + * Tests to validate the behaviour of Assertion generation on NSW 2021 Mayoral election data. + * Data is loaded in from src/test/resources/NSW2021Data/ + * These tests all pass, but can be disabled because loading in all the NSW data takes a long time. + */ +@ActiveProfiles("nsw-testcases") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +@EnabledIf(value = "${test-strategy.run-nsw-tests}", loadContext = true) +public class GenerateAssertionsAPINSWTests { + + private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsAPINSWTests.class); + private final static String baseURL = "http://localhost:"; + private final static String generateAssertionsEndpoint = "/raire/generate-assertions"; + + // Get assertions endpoint - used for testing that they were generated properly. + private final static String getAssertionsEndpoint = "/raire/get-assertions-json"; + private static final int DEFAULT_TIME_LIMIT = 5; + private static final BigDecimal DEFAULT_RISK_LIMIT = BigDecimal.valueOf(0.03); + private static final DoubleComparator doubleComparator = new DoubleComparator(); + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + /** + * Iterate through all the NSW example data, + * - request assertion generation through the API, + * - check for the right winner, + * - request the assertion data through the get-assertions API (JSON) + * - verify the expected difficulty. + */ + @Test + public void checkAllNSWByAPI() { + testUtils.log(logger, "checkAllNSWByAPI"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + String getUrl = baseURL + port + getAssertionsEndpoint; + + for(Expected expected : expectedSolutionData) { + testUtils.log(logger, "checkAllNSWByAPI: contest "+expected.contestName()); + GenerateAssertionsRequest generateRequest = new GenerateAssertionsRequest( + expected.contestName(), expected.ballotCount(), DEFAULT_TIME_LIMIT, expected.choices()); + + // Request for the assertions to be generated. + ResponseEntity response = restTemplate.postForEntity(generateUrl, + generateRequest, GenerateAssertionsResponse.class); + + // Check that generation is successful and we got the right winner. + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertNotNull(response.getBody()); + assertEquals(response.getBody().winner(), expected.winner()); + + // Request the assertions + GetAssertionsRequest getRequest = new GetAssertionsRequest(expected.contestName(), + expected.choices(), DEFAULT_RISK_LIMIT); + ResponseEntity getResponse = restTemplate.postForEntity(getUrl, getRequest, + RaireSolution.class); + + // Check that the retrieved assertions have the right overall difficulty. + assertTrue(response.getStatusCode().is2xxSuccessful()); + assertNotNull(getResponse.getBody()); + assertEquals(0, doubleComparator.compare(expected.difficulty(), + getResponse.getBody().solution.Ok.difficulty)); + } + } +} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIWickedTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIWickedTests.java new file mode 100644 index 00000000..1a207c9d --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GenerateAssertionsAPIWickedTests.java @@ -0,0 +1,166 @@ +/* +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.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 au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; +import au.org.democracydevelopers.raireservice.testUtils; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests to validate the behavior of Assertion generation on a collection of particularly nasty + * test cases designed to elicit errors. These kinds of errors _are_ expected to happen occasionally + * in normal operation, if the input data is particularly challenging. + * This has the same tests as GenerateAssertionsServiceWickedTests.java. Relevant data is preloaded + * into the test database from src/test/resources/known_testcases_votes.sql. + * This includes + * - a contest with tied winners, + * - a contest that times out trying to find the winners (there are 20 and they are all tied), + * - a contest (Byron Mayor '21) that has enough candidates to time out generating assertions (when + * given a very short timeout). + */ +@ActiveProfiles("known-testcases") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class GenerateAssertionsAPIWickedTests { + + private static final Logger logger = LoggerFactory.getLogger( + GenerateAssertionsAPIWickedTests.class); + private final static String baseURL = "http://localhost:"; + private final static String generateAssertionsEndpoint = "/raire/generate-assertions"; + + /** + * Names of contests, to match preloaded data. + */ + private static final String tiedWinnersContest = "Tied Winners Contest"; + private static final String ByronMayoral = "Byron Mayoral"; + private static final String timeOutCheckingWinnersContest = "Time out checking winners contest"; + + /** + * Candidate lists for the preloaded contests. + */ + private static final List aliceChuanBob = List.of("Alice", "Chuan", "Bob"); + private static final List choicesByron = List.of("HUNTER Alan", "CLARKE Bruce", + "COOREY Cate", "ANDERSON John", "MCILRATH Christopher", "LYON Michael", "DEY Duncan", + "PUGH Asren", "SWIVEL Mark"); + private static final List timeoutCheckingWinnersChoices = List.of("A", "B", "C", "D", "E", + "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T"); + + + /** + * The API requests appropriate for each preloaded contest. Those intended to produce a timeout + * have a particularly small timeLimit. + */ + private final static GenerateAssertionsRequest tiedWinnersRequest + = new GenerateAssertionsRequest(tiedWinnersContest, 2, 5, + aliceChuanBob); + private final static GenerateAssertionsRequest ByronShortTimeoutRequest + = new GenerateAssertionsRequest(ByronMayoral, 18165, 0.001, + choicesByron); + private final static GenerateAssertionsRequest checkingWinnersTimeoutRequest + = new GenerateAssertionsRequest(timeOutCheckingWinnersContest, 20, + 0.001, timeoutCheckingWinnersChoices); + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + /** + * Tied winners results in raire-java returning a TiedWinners RaireError. This is a super-simple + * election with two candidates with one vote each. + */ + @Test + @Transactional + void tiedWinnersGivesTiedWinnersError() { + testUtils.log(logger, "tiedWinnersGivesTiedWinnersError"); + + String generateUrl = baseURL + port + generateAssertionsEndpoint; + + // Request for the assertions to be generated. + ResponseEntity 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)); + } + + /** + * A huge number of tied winners results in raire-java returning a TimeOutCheckingWinners + * error. This election has 20 candidates who are all tied. + */ + @Test + @Transactional + void twentyTiedWinnersGivesTimeOutCheckingWinnersError() { + testUtils.log(logger, "twentyTiedWinnersGivesTimeOutCheckingWinnersError"); + + String generateUrl = baseURL + port + generateAssertionsEndpoint; + + // Request for the assertions to be generated. + ResponseEntity 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)); + } + + /** + * Byron Mayoral times out generating assertions when given a very very short timeout. + */ + @Test + @Transactional + void ByronWithShortTimeoutGivesTimeoutGeneratingAssertionsError() { + testUtils.log(logger, "ByronWithShortTimeoutGivesTimeoutGeneratingAssertionsError"); + String generateUrl = baseURL + port + generateAssertionsEndpoint; + + // Request for the assertions to be generated. + ResponseEntity 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)); + } +} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsCSVAPITests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPICsvTests.java similarity index 86% rename from src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsCSVAPITests.java rename to src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPICsvTests.java index d0956af1..d67bd7fc 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsCSVAPITests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPICsvTests.java @@ -20,6 +20,8 @@ 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.WRONG_CANDIDATE_NAMES; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -57,9 +59,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsCSVAPITests { +public class GetAssertionsAPICsvTests { - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsCSVAPITests.class); + private static final Logger logger = LoggerFactory.getLogger(GetAssertionsAPICsvTests.class); private final static HttpHeaders httpHeaders = new HttpHeaders(); private final static String baseURL = "http://localhost:"; @@ -69,7 +71,7 @@ public class GetAssertionsCSVAPITests { = List.of("Annoying, Alice", "\"Breaking, Bob\"", "Challenging, Chuan", "O'Difficult, Diego"); private final static String trickyCharactersAsJson = - "\"candidates\":[\"Annoying, Alice\",\"Breaking, Bob\",\"Challenging, Chuan\",\"O'Difficult, Diego\"]}"; + "\"candidates\":[\"Annoying, Alice\",\"\\\"Breaking, Bob\\\"\",\"Challenging, Chuan\",\"O'Difficult, Diego\"]}"; @LocalServerPort private int port; @@ -88,8 +90,8 @@ public static void before() { * maxima and minima have been manually computed to make sure they're correct. */ @Test - public void testValidRequestWithNoAssertions() { - testUtils.log(logger, "testValidRequestWithNoAssertions"); + public void testValidRequestWithLotsOfTies() { + testUtils.log(logger, "testValidRequestWithLotsOfTies"); String url = baseURL + port + getAssertionsEndpoint; String requestAsJson = @@ -163,9 +165,8 @@ public void testCharacterEscapingForCSVExport() { public void testCSVDemoContest() { testUtils.log(logger, "testCSVDemoContest"); String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = - "{\"riskLimit\":0.10,\"contestName\":\"CSV Demo Contest\"," - + candidatesAsJson; + String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"CSV Demo Contest\"," + + candidatesAsJson; HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); ResponseEntity response = restTemplate.postForEntity(url, request, String.class); @@ -191,4 +192,23 @@ public void testCSVDemoContest() { "2,NEN,Diego,Chuan,\"Alice,Chuan,Diego\",6.1,100,0.1,0.05,45,45,0,0,0,0,0\n" )); } + + /** + * A request with candidates who are inconsistent with the assertions in the database is an error. + */ + @Test + public void wrongCandidatesIsAnError() { + testUtils.log(logger, "wrongCandidatesIsAnError"); + String url = baseURL + port + getAssertionsEndpoint; + + String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"CSV Demo Contest\"," + + "\"candidates\":[\"Alicia\",\"Boba\",\"Chuan\",\"Diego\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + assertTrue(response.getStatusCode().is5xxServerError()); + assertEquals(WRONG_CANDIDATE_NAMES.toString(), + response.getHeaders().getFirst("error_code")); + } } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTestsJsonAndCsv.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorJsonAndCsvTests.java similarity index 99% rename from src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTestsJsonAndCsv.java rename to src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorJsonAndCsvTests.java index 20c13142..6c4475a3 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTestsJsonAndCsv.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorJsonAndCsvTests.java @@ -60,9 +60,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsAPIErrorTestsJsonAndCsv { +public class GetAssertionsAPIErrorJsonAndCsvTests { - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsAPIErrorTestsJsonAndCsv.class); + private static final Logger logger = LoggerFactory.getLogger(GetAssertionsAPIErrorJsonAndCsvTests.class); private final static HttpHeaders httpHeaders = new HttpHeaders(); diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTests.java deleted file mode 100644 index 8d25ead0..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIErrorTests.java +++ /dev/null @@ -1,399 +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.controller; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; -import au.org.democracydevelopers.raireservice.testUtils; -import java.util.Objects; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.apache.commons.lang3.StringUtils; - -/** - * Tests for get-assertions endpoint. This class automatically fires up the RAIRE Microservice on a random - * port, then runs a series of (at this stage) very basic tests. Currently we check for proper input - * validation, and check that one valid trivial request succeeds for each endpoint. - * The list of tests is similar to GetAssertionsRequestTests.java, and also to GenerateAssertionsAPITests.java - * when the same test is relevant to both endpoints. - * Note that you have to run the *whole class*. Individual tests do not work separately because they don't - * initiate the microservice on their own. - * Contests which will be used for validity testing are preloaded into the database using - * src/test/resources/data.sql. - */ -@ActiveProfiles("test-containers") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsAPIErrorTests { - - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsAPIErrorTests.class); - - private final static HttpHeaders httpHeaders = new HttpHeaders(); - - private final static String baseURL = "http://localhost:"; - private final static String getAssertionsEndpoint = "/raire/get-assertions-json"; - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @BeforeAll - public static void before() { - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - } - - - /** - * A valid request for a contest that exists but has no assertions. Returns the correct error - * code and message. - */ - @Test - public void testValidRequestWithNoAssertions() { - testUtils.log(logger, "testValidRequestWithNoAssertions"); - String url = "http://localhost:" + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Ballina Mayoral\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is5xxServerError()); - assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT.toString(), - Objects.requireNonNull(response.getHeaders().get("error_code")).getFirst()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), - "No assertions have been generated for the contest")); - } - - /** - * This is really just a test that the testing is working. There's no mapping for the plain - * localhost response, so when the microservice is running it just returns a default error. We - * check for 404. - */ - @Test - public void testErrorForNonFunctioningEndpoint() { - testUtils.log(logger, "testErrorForNonFunctioningEndpoint"); - ResponseEntity response = restTemplate.postForEntity(baseURL + port + "/", - new HttpEntity<>("", httpHeaders), String.class); - assertTrue(response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(404))); - } - - /** - * Calling the getAssertions endpoint with no header and no data produces an error. - */ - @Test - public void getAssertionsNoBodyError() { - testUtils.log(logger, "getAssertionsNoBodyError"); - ResponseEntity response = restTemplate.postForEntity(baseURL + port + - getAssertionsEndpoint, new HttpEntity<>("", new HttpHeaders()), String.class); - assertTrue(response.getStatusCode().is4xxClientError()); - } - - /** - * The getAssertions endpoint, with correct headers but no data, should produce "Bad Request". - */ - @Test - public void testGetAssertionsBadRequest() { - testUtils.log(logger, "testGetAssertionsBadRequest"); - String url = baseURL + port + getAssertionsEndpoint; - - HttpEntity request = new HttpEntity<>("", httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(Objects.requireNonNull(response.getBody()).contains("Bad Request")); - } - - /** - * The getAssertions endpoint, called with a nonexistent contest, returns a meaningful error. - */ - @Test - public void getAssertionsWithNonExistentContestIsAnError() { - testUtils.log(logger, "getAssertionsWithNonExistentContestIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Nonexistent Contest\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No such contest")); - - } - - /** - * The getAssertions endpoint, called with a valid plurality contest, returns a meaningful error. - */ - @Test - public void getAssertionsWithPluralityContestIsAnError() { - testUtils.log(logger, "getAssertionsWithPluralityContestIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Valid Plurality Contest\"," - +"\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Not comprised of all IRV")); - } - - /** - * The getAssertions endpoint, called with a mixed IRV and non-IRV contest, returns a meaningful error. - */ - @Test - public void getAssertionsWithMixedIRVPluralityContestIsAnError() { - testUtils.log(logger, "getAssertionsWithMixedIRVPluralityContestIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Invalid Mixed Contest\"," - +"\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Not comprised of all IRV")); - - } - - /** - * The getAssertions endpoint, called with a missing contest name, returns a meaningful error. - */ - @Test - public void getAssertionsWithMissingContestNameIsAnError() { - testUtils.log(logger, "getAssertionsWithMissingContestNameIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.05,\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No contest name")); - } - - /** - * The getAssertions endpoint, called with a null contest name, returns a meaningful error. - */ - @Test - public void getAssertionsWithNullContestNameIsAnError() { - testUtils.log(logger, "getAssertionsWithNullContestNameIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.05,\"contestName\":null,\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No contest name")); - } - - /** - * The getAssertions endpoint, called with an empty contest name, returns a meaningful error. - */ - @Test - public void getAssertionsWithEmptyContestNameIsAnError() { - testUtils.log(logger, "getAssertionsWithEmptyContestNameIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.05,\"contestName\":\"\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No contest name")); - } - - /** - * The getAssertions endpoint, called with an all-whitespace contest name, returns a meaningful error. - */ - @Test - public void getAssertionsWithWhitespaceContestNameIsAnError() { - testUtils.log(logger, "getAssertionsWithWhitespaceContestNameIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\" \",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "No contest name")); - } - - /** - * The getAssertions endpoint, called with a missing candidate list, returns a meaningful error. - */ - @Test - public void getAssertionsWithMissingCandidateListIsAnError() { - testUtils.log(logger, "getAssertionsWithMissingCandidateListIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.05,\"contestName\":\"Ballina Mayoral\"}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Bad candidate list")); - } - - /** - * The getAssertions endpoint, called with a null candidate list, returns a meaningful error. - */ - @Test - public void getAssertionsWithNullCandidateListIsAnError() { - testUtils.log(logger, "getAssertionsWithNullCandidateListIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Ballina Mayoral\",\"candidates\":null}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Bad candidate list")); - } - - /** - * The getAssertions endpoint, called with an empty candidate list, returns a meaningful error. - */ - @Test - public void getAssertionsWithEmptyCandidateListIsAnError() { - testUtils.log(logger, "getAssertionsWithEmptyCandidateListIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Ballina Mayoral\",\"candidates\":[]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Bad candidate list")); - } - - /** - * The getAssertions endpoint, called with a whitespace candidate name, returns a meaningful error. - */ - @Test - public void getAssertionsWithWhitespaceCandidateNameIsAnError() { - testUtils.log(logger, "getAssertionsWithWhitespaceCandidateNameIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"Ballina Mayoral\"," - +"\"candidates\":[\"Alice\",\" \"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is4xxClientError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), "Bad candidate list")); - } - - /** - * The getAssertions endpoint, called with a missing risk limit, returns a meaningful error. - */ - @Test - public void getAssertionsWithMissingRiskLimitIsAnError() { - testUtils.log(logger, "getAssertionsWithMissingRiskLimitIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"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(StringUtils.containsIgnoreCase(response.getBody(), "Null or negative risk limit")); - } - - /** - * The getAssertions endpoint, called with a null risk limit, returns a meaningful error. - */ - @Test - public void getAssertionsWithNullRiskLimitIsAnError() { - testUtils.log(logger, "getAssertionsWithNullRiskLimitIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":null,\"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(StringUtils.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() { - testUtils.log(logger, "getAssertionsWithNegativeRiskLimitIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":-0.05,\"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(StringUtils.containsIgnoreCase(response.getBody(), "Null or negative risk limit")); - } -} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsJson.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIJsonTests.java similarity index 76% rename from src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsJson.java rename to src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIJsonTests.java index ea3fa4a1..26950601 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsJson.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsAPIJsonTests.java @@ -21,14 +21,19 @@ 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.WRONG_CANDIDATE_NAMES; -import static au.org.democracydevelopers.raireservice.testUtils.correctIndexedAPIAssertionData; +import static au.org.democracydevelopers.raireservice.testUtils.correctAssertionData; import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; import static au.org.democracydevelopers.raireservice.testUtils.correctSolutionData; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import au.org.democracydevelopers.raire.RaireSolution; +import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.testUtils; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -65,10 +70,10 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsValidAPIRequestTestsJson { +public class GetAssertionsAPIJsonTests { private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsValidAPIRequestTestsJson.class); + GetAssertionsAPIJsonTests.class); private final static HttpHeaders httpHeaders = new HttpHeaders(); private final static String baseURL = "http://localhost:"; @@ -92,8 +97,8 @@ public static void before() { /** - * The getAssertions endpoint, valid request. Currently just checking that the serialization correctly - * ignores time_to_find_assertions. + * The getAssertions endpoint, valid request. Currently just checking that the serialization + * correctly ignores time_to_find_assertions. */ @Test public void getAssertionsWithOneNEBContest() { @@ -119,26 +124,26 @@ void retrieveAssertionsExistentContestOneNEBAssertion() { testUtils.log(logger, "retrieveAssertionsExistentContestOneNEBAssertion"); String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\"]}"; + // Make the request. + GetAssertionsRequest request = new GetAssertionsRequest(oneNEBAssertionContest, + List.of("Alice","Bob"), BigDecimal.valueOf(0.1)); + ResponseEntity response + = restTemplate.postForEntity(url, request, RaireSolution.class); - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, 0.1, - response.getBody())); + // The metadata has been constructed appropriately. + assertNotNull(response.getBody()); + assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, BigDecimal.valueOf(0.1), + response.getBody().metadata, Double.class)); // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); + assertNull(response.getBody().solution.Err); // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(320,1.1, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEB", 320, 1.1, 0, - 1, new ArrayList<>(), 1.0, response.getBody(),0)); + assertTrue(correctSolutionData(320,1.1, 1, response.getBody().solution.Ok)); + // We expect one NEB assertion with the following data. + assertTrue(correctAssertionData("NEB", 320, 1.1, 0, + 1, new ArrayList<>(), 1.0, response.getBody().solution.Ok.assertions[0])); } /** @@ -150,25 +155,24 @@ void retrieveAssertionsExistentContestOneNENAssertion() { testUtils.log(logger, "retrieveAssertionsExistentContestOneNENAssertion"); String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNENAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + GetAssertionsRequest request = new GetAssertionsRequest(oneNENAssertionContest, + List.of("Alice","Bob","Charlie","Diego"),BigDecimal.valueOf(0.1)); + ResponseEntity response = restTemplate.postForEntity(url, request, RaireSolution.class); // The metadata has been constructed appropriately + assertNotNull(response.getBody()); assertTrue(correctMetadata(List.of("Alice","Bob","Charlie","Diego"),oneNENAssertionContest, - 0.1, response.getBody())); + BigDecimal.valueOf(0.1), response.getBody().metadata, Double.class)); // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); + assertNull(response.getBody().solution.Err); // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(240, 3.01, 1, response.getBody())); + assertTrue(correctSolutionData(240, 3.01, 1, response.getBody().solution.Ok)); - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEN",240,3.01, 0, - 2, List.of(0,1,3,2), 1.0, response.getBody(),0)); + // We expect one NEN assertion with the following data. + assertTrue(correctAssertionData("NEN",240,3.01, 0, + 2, List.of(0,1,3,2), 1.0, response.getBody().solution.Ok.assertions[0])); } /** @@ -182,11 +186,10 @@ void retrieveAssertionsIncorrectCandidateNamesIsAnError() { testUtils.log(logger, "retrieveAssertionsIncorrectCandidateNamesIsAnError"); String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBOneNENAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + GetAssertionsRequest request = new GetAssertionsRequest(oneNEBOneNENAssertionContest, + List.of("Alice","Bob","Charlie","Diego"),BigDecimal.valueOf(0.1)); + ResponseEntity response + = restTemplate.postForEntity(url, request, String.class); assertTrue(response.getStatusCode().is5xxServerError()); assertTrue(StringUtils.containsIgnoreCase(response.getBody(), diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressAPIJsonAndCsvTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressAPIJsonAndCsvTests.java new file mode 100644 index 00000000..14c5f5c5 --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressAPIJsonAndCsvTests.java @@ -0,0 +1,309 @@ +/* +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.controller; + +import static au.org.democracydevelopers.raireservice.testUtils.correctAssertionData; +import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; +import static au.org.democracydevelopers.raireservice.testUtils.correctSolutionData; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import au.org.democracydevelopers.raire.RaireSolution; +import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; +import au.org.democracydevelopers.raireservice.testUtils; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests for get-assertions endpoint. This class automatically fires up the RAIRE Microservice on a + * random port, then runs a series of tests for correct responses to valid requests. + * The list of tests is similar to - and in most cases identical to - the tests in + * GetAssertionsInProgressServiceTestsJsonAndCsv.java. + * Contests which will be used for validity testing are preloaded into the database using + * src/test/resources/data.sql. + */ +@ActiveProfiles("assertions-in-progress") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class GetAssertionsInProgressAPIJsonAndCsvTests { + + private static final Logger logger = LoggerFactory.getLogger( + GetAssertionsInProgressAPIJsonAndCsvTests.class); + + private final static HttpHeaders httpHeaders = new HttpHeaders(); + private final static String baseURL = "http://localhost:"; + private final static String getAssertionsJsonEndpoint = "/raire/get-assertions-json"; + private final static String getAssertionsCsvEndpoint = "/raire/get-assertions-csv"; + private final static String oneNEBAssertionContest = "One NEB Assertion Contest"; + private final static String oneNENAssertionContest = "One NEN Assertion Contest"; + private final static String oneNEBOneNENAssertionContest = "One NEN NEB Assertion Contest"; + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @BeforeAll + public static void before() { + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + } + + + /** + * Retrieve assertions for a contest that has one NEB assertion (audit in progress). (JSON) + */ + @Test + @Transactional + void retrieveAssertionsExistentContestOneNEBAssertionJSON() { + testUtils.log(logger, "retrieveAssertionsExistentContestOneNEBAssertionJSON"); + String url = baseURL + port + getAssertionsJsonEndpoint; + + GetAssertionsRequest request = new GetAssertionsRequest(oneNEBAssertionContest, + List.of("Alice","Bob"), BigDecimal.valueOf(0.1)); + + ResponseEntity response = restTemplate.postForEntity(url, request, + RaireSolution.class); + + // The metadata has been constructed appropriately + assertNotNull(response.getBody()); + assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, + BigDecimal.valueOf(0.1), response.getBody().metadata, Double.class)); + + // The RaireSolution contains a RaireResultOrError, but the error should be null. + assertNull(response.getBody().solution.Err); + + // Check the contents of the RaireResults within the RaireSolution. + assertTrue(correctSolutionData(320,1.1, 1, + response.getBody().solution.Ok)); + + // We expect one NEB assertion with the following data. + assertTrue(correctAssertionData("NEB", 320, 1.1, 0, 1, + new ArrayList<>(), 0.5, response.getBody().solution.Ok.assertions[0])); + + } + + /** + * Retrieve assertions for a contest that has one NEB assertion (audit in progress). (CSV) + */ + @Test + @Transactional + void retrieveAssertionsExistentContestOneNEBAssertionCSV() { + testUtils.log(logger, "retrieveAssertionsExistentContestOneNEBAssertionCSV"); + String url = baseURL + port + getAssertionsCsvEndpoint; + + String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + + oneNEBAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + String csv = restTemplate.postForEntity(url, request, String.class).getBody(); + + assertNotNull(csv); + assertTrue(csv.contains("Contest name,One NEB Assertion Contest\n")); + assertTrue(csv.contains("Candidates,\"Alice,Bob\"")); + assertTrue(csv.contains("Extreme item,Value,Assertion IDs\n")); + assertTrue(csv.contains("Margin,320,\"1\"\n")); + assertTrue(csv.contains("Diluted margin,0.32,\"1\"\n")); + assertTrue(csv.contains("Raire difficulty,1.1,\"1\"\n")); + assertTrue(csv.contains("Current risk,0.50,\"1\"\n")); + assertTrue(csv.contains("Optimistic samples to audit,111,\"1\"\n")); + assertTrue(csv.contains("Estimated samples to audit,111,\"1\"\n")); + assertTrue(csv.contains( + "ID,Type,Winner,Loser,Assumed continuing,Difficulty,Margin,Diluted margin,Risk," + + "Estimated samples to audit,Optimistic samples to audit,Two vote over count," + + "One vote over count,Other discrepancy count,One vote under count," + + "Two vote under count\n")); + assertTrue(csv.contains("1,NEB,Alice,Bob,,1.1,320,0.32,0.50,111,111,0,0,0,0,0\n")); + } + + /** + * Retrieve assertions for a contest that has one NEN assertion (audit in progress). (JSON) + */ + @Test + @Transactional + void retrieveAssertionsExistentContestOneNENAssertionJSON() { + testUtils.log(logger, "retrieveAssertionsExistentContestOneNENAssertionJSON"); + String url = baseURL + port + getAssertionsJsonEndpoint; + + GetAssertionsRequest request = new GetAssertionsRequest(oneNENAssertionContest, + List.of("Alice","Bob","Charlie","Diego"), BigDecimal.valueOf(0.1)); + ResponseEntity response = restTemplate.postForEntity(url, request, + RaireSolution.class); + + // The metadata has been constructed appropriately + assertNotNull(response.getBody()); + assertTrue( + correctMetadata(List.of("Alice", "Bob", "Charlie", "Diego"), oneNENAssertionContest, + BigDecimal.valueOf(0.1), response.getBody().metadata, Double.class)); + + // The RaireSolution contains a RaireResultOrError, but the error should be null. + assertNull(response.getBody().solution.Err); + + // Check the contents of the RaireResults within the RaireSolution. + assertTrue(correctSolutionData(240, 3.01, 1, + response.getBody().solution.Ok)); + + // We expect one assertion with the following data. + assertTrue(correctAssertionData("NEN", 240, 3.01, 0, 2, + List.of(0, 1, 3, 2), 0.2, response.getBody().solution.Ok.assertions[0])); + } + + /** + * Retrieve assertions for a contest that has one NEN assertion (audit in progress). (CSV) + */ + @Test + @Transactional + void retrieveAssertionsExistentContestOneNENAssertionCSV() { + testUtils.log(logger, "retrieveAssertionsExistentContestOneNENAssertionCSV"); + String url = baseURL + port + getAssertionsCsvEndpoint; + + String requestAsJson = + "{\"riskLimit\":0.10,\"contestName\":\"" + oneNENAssertionContest + + "\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + String csv = restTemplate.postForEntity(url, request, String.class).getBody(); + + assertNotNull(csv); + assertTrue(csv.contains("Contest name,One NEN Assertion Contest\n")); + assertTrue(csv.contains("Candidates,\"Alice,Bob,Charlie,Diego\"")); + assertTrue(csv.contains("Extreme item,Value,Assertion IDs\n")); + assertTrue(csv.contains("Margin,240,\"1\"\n")); + assertTrue(csv.contains("Diluted margin,0.12,\"1\"\n")); + assertTrue(csv.contains("Raire difficulty,3.01,\"1\"\n")); + assertTrue(csv.contains("Current risk,0.20,\"1\"\n")); + assertTrue(csv.contains("Optimistic samples to audit,201,\"1\"\n")); + assertTrue(csv.contains("Estimated samples to audit,245,\"1\"\n")); + assertTrue(csv.contains( + "ID,Type,Winner,Loser,Assumed continuing,Difficulty,Margin,Diluted margin,Risk," + + "Estimated samples to audit,Optimistic samples to audit,Two vote over count," + + "One vote over count,Other discrepancy count,One vote under count," + + "Two vote under count\n")); + assertTrue(csv.contains( + "1,NEN,Alice,Charlie,\"Alice,Charlie,Diego,Bob\",3.01,240,0.12,0.20,245,201,0,1,2,0,0\n" + )); + } + + /** + * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). + * (JSON) + */ + @Test + @Transactional + void retrieveAssertionsOneNENOneNEBAssertionInProgressJSON() { + testUtils.log(logger, "retrieveAssertionsOneNENOneNEBAssertionInProgressJSON"); + String url = baseURL + port + getAssertionsJsonEndpoint; + + // Make the request. + GetAssertionsRequest request = new GetAssertionsRequest(oneNEBOneNENAssertionContest, + List.of("Liesl","Wendell","Amanda","Chuan"), BigDecimal.valueOf(0.05)); + ResponseEntity response = restTemplate.postForEntity(url, request, + RaireSolution.class); + + // The metadata has been constructed appropriately. + assertNotNull(response.getBody()); + assertTrue(correctMetadata(List.of("Liesl", "Wendell", "Amanda", "Chuan"), + oneNEBOneNENAssertionContest, BigDecimal.valueOf(0.05), response.getBody().metadata, + Double.class)); + + // The RaireSolution contains a RaireResultOrError, but the error should be null. + assertNull(response.getBody().solution.Err); + + // Check the contents of the RaireResults within the RaireSolution. + assertTrue(correctSolutionData(112, 3.17, 2, + response.getBody().solution.Ok)); + + // We expect two assertions with the following data, but we don't necessarily know what order + // they're in. So check for their presence at either position. + assertTrue( + correctAssertionData("NEB", 112, 0.1, 2, 0, + new ArrayList<>(), 0.08, response.getBody().solution.Ok.assertions[0]) || + correctAssertionData("NEB", 112, 0.1, 2, 0, + new ArrayList<>(), 0.08, response.getBody().solution.Ok.assertions[1]) + ); + + assertTrue( + correctAssertionData("NEN", 560, 3.17, 2, 1, + List.of(0, 1, 2), 0.7, response.getBody().solution.Ok.assertions[0]) || + correctAssertionData("NEN", 560, 3.17, 2, 1, + List.of(0, 1, 2), 0.7, response.getBody().solution.Ok.assertions[1]) + ); + } + + /** + * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). + * (CSV) + */ + @Test + @Transactional + void retrieveAssertionsOneNENOneNEBAssertionInProgressCSV() { + testUtils.log(logger, "retrieveAssertionsOneNENOneNEBAssertionInProgressCSV"); + String url = baseURL + port + getAssertionsCsvEndpoint; + + String requestAsJson = + "{\"riskLimit\":0.05,\"contestName\":\"" + oneNEBOneNENAssertionContest + + "\",\"candidates\":[\"Liesl\",\"Wendell\",\"Amanda\",\"Chuan\"]}"; + + HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); + String csv = restTemplate.postForEntity(url, request, String.class).getBody(); + + assertNotNull(csv); + assertTrue(csv.contains("Contest name,One NEN NEB Assertion Contest\n")); + assertTrue(csv.contains("Candidates,\"Liesl,Wendell,Amanda,Chuan\"")); + assertTrue(csv.contains("Extreme item,Value,Assertion IDs\n")); + assertTrue(csv.contains("Margin,112,\"1\"\n")); + assertTrue(csv.contains("Diluted margin,0.1,\"1\"\n")); + assertTrue(csv.contains("Raire difficulty,3.17,\"2\"\n")); + assertTrue(csv.contains("Current risk,0.70,\"2\"\n")); + assertTrue(csv.contains("Optimistic samples to audit,200,\"2\"\n")); + assertTrue(csv.contains("Estimated samples to audit,300,\"2\"\n")); + assertTrue(csv.contains( + "ID,Type,Winner,Loser,Assumed continuing,Difficulty,Margin,Diluted margin,Risk," + + "Estimated samples to audit,Optimistic samples to audit,Two vote over count," + + "One vote over count,Other discrepancy count,One vote under count," + + "Two vote under count\n")); + assertTrue(csv.contains("1,NEB,Amanda,Liesl,,0.1,112,0.1,0.08,27,20,2,0,0,1,0\n")); + assertTrue(csv.contains( + "2,NEN,Amanda,Wendell,\"Liesl,Wendell,Amanda\",3.17,560,0.5,0.70,300,200,0,2,0,0,1\n" + )); + } +} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTests.java deleted file mode 100644 index 7e338bfa..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTests.java +++ /dev/null @@ -1,195 +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.controller; - -import static au.org.democracydevelopers.raireservice.testUtils.correctIndexedAPIAssertionData; -import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; -import static au.org.democracydevelopers.raireservice.testUtils.correctSolutionData; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.org.democracydevelopers.raireservice.testUtils; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests for get-assertions endpoint. This class automatically fires up the RAIRE Microservice on a random - * port, then runs a series of tests for correct responses to valid requests. - * The list of tests is similar to - and in most cases identical to - the GetAssertionsJsonServiceTests. - * Note that you have to run the *whole class*. Individual tests do not work separately because they don't - * initiate the microservice on their own. - * Contests which will be used for validity testing are pre-loaded into the database using - * src/test/resources/data.sql. - */ -@ActiveProfiles("assertions-in-progress") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsInProgressValidAPIRequestTests { - - private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsInProgressValidAPIRequestTests.class); - - private final static HttpHeaders httpHeaders = new HttpHeaders(); - private final static String baseURL = "http://localhost:"; - private final static String getAssertionsJsonEndpoint = "/raire/get-assertions-json"; - private final static String oneNEBAssertionContest = "One NEB Assertion Contest"; - private final static String oneNENAssertionContest = "One NEN Assertion Contest"; - private final static String oneNEBOneNENAssertionContest = "One NEN NEB Assertion Contest"; - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @BeforeAll - public static void before() { - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - } - - - /** - * Retrieve assertions for a contest that has one NEB assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonExistentContestOneNEBAssertion() { - testUtils.log(logger, "retrieveAssertionsAsJsonExistentContestOneNEBAssertion"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, 0.1, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(320,1.1, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEB", 320, 1.1, 0, - 1, new ArrayList<>(), 0.5, response.getBody(),0)); - - } - - /** - * Retrieve assertions for a contest that has one NEN assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonExistentContestOneNENAssertion() { - testUtils.log(logger, "retrieveAssertionsAsJsonExistentContestOneNENAssertion"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.10,\"contestName\":\"" + oneNENAssertionContest - + "\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue( - correctMetadata(List.of("Alice", "Bob", "Charlie", "Diego"), oneNENAssertionContest, 0.1, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(240, 3.01, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEN", 240, 3.01, 0, 2, - List.of(0, 1, 3, 2), 0.2, response.getBody(), 0)); - } - - /** - * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonOneNENOneNEBAssertionInProgress() { - testUtils.log(logger, "retrieveAssertionsAsJsonOneNENOneNEBAssertionInProgress"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"" + oneNEBOneNENAssertionContest - + "\",\"candidates\":[\"Liesl\",\"Wendell\",\"Amanda\",\"Chuan\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Liesl", "Wendell", "Amanda", "Chuan"), - oneNEBOneNENAssertionContest, 0.05, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(112, 3.17, 2, response.getBody())); - - // We expect two assertions with the following data, but we don't necessarily know what order they're in. - // So check for their presence at either position. - assertTrue( - correctIndexedAPIAssertionData("NEB", 112, 0.1, 2, 0, - new ArrayList<>(), 0.08, response.getBody(), 0) || - correctIndexedAPIAssertionData("NEB", 112, 0.1, 2, 0, - new ArrayList<>(), 0.08, response.getBody(), 1) - ); - - assertTrue( - correctIndexedAPIAssertionData("NEN", 560, 3.17, 2, 1, - List.of(0, 1, 2), 0.7, response.getBody(), 0) || - correctIndexedAPIAssertionData("NEN", 560, 3.17, 2, 1, - List.of(0, 1, 2), 0.7, response.getBody(), 1) - ); - } -} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTestsJsonAndCsv.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTestsJsonAndCsv.java deleted file mode 100644 index 779ff335..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsInProgressValidAPIRequestTestsJsonAndCsv.java +++ /dev/null @@ -1,195 +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.controller; - -import static au.org.democracydevelopers.raireservice.testUtils.correctIndexedAPIAssertionData; -import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; -import static au.org.democracydevelopers.raireservice.testUtils.correctSolutionData; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.org.democracydevelopers.raireservice.testUtils; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests for get-assertions endpoint. This class automatically fires up the RAIRE Microservice on a random - * port, then runs a series of tests for correct responses to valid requests. - * The list of tests is similar to - and in most cases identical to - the GetAssertionsJsonServiceTests. - * Note that you have to run the *whole class*. Individual tests do not work separately because they don't - * initiate the microservice on their own. - * Contests which will be used for validity testing are pre-loaded into the database using - * src/test/resources/data.sql. - */ -@ActiveProfiles("assertions-in-progress") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsInProgressValidAPIRequestTestsJsonAndCsv { - - private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsInProgressValidAPIRequestTestsJsonAndCsv.class); - - private final static HttpHeaders httpHeaders = new HttpHeaders(); - private final static String baseURL = "http://localhost:"; - private final static String getAssertionsJsonEndpoint = "/raire/get-assertions-json"; - private final static String oneNEBAssertionContest = "One NEB Assertion Contest"; - private final static String oneNENAssertionContest = "One NEN Assertion Contest"; - private final static String oneNEBOneNENAssertionContest = "One NEN NEB Assertion Contest"; - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @BeforeAll - public static void before() { - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - } - - - /** - * Retrieve assertions for a contest that has one NEB assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonExistentContestOneNEBAssertion() { - testUtils.log(logger, "retrieveAssertionsAsJsonExistentContestOneNEBAssertion"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, 0.1, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(320,1.1, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEB", 320, 1.1, 0, - 1, new ArrayList<>(), 0.5, response.getBody(),0)); - - } - - /** - * Retrieve assertions for a contest that has one NEN assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonExistentContestOneNENAssertion() { - testUtils.log(logger, "retrieveAssertionsAsJsonExistentContestOneNENAssertion"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.10,\"contestName\":\"" + oneNENAssertionContest - + "\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue( - correctMetadata(List.of("Alice", "Bob", "Charlie", "Diego"), oneNENAssertionContest, 0.1, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(240, 3.01, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEN", 240, 3.01, 0, 2, - List.of(0, 1, 3, 2), 0.2, response.getBody(), 0)); - } - - /** - * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). - */ - @Test - @Transactional - void retrieveAssertionsAsJsonOneNENOneNEBAssertionInProgress() { - testUtils.log(logger, "retrieveAssertionsAsJsonOneNENOneNEBAssertionInProgress"); - String url = baseURL + port + getAssertionsJsonEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.05,\"contestName\":\"" + oneNEBOneNENAssertionContest - + "\",\"candidates\":[\"Liesl\",\"Wendell\",\"Amanda\",\"Chuan\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Liesl", "Wendell", "Amanda", "Chuan"), - oneNEBOneNENAssertionContest, 0.05, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(112, 3.17, 2, response.getBody())); - - // We expect two assertions with the following data, but we don't necessarily know what order they're in. - // So check for their presence at either position. - assertTrue( - correctIndexedAPIAssertionData("NEB", 112, 0.1, 2, 0, - new ArrayList<>(), 0.08, response.getBody(), 0) || - correctIndexedAPIAssertionData("NEB", 112, 0.1, 2, 0, - new ArrayList<>(), 0.08, response.getBody(), 1) - ); - - assertTrue( - correctIndexedAPIAssertionData("NEN", 560, 3.17, 2, 1, - List.of(0, 1, 2), 0.7, response.getBody(), 0) || - correctIndexedAPIAssertionData("NEN", 560, 3.17, 2, 1, - List.of(0, 1, 2), 0.7, response.getBody(), 1) - ); - } -} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTests.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTests.java deleted file mode 100644 index f9990347..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTests.java +++ /dev/null @@ -1,197 +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.controller; - -import static au.org.democracydevelopers.raireservice.testUtils.correctIndexedAPIAssertionData; -import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; -import static au.org.democracydevelopers.raireservice.testUtils.correctSolutionData; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; -import au.org.democracydevelopers.raireservice.testUtils; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import org.apache.commons.lang3.StringUtils; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests for get-assertions endpoint. This class automatically fires up the RAIRE Microservice on a - * random port, then runs a series of tests for correct responses to valid requests. - * The list of tests is similar to - and in most cases identical to - the GetAssertionsJsonServiceTests. - * Note that you have to run the *whole class*. Individual tests do not work separately because they - * don't initiate the microservice on their own. - * Contests which will be used for validity testing are preloaded into the database using - * src/test/resources/data.sql. - */ -@ActiveProfiles("simple-assertions") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsValidAPIRequestTests { - - private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsValidAPIRequestTests.class); - - private final static HttpHeaders httpHeaders = new HttpHeaders(); - private final static String baseURL = "http://localhost:"; - private final static String getAssertionsEndpoint = "/raire/get-assertions-json"; - - private final static String oneNEBAssertionContest = "One NEB Assertion Contest"; - private final static String oneNENAssertionContest = "One NEN Assertion Contest"; - private final static String oneNEBOneNENAssertionContest = "One NEN NEB Assertion Contest"; - - - @LocalServerPort - private int port; - - @Autowired - private TestRestTemplate restTemplate; - - @BeforeAll - public static void before() { - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - } - - - /** - * The getAssertions endpoint, valid request. Currently just checking that the serialization correctly - * ignores time_to_find_assertions. - */ - @Test - public void getAssertionsWithOneNEBContest() { - testUtils.log(logger, "getAssertionsWithOneNEBContest"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.05,\"contestName\":\"" + oneNEBAssertionContest - +"\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is2xxSuccessful()); - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "time_to_find_assertions")); - } - - /** - * Retrieve assertions for a contest that has one NEB assertion. - */ - @Test - @Transactional - void retrieveAssertionsExistentContestOneNEBAssertion() { - testUtils.log(logger, "retrieveAssertionsExistentContestOneNEBAssertion"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Alice","Bob"), oneNEBAssertionContest, 0.1, - response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(320,1.1, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEB", 320, 1.1, 0, - 1, new ArrayList<>(), 1.0, response.getBody(),0)); - - } - - /** - * Retrieve assertions for a contest that has one NEN assertion. - */ - @Test - @Transactional - void retrieveAssertionsExistentContestOneNENAssertion() { - testUtils.log(logger, "retrieveAssertionsExistentContestOneNENAssertion"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNENAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - // The metadata has been constructed appropriately - assertTrue(correctMetadata(List.of("Alice","Bob","Charlie","Diego"),oneNENAssertionContest, - 0.1, response.getBody())); - - // The RaireSolution contains a RaireResultOrError, but the error should be null. - assertFalse(StringUtils.containsIgnoreCase(response.getBody(), "Error")); - - // Check the contents of the RaireResults within the RaireSolution. - assertTrue(correctSolutionData(240, 3.01, 1, response.getBody())); - - // We expect one assertion with the following data. - assertTrue(correctIndexedAPIAssertionData("NEN",240,3.01, 0, - 2, List.of(0,1,3,2), 1.0, response.getBody(),0)); - } - - /** - * Retrieve assertions for a contest where the request has been set up with incorrect - * candidate names for the given contest. - * This is a valid request in the sense that it passes Request.Validate(), but should later fail. - */ - @Test - @Transactional - void retrieveAssertionsIncorrectCandidateNamesIsAnError() { - testUtils.log(logger, "retrieveAssertionsIncorrectCandidateNamesIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"" + - oneNEBOneNENAssertionContest+"\",\"candidates\":[\"Alice\",\"Bob\",\"Charlie\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is5xxServerError()); - assertTrue(StringUtils.containsIgnoreCase(response.getBody(), - "candidate list provided as parameter is inconsistent")); - assertEquals(RaireErrorCode.INTERNAL_ERROR.toString(), - Objects.requireNonNull(response.getHeaders().get("error_code")).getFirst()); - } -} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsCsv.java b/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsCsv.java deleted file mode 100644 index ab7ee16d..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/controller/GetAssertionsValidAPIRequestTestsCsv.java +++ /dev/null @@ -1,213 +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.controller; - -import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.WRONG_CANDIDATE_NAMES; -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 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; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; - -/** - * Tests for get-assertions endpoint with the csv request. This class automatically fires up the - * RAIRE Microservice on a random port, then runs a series of tests. - * The tests are essentially the same as those in GetAssertionsCSVTests.java, but we're checking for - * correct API output rather than checking the service directly. - * Contests which will be used for validity testing are preloaded into the database using - * src/test/resources/simple_assertions_csv_challenges.sql. - */ -@ActiveProfiles("csv-challenges") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsValidAPIRequestTestsCsv { - - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsValidAPIRequestTestsCsv.class); - - private final static HttpHeaders httpHeaders = new HttpHeaders(); - private final static String baseURL = "http://localhost:"; - private final static String getAssertionsEndpoint = "/raire/get-assertions-csv"; - private final static String candidatesAsJson = "\"candidates\":[\"Alice\",\"Bob\",\"Chuan\",\"Diego\"]}"; - private final static List trickyCharacters - = List.of("Annoying, Alice", "\"Breaking, Bob\"", "Challenging, Chuan", "O'Difficult, Diego"); - - private final static String trickyCharactersAsJson = - "\"candidates\":[\"Annoying, Alice\",\"\\\"Breaking, Bob\\\"\",\"Challenging, Chuan\",\"O'Difficult, Diego\"]}"; - - @LocalServerPort - private int port; - - - @Autowired - private TestRestTemplate restTemplate; - - @BeforeAll - public static void before() { - httpHeaders.setContentType(MediaType.APPLICATION_JSON); - } - - /** - * Test proper csv file generation of assertions when those assertions have lots of ties. These - * maxima and minima have been manually computed to make sure they're correct. - */ - @Test - public void testValidRequestWithNoAssertions() { - testUtils.log(logger, "testValidRequestWithNoAssertions"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = - "{\"riskLimit\":0.10,\"contestName\":\"Lots of assertions with ties Contest\"," - + candidatesAsJson; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is2xxSuccessful()); - String output = response.getBody(); - - assertNotNull(output); - assertTrue(output.contains("Contest name, Lots of assertions with ties Contest\n")); - assertTrue(output.contains("Candidates, \"Alice, Bob, Chuan, Diego\"\n\n")); - assertTrue(output.contains("Extreme item, Value, Assertion IDs")); - assertTrue(output.contains("Margin, 220, \"2, 5, 6\"")); - assertTrue(output.contains("Diluted margin, 0.22, \"2, 5, 6\"")); - assertTrue(output.contains("Raire difficulty, 3.1, 3")); - assertTrue(output.contains("Current risk, 0.23, \"2, 3\"")); - assertTrue(output.contains("Optimistic samples to audit, 910, 4")); - assertTrue(output.contains("Estimated samples to audit, 430, \"2, 5\"\n\n")); - assertTrue(output.contains( - "ID, Type, Winner, Loser, Assumed continuing, Difficulty, Margin, Diluted margin, Risk, " - + "Estimated samples to audit, Optimistic samples to audit, Two vote over count, " - + "One vote over count, Other discrepancy count, One vote under count, " - + "Two vote under count\n" - )); - assertTrue(output.contains("1, NEB, Alice, Bob, , 2.1, 320, 0.32, 0.04, 110, 100, 0, 0, 0, 0, 0\n")); - assertTrue(output.contains("2, NEB, Chuan, Bob, , 1.1, 220, 0.22, 0.23, 430, 200, 0, 0, 0, 0, 0\n")); - assertTrue(output.contains("3, NEB, Diego, Chuan, , 3.1, 320, 0.32, 0.23, 50, 110, 0, 0, 0, 0, 0\n")); - assertTrue(output.contains( - "4, NEN, Alice, Bob, \"Alice, Bob, Chuan\", 2.0, 420, 0.42, 0.04, 320, 910, 0, 0, 0, 0, 0\n" - )); - assertTrue(output.contains( - "5, NEN, Alice, Diego, \"Alice, Diego\", 1.1, 220, 0.22, 0.07, 430, 210, 0, 0, 0, 0, 0\n" - )); - assertTrue(output.contains( - "6, NEN, Alice, Bob, \"Alice, Bob, Diego\", 1.2, 220, 0.22, 0.04, 400, 110, 0, 0, 0, 0, 0\n" - )); - } - - /** - * Test for difficult characters in candidate names, including ' and " and , - */ - @Test - public void testCharacterEscapingForCSVExport() { - testUtils.log(logger, "testCharacterEscapingForCSVExport"); - String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = - "{\"riskLimit\":0.10,\"contestName\":\"Lots of tricky characters Contest\"," - + trickyCharactersAsJson; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is2xxSuccessful()); - String output = response.getBody(); - - assertNotNull(output); - assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(0))); - assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(1))); - assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(2))); - assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(3))); - } - - /** - * A simple test for correct generation on a simple test case with one assertion of each type. - */ - @Test - public void testCSVDemoContest() { - testUtils.log(logger, "testCSVDemoContest"); - String url = baseURL + port + getAssertionsEndpoint; - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"CSV Demo Contest\"," - + candidatesAsJson; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - String output = response.getBody(); - - assertNotNull(output); - assertTrue(output.contains("Contest name, CSV Demo Contest\n")); - assertTrue(output.contains("Candidates, \"Alice, Bob, Chuan, Diego\"\n\n")); - assertTrue(output.contains("Extreme item, Value, Assertion IDs\n")); - assertTrue(output.contains("Margin, 100, 2\n")); - assertTrue(output.contains("Diluted margin, 0.1, 2\n")); - assertTrue(output.contains("Raire difficulty, 6.1, 2\n")); - assertTrue(output.contains("Current risk, 0.06, 1\n")); - assertTrue(output.contains("Optimistic samples to audit, 45, 2\n")); - assertTrue(output.contains("Estimated samples to audit, 55, 1\n")); - assertTrue(output.contains( - "ID, Type, Winner, Loser, Assumed continuing, Difficulty, Margin, Diluted margin, Risk, " - + "Estimated samples to audit, Optimistic samples to audit, Two vote over count, " - + "One vote over count, Other discrepancy count, One vote under count, " - + "Two vote under count\n")); - assertTrue(output.contains("1, NEB, Bob, Alice, , 5.1, 112, 0.112, 0.06, 55, 35, 0, 2, 0, 0, 0\n")); - assertTrue(output.contains( - "2, NEN, Diego, Chuan, \"Alice, Chuan, Diego\", 6.1, 100, 0.1, 0.05, 45, 45, 0, 0, 0, 0, 0\n" - )); - } - - /** - * A request with candidates who are inconsistent with the assertions in the database is an error. - */ - @Test - public void wrongCandidatesIsAnError() { - testUtils.log(logger, "wrongCandidatesIsAnError"); - String url = baseURL + port + getAssertionsEndpoint; - - String requestAsJson = "{\"riskLimit\":0.10,\"contestName\":\"CSV Demo Contest\"," - + "\"candidates\":[\"Alicia\",\"Boba\",\"Chuan\",\"Diego\"]}"; - - HttpEntity request = new HttpEntity<>(requestAsJson, httpHeaders); - ResponseEntity response = restTemplate.postForEntity(url, request, String.class); - - assertTrue(response.getStatusCode().is5xxServerError()); - assertEquals(WRONG_CANDIDATE_NAMES.toString(), - response.getHeaders().getFirst("error_code")); - } -} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/persistence/repository/AssertionRepositoryInProgressTests.java b/src/test/java/au/org/democracydevelopers/raireservice/persistence/repository/AssertionRepositoryInProgressTests.java index 540e6af3..d409588f 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/persistence/repository/AssertionRepositoryInProgressTests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/persistence/repository/AssertionRepositoryInProgressTests.java @@ -107,7 +107,7 @@ void retrieveAssertionsOneNEBAssertionConvert() throws RaireServiceException { assertEquals(1, ((NotEliminatedBefore)aad.assertion).loser); // Check that current risk is 0.5 - assertEquals(0, BigDecimal.valueOf(0.50).compareTo( + assertEquals(0, BigDecimal.valueOf(0.5).compareTo( ((BigDecimal)aad.status.get(Metadata.STATUS_RISK)))); } @@ -131,7 +131,7 @@ void retrieveAssertionsOneNENAssertionInProgress() throws RaireServiceException "Alice", "Charlie", List.of("Alice", "Charlie", "Diego", "Bob"), Map.of(13L, 1, 14L, 0, 15L, 0), 245, 201, 0, 0, 1, - 0, 2, BigDecimal.valueOf(0.20), + 0, 2, BigDecimal.valueOf(0.2), "One NEN Assertion Contest", r)); } @@ -161,7 +161,7 @@ void retrieveAssertionsOneNENOneNEBAssertionInProgress() throws RaireServiceExce "Amanda", "Wendell", List.of("Liesl", "Wendell", "Amanda"), Map.of(13L, 1, 14L, 1, 15L, -2), 300, 200, 1, 0, 2, - 0, 0, BigDecimal.valueOf(0.70), + 0, 0, BigDecimal.valueOf(0.7), "One NEN NEB Assertion Contest", r2)); } } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnNSWTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnNSWTests.java deleted file mode 100644 index 2a5e048e..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnNSWTests.java +++ /dev/null @@ -1,1662 +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.service; - - -import static au.org.democracydevelopers.raireservice.testUtils.difficultyMatchesMax; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import au.org.democracydevelopers.raire.RaireSolution.RaireResultOrError; -import au.org.democracydevelopers.raireservice.persistence.entity.Assertion; -import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; -import au.org.democracydevelopers.raireservice.persistence.repository.CVRContestInfoRepository; -import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; -import au.org.democracydevelopers.raireservice.testUtils; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit.jupiter.EnabledIf; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests to validate the behaviour of Assertion generation on NSW 2021 Mayoral election data. - * Data is loaded in from src/test/resources/NSW2021Data/ - * These tests all pass, but are disabled because loading in all the NSW data takes a long time. - */ -@ActiveProfiles("nsw-testcases") -@SpringBootTest -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -@EnabledIf(value = "${test-strategy.run-nsw-tests}", loadContext = true) -public class GenerateAssertionsOnNSWTests { - - private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsOnNSWTests.class); - - @Autowired - private CVRContestInfoRepository cvrContestInfoRepository; - - @Autowired - AssertionRepository assertionRepository; - - @Autowired - GenerateAssertionsService generateAssertionsService; - - private static final int DEFAULT_TIME_LIMIT=5; - - /** - * Expected data for each NSW contest. - * Difficulties are taken from raire-java::src/test/java/au/org/democracydevelopers/raire/TestNSW - * which in turn tests against raire-rs. - * Winners are taken from the New South Wales official election results at - * https://pastvtr.elections.nsw.gov.au/LG2101/status/mayoral - * The ballotCounts are derived from the data, but double-checked for exact match with the - * NSWEC website. - */ - // Contest Eurobodalla Mayoral - private static final String nameContest_1 = "Eurobodalla Mayoral"; - private static final List choicesContest_1 = List.of("WORTHINGTON Alison","GRACE David", - "SMITH Gary","HATCHER Mat","HARRISON N (Tubby)","POLLOCK Rob","STARMER Karyn"); - private static final int ballotCountContest_1 = 25526; - private static final double difficultyContest_1 = 23.079566003616637; - private static final String winnerContest_1 = "HATCHER Mat"; - - // Contest City of Lake Macquarie Mayoral - private static final String nameContest_2 = "City of Lake Macquarie Mayoral"; - private static final List choicesContest_2 = List.of("FRASER Kay","DAWSON Rosmairi", - "CUBIS Luke","PAULING Jason"); - private static final int ballotCountContest_2 = 130336; - private static final double difficultyContest_2 = 3.1113869658629745; - private static final String winnerContest_2 = "FRASER Kay"; - - // Contest City of Coffs Harbour Mayoral - private static final String nameContest_3 = "City of Coffs Harbour Mayoral"; - private static final List choicesContest_3 = List.of("SWAN Tegan","CECATO George", - "ADENDORFF Michael","JUDGE Tony","PRYCE Rodger","PIKE Donna","AMOS Paul","TOWNLEY Sally", - "ARKAN John","CASSELL Jonathan"); - private static final int ballotCountContest_3 = 45155; - private static final double difficultyContest_3 = 8.571564160971906; - private static final String winnerContest_3 = "AMOS Paul"; - - // Contest Singleton Mayoral - private static final String nameContest_4 = "Singleton Mayoral"; - private static final List choicesContest_4 = List.of("MOORE Sue","THOMPSON Danny", - "JARRETT Tony","CHARLTON Belinda"); - private static final int ballotCountContest_4 = 13755; - private static final double difficultyContest_4 = 12.118942731277533; - private static final String winnerContest_4 = "MOORE Sue"; - - // Contest City of Newcastle Mayoral - private static final String nameContest_5 = "City of Newcastle Mayoral"; - private static final List choicesContest_5 = List.of("CHURCH John","NELMES Nuatali", - "HOLDING Rod","MACKENZIE John","O'BRIEN Steve","BARRIE Jenny"); - private static final int ballotCountContest_5 = 100275; - private static final double difficultyContest_5 = 5.913487055493307; - private static final String winnerContest_5 = "NELMES Nuatali"; - - // Contest Nambucca Valley Mayoral - private static final String nameContest_6 = "Nambucca Valley Mayoral"; - private static final List choicesContest_6 = List.of("JENVEY Susan","HOBAN Rhonda"); - private static final int ballotCountContest_6 = 12482; - private static final double difficultyContest_6 = 2.7360806663743973; - private static final String winnerContest_6 = "HOBAN Rhonda"; - - // Contest City of Maitland Mayoral - private static final String nameContest_7 = "City of Maitland Mayoral"; - private static final List choicesContest_7 = List.of("BROWN John","MITCHELL Ben", - "BAKER Loretta","PENFOLD Philip","SAFFARI Shahriar (Sean)","COOPER Michael","BURKE Brian"); - private static final int ballotCountContest_7 = 54181; - private static final double difficultyContest_7 = 47.072980017376196; - private static final String winnerContest_7 = "PENFOLD Philip"; - - // Contest Kempsey Mayoral - private static final String nameContest_8 = "Kempsey Mayoral"; - private static final List choicesContest_8 = List.of("HAUVILLE Leo","EVANS Andrew", - "BAIN Arthur","CAMPBELL Liz","SAUL Dean","IRWIN Troy","RAEBURN Bruce"); - private static final int ballotCountContest_8 = 17585; - private static final double difficultyContest_8 = 45.43927648578811; - private static final String winnerContest_8 = "HAUVILLE Leo"; - - // Contest Canada Bay Mayoral - private static final String nameContest_9 = "Canada Bay Mayoral"; - private static final List choicesContest_9 = List.of("TSIREKAS Angelo","LITTLE Julia", - "MEGNA Michael","JAGO Charles","RAMONDINO Daniela"); - private static final int ballotCountContest_9 = 48542; - private static final double difficultyContest_9 = 8.140533288613113; - private static final String winnerContest_9 = "TSIREKAS Angelo"; - - // Contest Richmond Valley Mayoral - private static final String nameContest_10 = "Richmond Valley Mayoral"; - private static final List choicesContest_10 = List.of("MUSTOW Robert","HAYES Robert"); - private static final int ballotCountContest_10 = 13405; - private static final double difficultyContest_10 = 2.302868922865487; - private static final String winnerContest_10 = "MUSTOW Robert"; - - // Contest City of Sydney Mayoral - private static final String nameContest_11 = "City of Sydney Mayoral"; - private static final List choicesContest_11 = List.of("VITHOULKAS Angela", - "WELDON Yvonne","SCOTT Linda","JARRETT Shauna","ELLSMORE Sylvie","MOORE Clover"); - private static final int ballotCountContest_11 = 118511; - private static final double difficultyContest_11 = 3.6873366521468576; - private static final String winnerContest_11 = "MOORE Clover"; - - // Contest Byron Mayoral - private static final String nameContest_12 = "Byron Mayoral"; - private static final List choicesContest_12 = List.of("HUNTER Alan","CLARKE Bruce", - "COOREY Cate","ANDERSON John","MCILRATH Christopher","LYON Michael","DEY Duncan", - "PUGH Asren","SWIVEL Mark"); - private static final int ballotCountContest_12 = 18165; - private static final double difficultyContest_12 = 17.13679245283019; - private static final String winnerContest_12 = "LYON Michael"; - - // Contest City of Broken Hill Mayoral - private static final String nameContest_13 = "City of Broken Hill Mayoral"; - private static final List choicesContest_13 = List.of("TURLEY Darriea","KENNEDY Tom", - "GALLAGHER Dave"); - private static final int ballotCountContest_13 = 10812; - private static final double difficultyContest_13 = 3.2773567747802366; - private static final String winnerContest_13 = "KENNEDY Tom"; - - // Contest City of Shellharbour Mayoral - private static final String nameContest_14 = "City of Shellharbour Mayoral"; - private static final List choicesContest_14 = List.of("HOMER Chris","SALIBA Marianne"); - private static final int ballotCountContest_14 = 46273; - private static final double difficultyContest_14 = 17.83159922928709; - private static final String winnerContest_14 = "HOMER Chris"; - - // Contest City of Shoalhaven Mayoral - private static final String nameContest_15 = "City of Shoalhaven Mayoral"; - private static final List choicesContest_15 = List.of("GREEN Paul","KITCHENER Mark", - "WHITE Patricia","WATSON Greg","DIGIGLIO Nina","FINDLEY Amanda"); - private static final int ballotCountContest_15 = 67030; - private static final double difficultyContest_15 = 41.53035935563817; - private static final String winnerContest_15 = "FINDLEY Amanda"; - - // Contest Mosman Mayoral - private static final String nameContest_16 = "Mosman Mayoral"; - private static final List choicesContest_16 = List.of("MOLINE Libby","BENDALL Roy", - "HARDING Sarah","CORRIGAN Carolyn","MENZIES Simon"); - private static final int ballotCountContest_16 = 16425; - private static final double difficultyContest_16 = 4.498767460969598; - private static final String winnerContest_16 = "CORRIGAN Carolyn"; - - // Contest City of Orange Mayoral - private static final String nameContest_17 = "City of Orange Mayoral"; - private static final List choicesContest_17 = List.of("HAMLING Jason","SPALDING Amanda", - "JONES Neil","WHITTON Jeffery","DUFFY Kevin","SMITH Lesley","MILETO Tony"); - private static final int ballotCountContest_17 = 24355; - private static final double difficultyContest_17 = 50.01026694045174; - private static final String winnerContest_17 = "HAMLING Jason"; - - // Contest City of Wollongong Mayoral - private static final String nameContest_18 = "City of Wollongong Mayoral"; - private static final List choicesContest_18 = List.of("GLYKIS Marie","DORAHY John", - "BROWN Tania","BRADBERY Gordon","ANTHONY Andrew","COX Mithra"); - private static final int ballotCountContest_18 = 127240; - private static final double difficultyContest_18 = 47.72693173293323; - private static final String winnerContest_18 = "BRADBERY Gordon"; - - // Contest Port Stephens Mayoral - private static final String nameContest_19 = "Port Stephens Mayoral"; - private static final List choicesContest_19 = List.of("ANDERSON Leah","PALMER Ryan"); - private static final int ballotCountContest_19 = 47807; - private static final double difficultyContest_19 = 84.31569664902999; - private static final String winnerContest_19 = "PALMER Ryan"; - - // Contest Wollondilly Mayoral - private static final String nameContest_20 = "Wollondilly Mayoral"; - private static final List choicesContest_20 = List.of("KHAN Robert","BANASIK Michael", - "DEETH Matthew","LAW Ray","GOULD Matt","HANNAN Judy"); - private static final int ballotCountContest_20 = 31355; - private static final double difficultyContest_20 = 24.40077821011673; - private static final String winnerContest_20 = "GOULD Matt"; - - // Contest Hornsby Mayoral - private static final String nameContest_21 = "Hornsby Mayoral"; - private static final List choicesContest_21 = List.of("HEYDE Emma","RUDDOCK Philip"); - private static final int ballotCountContest_21 = 85656; - private static final double difficultyContest_21 = 6.866762866762866; - private static final String winnerContest_21 = "RUDDOCK Philip"; - - // Contest Ballina Mayoral - private static final String nameContest_22 = "Ballina Mayoral"; - private static final List choicesContest_22 = List.of("WILLIAMS Keith","JOHNSON Jeff", - "MCCARTHY Steve","JOHNSTON Eoin","CADWALLADER Sharon"); - private static final int ballotCountContest_22 = 26913; - private static final double difficultyContest_22 = 7.285598267460747; - private static final String winnerContest_22 = "CADWALLADER Sharon"; - - // Contest Bellingen Mayoral - private static final String nameContest_23 = "Bellingen Mayoral"; - private static final List choicesContest_23 = List.of("ALLAN Steve","WOODWARD Andrew", - "KING Dominic"); - private static final int ballotCountContest_23 = 8374; - private static final double difficultyContest_23 = 3.3335987261146496; - private static final String winnerContest_23 = "ALLAN Steve"; - - // Contest City of Lismore Mayoral - private static final String nameContest_24 = "City of Lismore Mayoral"; - private static final List choicesContest_24 = List.of("KRIEG Steve","COOK Darlene", - "HEALEY Patrick","GRINDON-EKINS Vanessa","ROB Big","BIRD Elly"); - private static final int ballotCountContest_24 = 26474; - private static final double difficultyContest_24 = 2.929836210712705; - private static final String winnerContest_24 = "KRIEG Steve"; - - // Contest City of Willoughby Mayoral - private static final String nameContest_25 = "City of Willoughby Mayoral"; - private static final List choicesContest_25 = List.of("ROZOS Angelo","CAMPBELL Craig", - "TAYLOR Tanya"); - private static final int ballotCountContest_25 = 37942; - private static final double difficultyContest_25 = 14.990912682734097; - private static final String winnerContest_25 = "TAYLOR Tanya"; - - // Contest The Hills Shire Mayoral - private static final String nameContest_26 = "The Hills Shire Mayoral"; - private static final List choicesContest_26 = List.of("SHAHAMAT Vida","GANGEMI Peter", - "ROZYCKI Jerzy (George)","TRACEY Ryan","YAZDANI Ereboni (Alexia)"); - private static final int ballotCountContest_26 = 105384; - private static final double difficultyContest_26 = 3.6801229221958374; - private static final String winnerContest_26 = "GANGEMI Peter"; - - // Contest City of Cessnock Mayoral - private static final String nameContest_27 = "City of Cessnock Mayoral"; - private static final List choicesContest_27 = List.of("MURRAY Janet","SUVAAL Jay", - "MOORES John","OLSEN Ian"); - private static final int ballotCountContest_27 = 36497; - private static final double difficultyContest_27 = 6.466513111268604; - private static final String winnerContest_27 = "SUVAAL Jay"; - - // Contest City of Griffith Mayoral - private static final String nameContest_28 = "City of Griffith Mayoral"; - private static final List choicesContest_28 = List.of("MERCURI Rina","NAPOLI Anne", - "ZAPPACOSTA Dino","LA ROCCA Mariacarmina (Carmel)","CURRAN Doug"); - private static final int ballotCountContest_28 = 14179; - private static final double difficultyContest_28 = 3.9320576816417083; - private static final String winnerContest_28 = "CURRAN Doug"; - - // Contest Port Macquarie-Hastings Mayoral - private static final String nameContest_29 = "Port Macquarie-Hastings Mayoral"; - private static final List choicesContest_29 = List.of("PINSON Peta","GATES Steven", - "SHEPPARD Rachel","INTEMANN Lisa","LIPOVAC Nik"); - private static final int ballotCountContest_29 = 54499; - private static final double difficultyContest_29 = 2.8524547262640008; - private static final String winnerContest_29 = "PINSON Peta"; - - // Contest City of Liverpool Mayoral - private static final String nameContest_30 = "City of Liverpool Mayoral"; - private static final List choicesContest_30 = List.of("HAGARTY Nathan","MORSHED Asm", - "ANDJELKOVIC Milomir (Michael)","HARLE Peter","MANNOUN Ned"); - private static final int ballotCountContest_30 = 115177; - private static final double difficultyContest_30 = 45.416798107255524; - private static final String winnerContest_30 = "MANNOUN Ned"; - - // Contest Uralla Mayoral - private static final String nameContest_31 = "Uralla Mayoral"; - private static final List choicesContest_31 = List.of("BELL Robert","LEDGER Natasha", - "STRUTT Isabel"); - private static final int ballotCountContest_31 = 3781; - private static final double difficultyContest_31 = 1.6297413793103448; - private static final String winnerContest_31 = "BELL Robert"; - - // Contest Hunter's Hill Mayoral - private static final String nameContest_32 = "Hunter's Hill Mayoral"; - private static final List choicesContest_32 = List.of("GUAZZAROTTO David","MILES Zac", - "QUINN Richard","WILLIAMS Ross"); - private static final int ballotCountContest_32 = 8356; - private static final double difficultyContest_32 = 38.330275229357795; - private static final String winnerContest_32 = "MILES Zac"; - - // Contest Burwood Mayoral - private static final String nameContest_33 = "Burwood Mayoral"; - private static final List choicesContest_33 = List.of("HULL David","MURRAY Alan", - "CUTCHER Ned","FAKER John"); - private static final int ballotCountContest_33 = 17797; - private static final double difficultyContest_33 = 2.5269061479483175; - private static final String winnerContest_33 = "FAKER John"; - - /** - * Contest 1. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest1() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest1"); - List retrieved = cvrContestInfoRepository.getCVRs(1, 1); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_1.contains(retrievedFirstChoice)); - } - - /** - * Contest 1. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest1() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest1"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_1, - ballotCountContest_1, DEFAULT_TIME_LIMIT, choicesContest_1); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_1, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_1); - assertTrue(difficultyMatchesMax(difficultyContest_1, assertions)); - } - - /** - * Contest 2. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest2() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest2"); - List retrieved = cvrContestInfoRepository.getCVRs(2, 2); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_2.contains(retrievedFirstChoice)); - } - - /** - * Contest 2. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest2() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest2"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_2, - ballotCountContest_2, DEFAULT_TIME_LIMIT, choicesContest_2); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_2, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_2); - assertTrue(difficultyMatchesMax(difficultyContest_2, assertions)); - } - - /** - * Contest 3. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest3() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest3"); - List retrieved = cvrContestInfoRepository.getCVRs(3, 3); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_3.contains(retrievedFirstChoice)); - } - - /** - * Contest 3. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest3() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest3"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_3, - ballotCountContest_3, DEFAULT_TIME_LIMIT, choicesContest_3); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_3, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_3); - assertTrue(difficultyMatchesMax(difficultyContest_3, assertions)); - } - - /** - * Contest 4. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest4() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest4"); - List retrieved = cvrContestInfoRepository.getCVRs(4, 4); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_4.contains(retrievedFirstChoice)); - } - - /** - * Contest 4. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest4() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest4"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_4, - ballotCountContest_4, DEFAULT_TIME_LIMIT, choicesContest_4); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_4, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_4); - assertTrue(difficultyMatchesMax(difficultyContest_4, assertions)); - } - - /** - * Contest 5. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest5() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest5"); - List retrieved = cvrContestInfoRepository.getCVRs(5, 5); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_5.contains(retrievedFirstChoice)); - } - - /** - * Contest 5. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest5() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest5"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_5, - ballotCountContest_5, DEFAULT_TIME_LIMIT, choicesContest_5); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_5, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_5); - assertTrue(difficultyMatchesMax(difficultyContest_5, assertions)); - } - - /** - * Contest 6. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest6() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest6"); - List retrieved = cvrContestInfoRepository.getCVRs(6, 6); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_6.contains(retrievedFirstChoice)); - } - - /** - * Contest 6. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest6() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest6"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_6, - ballotCountContest_6, DEFAULT_TIME_LIMIT, choicesContest_6); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_6, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_6); - assertTrue(difficultyMatchesMax(difficultyContest_6, assertions)); - } - - /** - * Contest 7. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest7() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest7"); - List retrieved = cvrContestInfoRepository.getCVRs(7, 7); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_7.contains(retrievedFirstChoice)); - } - - /** - * Contest 7. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest7() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest7"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_7, - ballotCountContest_7, DEFAULT_TIME_LIMIT, choicesContest_7); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_7, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_7); - assertTrue(difficultyMatchesMax(difficultyContest_7, assertions)); - } - - /** - * Contest 8. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest8() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest8"); - List retrieved = cvrContestInfoRepository.getCVRs(8, 8); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_8.contains(retrievedFirstChoice)); - } - - /** - * Contest 8. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest8() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest8"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_8, - ballotCountContest_8, DEFAULT_TIME_LIMIT, choicesContest_8); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_8, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_8); - assertTrue(difficultyMatchesMax(difficultyContest_8, assertions)); - } - - /** - * Contest 9. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest9() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest9"); - List retrieved = cvrContestInfoRepository.getCVRs(9, 9); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_9.contains(retrievedFirstChoice)); - } - - /** - * Contest 9. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest9() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest9"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_9, - ballotCountContest_9, DEFAULT_TIME_LIMIT, choicesContest_9); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_9, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_9); - assertTrue(difficultyMatchesMax(difficultyContest_9, assertions)); - } - - /** - * Contest 10. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest10() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest10"); - List retrieved = cvrContestInfoRepository.getCVRs(10, 10); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_10.contains(retrievedFirstChoice)); - } - - /** - * Contest 10. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest10() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest10"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_10, - ballotCountContest_10, DEFAULT_TIME_LIMIT, choicesContest_10); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_10, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_10); - assertTrue(difficultyMatchesMax(difficultyContest_10, assertions)); - } - - /** - * Contest 11. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest11() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest11"); - List retrieved = cvrContestInfoRepository.getCVRs(11, 11); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_11.contains(retrievedFirstChoice)); - } - - /** - * Contest 11. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest11() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest11"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_11, - ballotCountContest_11, DEFAULT_TIME_LIMIT, choicesContest_11); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_11, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_11); - assertTrue(difficultyMatchesMax(difficultyContest_11, assertions)); - } - - /** - * Contest 12. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest12() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest12"); - List retrieved = cvrContestInfoRepository.getCVRs(12, 12); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_12.contains(retrievedFirstChoice)); - } - - /** - * Contest 12. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest12() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest12"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_12, - ballotCountContest_12, DEFAULT_TIME_LIMIT, choicesContest_12); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_12, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_12); - assertTrue(difficultyMatchesMax(difficultyContest_12, assertions)); - } - - /** - * Contest 13. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest13() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest13"); - List retrieved = cvrContestInfoRepository.getCVRs(13, 13); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_13.contains(retrievedFirstChoice)); - } - - /** - * Contest 13. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest13() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest13"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_13, - ballotCountContest_13, DEFAULT_TIME_LIMIT, choicesContest_13); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_13, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_13); - assertTrue(difficultyMatchesMax(difficultyContest_13, assertions)); - } - - /** - * Contest 14. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest14() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest14"); - List retrieved = cvrContestInfoRepository.getCVRs(14, 14); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_14.contains(retrievedFirstChoice)); - } - - /** - * Contest 14. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest14() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest14"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_14, - ballotCountContest_14, DEFAULT_TIME_LIMIT, choicesContest_14); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_14, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_14); - assertTrue(difficultyMatchesMax(difficultyContest_14, assertions)); - } - - /** - * Contest 15. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest15() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest15"); - List retrieved = cvrContestInfoRepository.getCVRs(15, 15); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_15.contains(retrievedFirstChoice)); - } - - /** - * Contest 15. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest15() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest15"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_15, - ballotCountContest_15, DEFAULT_TIME_LIMIT, choicesContest_15); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_15, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_15); - assertTrue(difficultyMatchesMax(difficultyContest_15, assertions)); - } - - /** - * Contest 16. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest16() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest16"); - List retrieved = cvrContestInfoRepository.getCVRs(16, 16); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_16.contains(retrievedFirstChoice)); - } - - /** - * Contest 16. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest16() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest16"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_16, - ballotCountContest_16, DEFAULT_TIME_LIMIT, choicesContest_16); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_16, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_16); - assertTrue(difficultyMatchesMax(difficultyContest_16, assertions)); - } - - /** - * Contest 17. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest17() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest17"); - List retrieved = cvrContestInfoRepository.getCVRs(17, 17); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_17.contains(retrievedFirstChoice)); - } - - /** - * Contest 17. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest17() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest17"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_17, - ballotCountContest_17, DEFAULT_TIME_LIMIT, choicesContest_17); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_17, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_17); - assertTrue(difficultyMatchesMax(difficultyContest_17, assertions)); - } - - /** - * Contest 18. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest18() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest18"); - List retrieved = cvrContestInfoRepository.getCVRs(18, 18); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_18.contains(retrievedFirstChoice)); - } - - /** - * Contest 18. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest18() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest18"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_18, - ballotCountContest_18, DEFAULT_TIME_LIMIT, choicesContest_18); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_18, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_18); - assertTrue(difficultyMatchesMax(difficultyContest_18, assertions)); - } - - /** - * Contest 19. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest19() { - testUtils.log(logger, ""); - List retrieved = cvrContestInfoRepository.getCVRs(19, 19); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_19.contains(retrievedFirstChoice)); - } - - /** - * Contest 19. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest19() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest19"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_19, - ballotCountContest_19, DEFAULT_TIME_LIMIT, choicesContest_19); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_19, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_19); - assertTrue(difficultyMatchesMax(difficultyContest_19, assertions)); - } - - /** - * Contest 20. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest20() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest20"); - List retrieved = cvrContestInfoRepository.getCVRs(20, 20); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_20.contains(retrievedFirstChoice)); - } - - /** - * Contest 20. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest20() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest20"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_20, - ballotCountContest_20, DEFAULT_TIME_LIMIT, choicesContest_20); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_20, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_20); - assertTrue(difficultyMatchesMax(difficultyContest_20, assertions)); - } - - /** - * Contest 21. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest21() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest21"); - List retrieved = cvrContestInfoRepository.getCVRs(21, 21); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_21.contains(retrievedFirstChoice)); - } - - /** - * Contest 21. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest21() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest21"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_21, - ballotCountContest_21, DEFAULT_TIME_LIMIT, choicesContest_21); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_21, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_21); - assertTrue(difficultyMatchesMax(difficultyContest_21, assertions)); - } - - /** - * Contest 22. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest22() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest22"); - List retrieved = cvrContestInfoRepository.getCVRs(22, 22); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_22.contains(retrievedFirstChoice)); - } - - /** - * Contest 22. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest22() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest22"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_22, - ballotCountContest_22, DEFAULT_TIME_LIMIT, choicesContest_22); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_22, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_22); - assertTrue(difficultyMatchesMax(difficultyContest_22, assertions)); - } - - /** - * Contest 23. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest23() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest23"); - List retrieved = cvrContestInfoRepository.getCVRs(23, 23); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_23.contains(retrievedFirstChoice)); - } - - /** - * Contest 23. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest23() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest23"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_23, - ballotCountContest_23, DEFAULT_TIME_LIMIT, choicesContest_23); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_23, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_23); - assertTrue(difficultyMatchesMax(difficultyContest_23, assertions)); - } - - /** - * Contest 24. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest24() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest24"); - List retrieved = cvrContestInfoRepository.getCVRs(24, 24); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_24.contains(retrievedFirstChoice)); - } - - /** - * Contest 24. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest24() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest24"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_24, - ballotCountContest_24, DEFAULT_TIME_LIMIT, choicesContest_24); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_24, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_24); - assertTrue(difficultyMatchesMax(difficultyContest_24, assertions)); - } - - /** - * Contest 25. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest25() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest25"); - List retrieved = cvrContestInfoRepository.getCVRs(25, 25); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_25.contains(retrievedFirstChoice)); - } - - /** - * Contest 25. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest25() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest25"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_25, - ballotCountContest_25, DEFAULT_TIME_LIMIT, choicesContest_25); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_25, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_25); - assertTrue(difficultyMatchesMax(difficultyContest_25, assertions)); - } - - /** - * Contest 26. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest26() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest26"); - List retrieved = cvrContestInfoRepository.getCVRs(26, 26); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_26.contains(retrievedFirstChoice)); - } - - /** - * Contest 26. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest26() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest26"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_26, - ballotCountContest_26, DEFAULT_TIME_LIMIT, choicesContest_26); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_26, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_26); - assertTrue(difficultyMatchesMax(difficultyContest_26, assertions)); - } - - /** - * Contest 27. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest27() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest27"); - List retrieved = cvrContestInfoRepository.getCVRs(27, 27); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_27.contains(retrievedFirstChoice)); - } - - /** - * Contest 27. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest27() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest27"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_27, - ballotCountContest_27, DEFAULT_TIME_LIMIT, choicesContest_27); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_27, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_27); - assertTrue(difficultyMatchesMax(difficultyContest_27, assertions)); - } - - /** - * Contest 28. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest28() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest28"); - List retrieved = cvrContestInfoRepository.getCVRs(28, 28); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_28.contains(retrievedFirstChoice)); - } - - /** - * Contest 28. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest28() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest28"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_28, - ballotCountContest_28, DEFAULT_TIME_LIMIT, choicesContest_28); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_28, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_28); - assertTrue(difficultyMatchesMax(difficultyContest_28, assertions)); - } - - /** - * Contest 29. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest29() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest29"); - List retrieved = cvrContestInfoRepository.getCVRs(29, 29); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_29.contains(retrievedFirstChoice)); - } - - /** - * Contest 29. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest29() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest29"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_29, - ballotCountContest_29, DEFAULT_TIME_LIMIT, choicesContest_29); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_29, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_29); - assertTrue(difficultyMatchesMax(difficultyContest_29, assertions)); - } - - /** - * Contest 30. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest30() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest30"); - List retrieved = cvrContestInfoRepository.getCVRs(30, 30); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_30.contains(retrievedFirstChoice)); - } - - /** - * Contest 30. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest30() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest30"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_30, - ballotCountContest_30, DEFAULT_TIME_LIMIT, choicesContest_30); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_30, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_30); - assertTrue(difficultyMatchesMax(difficultyContest_30, assertions)); - } - - /** - * Contest 31. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest31() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest31"); - List retrieved = cvrContestInfoRepository.getCVRs(31, 31); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_31.contains(retrievedFirstChoice)); - } - - /** - * Contest 31. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest31() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest31"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_31, - ballotCountContest_31, DEFAULT_TIME_LIMIT, choicesContest_31); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_31, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_31); - assertTrue(difficultyMatchesMax(difficultyContest_31, assertions)); - } - - /** - * Contest 32. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest32() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest32"); - List retrieved = cvrContestInfoRepository.getCVRs(32, 32); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_32.contains(retrievedFirstChoice)); - } - - /** - * Contest 32. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest32() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest32"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_32, - ballotCountContest_32, DEFAULT_TIME_LIMIT, choicesContest_32); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winner. - assertEquals(winnerContest_32, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_32); - assertTrue(difficultyMatchesMax(difficultyContest_32, assertions)); - } - - /** - * Contest 33. - * Sanity check to make sure that the first vote's first preference has one of the candidate names - * we expect for that contest. - */ - @Test - @Transactional - void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest33() { - testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest33"); - List retrieved = cvrContestInfoRepository.getCVRs(33, 33); - String retrievedFirstChoice = retrieved.getFirst()[0]; - assertTrue(choicesContest_33.contains(retrievedFirstChoice)); - } - - /** - * Contest 33. - * Generate assertions. Check the winner. - * Then check the difficulty, to ensure it matches what is generated by raire-java directly (which in - * turn has been tested against raire-rs). - * @throws RaireServiceException when an error occurs in assertion generation. - */ - @Test - @Transactional - public void checkDifficulty_contest33() throws RaireServiceException { - testUtils.log(logger, "checkDifficulty_contest33"); - GenerateAssertionsRequest request = new GenerateAssertionsRequest(nameContest_33, - ballotCountContest_33, DEFAULT_TIME_LIMIT, choicesContest_33); - - // Generate assertions. - RaireResultOrError response = generateAssertionsService.generateAssertions(request); - generateAssertionsService.persistAssertions(response.Ok, request); - - // Check winners. - assertEquals(winnerContest_33, request.candidates.get(response.Ok.winner)); - - // Check difficulty. - List assertions = assertionRepository.findByContestName(nameContest_33); - assertTrue(difficultyMatchesMax(difficultyContest_33, assertions)); - } -} \ No newline at end of file diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnKnownTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceKnownTests.java similarity index 96% rename from src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnKnownTests.java rename to src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceKnownTests.java index 88891f4d..941aab7e 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsOnKnownTests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceKnownTests.java @@ -30,7 +30,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import au.org.democracydevelopers.raire.RaireError.TiedWinners; import au.org.democracydevelopers.raire.RaireSolution.RaireResultOrError; import au.org.democracydevelopers.raire.algorithm.RaireResult; import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; @@ -63,7 +62,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra * human-computable assertions. Relevant data is preloaded into the test database from * src/test/resources/known_testcases_votes.sql. * This includes - * - A contest with tied winners, to check that the correct error is produced. * - The examples from the Guide To Raire Vol 2. Exact matching for Ex. 2 and some for Ex. 1. * - A very simple example test with two obvious assertions (an NEN and NEB), described below. * - A cross-county version of the simple example. @@ -79,9 +77,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GenerateAssertionsOnKnownTests { +public class GenerateAssertionsServiceKnownTests { - private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsOnKnownTests.class); + private static final Logger logger = LoggerFactory.getLogger(GenerateAssertionsServiceKnownTests.class); @Autowired AssertionRepository assertionRepository; @@ -98,7 +96,6 @@ public class GenerateAssertionsOnKnownTests { private static final String ThreeAssertionContest = "Sanity Check 3 Assertion Contest"; private static final String guideToRaireExample1 = "Guide To Raire Example 1"; private static final String guideToRaireExample2 = "Guide To Raire Example 2"; - private static final String tiedWinnersContest = "Tied Winners Contest"; private static final String simpleContest = "Simple Contest"; private static final String crossCountySimpleContest = "Cross-county Simple Contest"; @@ -112,10 +109,6 @@ public class GenerateAssertionsOnKnownTests { */ private static final String[] aliceChuanBob = {"Alice", "Chuan", "Bob"}; - private final static GenerateAssertionsRequest tiedWinnersRequest - = new GenerateAssertionsRequest(tiedWinnersContest, 2, 5, - Arrays.stream(aliceChuanBob).toList()); - /** * Check that NEB assertion retrieval works. This is just a sanity check. */ @@ -144,36 +137,19 @@ void NENassertionRetrievalWorks() { assertInstanceOf(NENAssertion.class, assertion); } - - /** - * Tied winners results in raire-java returning a TiedWinners RaireError. - * This is a super-simple election with two candidates with one vote each. - */ - @Test - @Transactional - void tiedWinnersThrowsTiedWinnersError() throws RaireServiceException { - testUtils.log(logger, "tiedWinnersThrowsTiedWinnersError"); - RaireResultOrError result = generateAssertionsService.generateAssertions(tiedWinnersRequest); - - assertNull(result.Ok); - assertNotNull(result.Err); - - assertEquals(TiedWinners.class, result.Err.getClass()); - } - /** - * Some tests of the assertions described in the Guide to Raire Example 1. + * Some tests of the assertions described in the Guide to Raire, Part 2, Example 1. * The test data has 1/500 of the votes, so divide margins by 500. * The difficulties should be the same, because both numerator and denominator should be divided by 500. * We do not test the NEN assertions because the ones in the Guide have some redundancy. - * Test assertion: Alice NEB Bob. + * Test assertion: Chuan NEB Bob. * Margin is 4000, but data is divided by 500, so 8. Difficulty is 3.375 as in the Guide. * Diluted margin is 8/27 = 0.296... */ @Test @Transactional - void testGuideToRaireExample1() throws RaireServiceException { - testUtils.log(logger, "testGuideToRaireExample1"); + void testGuideToRairePart2Example1() throws RaireServiceException { + testUtils.log(logger, "testGuideToRairePart2Example1"); GenerateAssertionsRequest request = new GenerateAssertionsRequest(guideToRaireExample1, 27, 5, Arrays.stream(aliceBobChuanDiego).toList()); @@ -206,7 +182,7 @@ void testGuideToRaireExample1() throws RaireServiceException { @Test @Transactional void testGuideToRaireExample2() throws RaireServiceException { - testUtils.log(logger, "testGuideToRaireExample2"); + testUtils.log(logger, "testGuideToRairePart2Example2"); GenerateAssertionsRequest request = new GenerateAssertionsRequest(guideToRaireExample2, 41, 5, Arrays.stream(aliceChuanBob).toList()); RaireResultOrError response = generateAssertionsService.generateAssertions(request); @@ -726,6 +702,8 @@ void checkGuideToRaireExample2Assertions(List assertions){ "Chuan", "Bob", List.of("Chuan","Bob"), nenAssertion)); } + + /** * Create and return a RaireResult containing assertions that would be generated for second * Guide to Raire example. diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceNSWTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceNSWTests.java new file mode 100644 index 00000000..fc0fbf0a --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceNSWTests.java @@ -0,0 +1,122 @@ +/* +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.service; + + +import static au.org.democracydevelopers.raireservice.NSWValues.expectedSolutionData; +import static au.org.democracydevelopers.raireservice.testUtils.difficultyMatchesMax; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import au.org.democracydevelopers.raire.RaireSolution.RaireResultOrError; +import au.org.democracydevelopers.raireservice.NSWValues.Expected; +import au.org.democracydevelopers.raireservice.persistence.entity.Assertion; +import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; +import au.org.democracydevelopers.raireservice.persistence.repository.CVRContestInfoRepository; +import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; +import au.org.democracydevelopers.raireservice.testUtils; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests to validate the behaviour of Assertion generation on NSW 2021 Mayoral election data. + * Data is loaded in from src/test/resources/NSW2021Data/ + * These tests all pass, but are disabled because loading in all the NSW data takes a long time. + */ +@ActiveProfiles("nsw-testcases") +@SpringBootTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +@EnabledIf(value = "${test-strategy.run-nsw-tests}", loadContext = true) +public class GenerateAssertionsServiceNSWTests { + + private static final Logger logger = LoggerFactory.getLogger( + GenerateAssertionsServiceNSWTests.class); + + @Autowired + private CVRContestInfoRepository cvrContestInfoRepository; + + @Autowired + AssertionRepository assertionRepository; + + @Autowired + GenerateAssertionsService generateAssertionsService; + + private static final int DEFAULT_TIME_LIMIT = 5; + + /** + * Sanity check to make sure that the first contest's first vote's first preference has one of the + * candidate names we expect for that contest. + */ + @Test + @Transactional + void firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest1() { + testUtils.log(logger, "firstPreferenceOfFirstVoteHasAnExpectedCandidateName_contest1"); + List retrieved = cvrContestInfoRepository.getCVRs(1, 1); + String retrievedFirstChoice = retrieved.getFirst()[0]; + assertTrue(expectedSolutionData.getFirst().choices().contains(retrievedFirstChoice)); + } + + /** + * Iterate through all the NSW example contests, + * - request assertion generation from the service, + * - check the winner, + * - save the assertions in the database, + * - retrieve the assertions from the database, + * - verify the maximum expected difficulty. + * @throws RaireServiceException if either assertion generation or assertion retrieval fail. + */ + @Test + @Transactional + public void checkAllNSWByService() throws RaireServiceException { + + for (Expected expected : expectedSolutionData) { + testUtils.log(logger, "checkAllNSWByService: contest " + expected.contestName()); + + GenerateAssertionsRequest request = new GenerateAssertionsRequest(expected.contestName(), + expected.ballotCount(), DEFAULT_TIME_LIMIT, expected.choices()); + + // Generate assertions. + RaireResultOrError response = generateAssertionsService.generateAssertions(request); + // Check the winner. + assertEquals(expected.winner(), request.candidates.get(response.Ok.winner)); + + // Save the assertions + generateAssertionsService.persistAssertions(response.Ok, request); + + // Check difficulty. + List assertions = assertionRepository.findByContestName(expected.contestName()); + assertTrue(difficultyMatchesMax(expected.difficulty(), assertions)); + } + } +} + diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceWickedTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceWickedTests.java new file mode 100644 index 00000000..0927a846 --- /dev/null +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GenerateAssertionsServiceWickedTests.java @@ -0,0 +1,153 @@ +/* +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.service; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import au.org.democracydevelopers.raire.RaireError.TiedWinners; +import au.org.democracydevelopers.raire.RaireError.TimeoutCheckingWinner; +import au.org.democracydevelopers.raire.RaireError.TimeoutFindingAssertions; +import au.org.democracydevelopers.raire.RaireSolution.RaireResultOrError; +import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; +import au.org.democracydevelopers.raireservice.request.GenerateAssertionsRequest; +import au.org.democracydevelopers.raireservice.testUtils; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +/** + * Tests to validate the behavior of Assertion generation on a collection of particularly nasty + * test cases designed to elicit errors. These kinds of errors _are_ expected to happen occasionally + * in normal operation, if the input data is particularly challenging. + * This has the same tests as GenerateAssertionsAPIWickedTests.java. Relevant data is preloaded into + * the test database from src/test/resources/known_testcases_votes.sql. + * This includes + * - a contest with tied winners, + * - a contest that times out trying to find the winners (there are 20 and they are all tied), + * - a contest (Byron Mayor '21) that has enough candidates to time out generating assertions (when + * given a very short timeout). + */ +@ActiveProfiles("known-testcases") +@SpringBootTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class GenerateAssertionsServiceWickedTests { + + private static final Logger logger = LoggerFactory.getLogger( + GenerateAssertionsServiceWickedTests.class); + + @Autowired + AssertionRepository assertionRepository; + + @Autowired + GenerateAssertionsService generateAssertionsService; + + /** + * Names of contests, to match preloaded data. + */ + private static final String tiedWinnersContest = "Tied Winners Contest"; + private static final String ByronMayoral = "Byron Mayoral"; + private static final String timeOutCheckingWinnersContest = "Time out checking winners contest"; + + /** + * Candidate lists for the preloaded contests. + */ + private static final List aliceChuanBob = List.of("Alice", "Chuan", "Bob"); + private static final List choicesByron = List.of("HUNTER Alan","CLARKE Bruce", + "COOREY Cate","ANDERSON John","MCILRATH Christopher","LYON Michael","DEY Duncan", + "PUGH Asren","SWIVEL Mark"); + private static final List timeoutCheckingWinnersChoices = List.of("A","B","C","D","E", + "F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T"); + + + /** + * The API requests appropriate for each preloaded contest. Those intended to produce a timeout + * have a particularly small timeLimit. + */ + private final static GenerateAssertionsRequest tiedWinnersRequest + = new GenerateAssertionsRequest(tiedWinnersContest, 2, 5, + aliceChuanBob); + private final static GenerateAssertionsRequest ByronShortTimeoutRequest + = new GenerateAssertionsRequest(ByronMayoral, 18165, 0.001, + choicesByron); + private final static GenerateAssertionsRequest checkingWinnersTimeoutRequest + = new GenerateAssertionsRequest(timeOutCheckingWinnersContest, 20, + 0.001, timeoutCheckingWinnersChoices); + + /** + * Tied winners results in raire-java returning a TiedWinners RaireError. This is a super-simple + * election with two candidates with one vote each. + */ + @Test + @Transactional + void tiedWinnersThrowsTiedWinnersError() throws RaireServiceException { + testUtils.log(logger, "tiedWinnersThrowsTiedWinnersError"); + RaireResultOrError result = generateAssertionsService.generateAssertions(tiedWinnersRequest); + + assertNull(result.Ok); + assertNotNull(result.Err); + + assertInstanceOf(TiedWinners.class, result.Err); + } + + /** + * A huge number of tied winners results in raire-java returning a TimeOutCheckingWinners + * RaireError. This election has 20 candidates who are all tied. + */ + @Test + @Transactional + void twentyTiedWinnersThrowsTimeOutCheckingWinnersError() throws RaireServiceException { + testUtils.log(logger, "twentyTiedWinnersThrowsTimeOutCheckingWinnersError"); + RaireResultOrError result = generateAssertionsService + .generateAssertions(checkingWinnersTimeoutRequest); + + assertNull(result.Ok); + assertNotNull(result.Err); + + assertInstanceOf(TimeoutCheckingWinner.class, result.Err); + } + + /** + * Byron Mayoral times out generating assertions when given a very very short timeout. + */ + @Test + @Transactional + void ByronWithShortTimeoutThrowsTimeoutGeneratingAssertionsError() throws RaireServiceException { + testUtils.log(logger, "ByronWithShortTimeoutThrowsTimeoutGeneratingAssertionsError"); + RaireResultOrError result = generateAssertionsService.generateAssertions(ByronShortTimeoutRequest); + + assertNull(result.Ok); + assertNotNull(result.Err); + + assertInstanceOf(TimeoutFindingAssertions.class, result.Err); + } +} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceInProgressTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsInProgressServiceJsonAndCsvTests.java similarity index 59% rename from src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceInProgressTests.java rename to src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsInProgressServiceJsonAndCsvTests.java index 1bb7127b..2aa7c268 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceInProgressTests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsInProgressServiceJsonAndCsvTests.java @@ -20,6 +20,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.service; +import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -30,11 +31,8 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty; import au.org.democracydevelopers.raire.assertions.NotEliminatedBefore; import au.org.democracydevelopers.raire.assertions.NotEliminatedNext; -import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.testUtils; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import java.math.BigDecimal; import java.util.List; import org.junit.jupiter.api.Test; @@ -54,44 +52,40 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra * preloaded into the test database from: src/test/resources/assertions_in_progress.sql. * Note that tests of GetAssertionsJsonService have been spread across several test classes, each * defined with respect to a different test container. + * There is a json version and a csv version of the same test. */ @ActiveProfiles("assertions-in-progress") @SpringBootTest @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsJsonServiceInProgressTests { +public class GetAssertionsInProgressServiceJsonAndCsvTests { private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsJsonServiceInProgressTests.class); + GetAssertionsInProgressServiceJsonAndCsvTests.class); @Autowired - AssertionRepository assertionRepository; - - /** - * To facilitate easier checking of retrieved/saved assertion content. - */ - private static final Gson GSON = - new GsonBuilder().serializeNulls().disableHtmlEscaping().create(); + GetAssertionsJsonService getAssertionsJsonService; + @Autowired + GetAssertionsCsvService getAssertionsCsvService; /** * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). + * (JSON). */ @Test @Transactional - void retrieveAssertionsOneNENOneNEBAssertionInProgress() throws RaireServiceException { - testUtils.log(logger, "retrieveAssertionsOneNENOneNEBAssertionInProgress"); - GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); + void retrieveAssertionsOneNENOneNEBAssertionInProgressJSON() throws RaireServiceException { + testUtils.log(logger, "retrieveAssertionsOneNENOneNEBAssertionInProgressJSON"); GetAssertionsRequest request = new GetAssertionsRequest("One NEN NEB Assertion Contest", - List.of("Liesl", "Wendell", "Amanda", "Chuan"), new BigDecimal("0.05")); + List.of("Liesl", "Wendell", "Amanda", "Chuan"), BigDecimal.valueOf(0.05)); - RaireSolution solution = service.getRaireSolution(request); + RaireSolution solution = getAssertionsJsonService.getRaireSolution(request); // Check that the metadata has been constructed appropriately - final String metadata = "{\"candidates\":[\"Liesl\",\"Wendell\",\"Amanda\",\"Chuan\"]," + - "\"contest\":\"One NEN NEB Assertion Contest\",\"risk_limit\":0.05}"; - - assertEquals(metadata, GSON.toJson(solution.metadata)); + assertTrue(correctMetadata(List.of("Liesl","Wendell","Amanda","Chuan"), + "One NEN NEB Assertion Contest", BigDecimal.valueOf(0.05), solution.metadata, + BigDecimal.class)); // The RaireSolution contains a RaireResultOrError, but the error should be null. assertNull(solution.solution.Err); @@ -106,7 +100,7 @@ void retrieveAssertionsOneNENOneNEBAssertionInProgress() throws RaireServiceExce AssertionAndDifficulty aad1 = assertions[0]; assertEquals(0.1, aad1.difficulty); assertEquals(112, aad1.margin); - assertEquals(new BigDecimal("0.08"), aad1.status.get(Metadata.STATUS_RISK)); + assertEquals(BigDecimal.valueOf(0.08), aad1.status.get(Metadata.STATUS_RISK)); assertTrue(aad1.assertion.isNEB()); assertEquals(2, ((NotEliminatedBefore)aad1.assertion).winner); assertEquals(0, ((NotEliminatedBefore)aad1.assertion).loser); @@ -121,7 +115,44 @@ void retrieveAssertionsOneNENOneNEBAssertionInProgress() throws RaireServiceExce int[] continuing = {0, 1, 2}; assertArrayEquals(continuing, ((NotEliminatedNext)aad2.assertion).continuing); - assertEquals(new BigDecimal("0.70"), aad2.status.get(Metadata.STATUS_RISK)); + var risk = (BigDecimal) aad2.status.get(Metadata.STATUS_RISK); + assertEquals(0, risk.compareTo(BigDecimal.valueOf(0.7))); + } + + /** + * Retrieve assertions for a contest that has one NEN and one NEB assertion (audit in progress). + * (CSV) + * For the csv files, we _do_ expect consistent ordering of the assertions, so this test requests + * exact matches with expected strings that give the assertions in a particular order and the + * extrema with particular indices. + */ + @Test + @Transactional + void retrieveAssertionsOneNENOneNEBAssertionInProgressCSV() throws RaireServiceException { + testUtils.log(logger, "retrieveAssertionsOneNENOneNEBAssertionInProgressCSV"); + GetAssertionsRequest request = new GetAssertionsRequest("One NEN NEB Assertion Contest", + List.of("Liesl", "Wendell", "Amanda", "Chuan"), BigDecimal.valueOf(0.05)); + + String csv = getAssertionsCsvService.generateCSV(request); + + assertTrue(csv.contains("Contest name,One NEN NEB Assertion Contest\n")); + assertTrue(csv.contains("Candidates,\"Liesl,Wendell,Amanda,Chuan\"")); + assertTrue(csv.contains("Extreme item,Value,Assertion IDs\n")); + assertTrue(csv.contains("Margin,112,\"1\"\n")); + assertTrue(csv.contains("Diluted margin,0.1,\"1\"\n")); + assertTrue(csv.contains("Raire difficulty,3.17,\"2\"\n")); + assertTrue(csv.contains("Current risk,0.70,\"2\"\n")); + assertTrue(csv.contains("Optimistic samples to audit,200,\"2\"\n")); + assertTrue(csv.contains("Estimated samples to audit,300,\"2\"\n")); + assertTrue(csv.contains( + "ID,Type,Winner,Loser,Assumed continuing,Difficulty,Margin,Diluted margin,Risk," + + "Estimated samples to audit,Optimistic samples to audit,Two vote over count," + + "One vote over count,Other discrepancy count,One vote under count," + + "Two vote under count\n")); + assertTrue(csv.contains("1,NEB,Amanda,Liesl,,0.1,112,0.1,0.08,27,20,2,0,0,1,0\n")); + assertTrue(csv.contains( + "2,NEN,Amanda,Wendell,\"Liesl,Wendell,Amanda\",3.17,560,0.5,0.70,300,200,0,2,0,0,1\n" + )); } } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceTests.java deleted file mode 100644 index 3231d19c..00000000 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceTests.java +++ /dev/null @@ -1,95 +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.service; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; -import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; -import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; -import au.org.democracydevelopers.raireservice.testUtils; -import java.math.BigDecimal; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -/** - * Tests of assertion retrieval in GetAssertionsJsonService. Data is preloaded into the database - * using src/test/resources/data.sql. Note that tests of GetAssertionsJsonService have been - * spread across several test classes, each defined with respect to a different test container. - */ -@ActiveProfiles("test-containers") -@SpringBootTest -@AutoConfigureTestDatabase(replace = Replace.NONE) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsJsonServiceTests { - - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsJsonServiceTests.class); - - @Autowired - AssertionRepository assertionRepository; - - /** - * Retrieval of assertions for an existing contest with no associated assertions will throw - * a RaireServiceException with error code NO_ASSERTIONS_PRESENT. - */ - @Test - @Transactional - void existentContestNoAssertions(){ - testUtils.log(logger, "existentContestNoAssertions"); - GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); - GetAssertionsRequest request = new GetAssertionsRequest("No CVR Mayoral", - List.of(), new BigDecimal("0.10")); - - RaireServiceException ex = assertThrows(RaireServiceException.class, () -> - service.getRaireSolution(request)); - assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); - } - - /** - * Retrieval of assertions for a non-existent contest will throw a RaireServiceException - * with error code NO_ASSERTIONS_PRESENT. - * Note that this should not happen because it should be caught by request validation. - */ - @Test - @Transactional - void nonExistentContestNoAssertions(){ - testUtils.log(logger, "nonExistentContestNoAssertions"); - GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); - GetAssertionsRequest request = new GetAssertionsRequest("Non-Existent Contest Name", - List.of(), new BigDecimal("0.10")); - - RaireServiceException ex = assertThrows(RaireServiceException.class, () -> - service.getRaireSolution(request)); - assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); - } - -} diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCSVTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceCsvTests.java similarity index 85% rename from src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCSVTests.java rename to src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceCsvTests.java index 80680d42..39433c56 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsCSVTests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceCsvTests.java @@ -20,6 +20,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.service; +import static au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode.WRONG_CANDIDATE_NAMES; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; @@ -51,9 +54,9 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsCSVTests { +public class GetAssertionsServiceCsvTests { - private static final Logger logger = LoggerFactory.getLogger(GetAssertionsCSVTests.class); + private static final Logger logger = LoggerFactory.getLogger(GetAssertionsServiceCsvTests.class); @Autowired AssertionRepository assertionRepository; @@ -74,7 +77,7 @@ public class GetAssertionsCSVTests { public void testCSVTies() throws RaireServiceException { testUtils.log(logger, "testCSVTies"); GetAssertionsRequest request = new GetAssertionsRequest( - "Lots of assertions with ties Contest", candidates, new BigDecimal("0.10")); + "Lots of assertions with ties Contest", candidates, BigDecimal.valueOf(0.1)); String output = getAssertionsCSVService.generateCSV(request); assertTrue(output.contains("Contest name,Lots of assertions with ties Contest\n")); assertTrue(output.contains("Candidates,\"Alice,Bob,Chuan,Diego\"\n\n")); @@ -113,7 +116,7 @@ public void testCSVTies() throws RaireServiceException { public void testCharacterEscaping() throws RaireServiceException { testUtils.log(logger,"testCharacterEscaping"); GetAssertionsRequest request = new GetAssertionsRequest("Lots of tricky characters Contest", - trickyCharacters, new BigDecimal("0.10")); + trickyCharacters, BigDecimal.valueOf(0.1)); String output = getAssertionsCSVService.generateCSV(request); assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(0))); assertTrue(StringUtils.containsIgnoreCase(output, trickyCharacters.get(1))); @@ -123,14 +126,14 @@ public void testCharacterEscaping() throws RaireServiceException { } /** - * A simple test for correct generation on a simple test case with one assertion of each type. + * Test for correct generation on a simple test case with one assertion of each type. * @throws RaireServiceException if assertion database retrieval fails. */ @Test public void testCsvDemoContest() throws RaireServiceException { testUtils.log(logger,"testCsvDemoContest"); GetAssertionsRequest request = new GetAssertionsRequest( - "CSV Demo Contest", candidates, new BigDecimal("0.10")); + "CSV Demo Contest", candidates, BigDecimal.valueOf(0.1)); String output = getAssertionsCSVService.generateCSV(request); assertTrue(output.contains("Contest name,CSV Demo Contest\n")); assertTrue(output.contains("Candidates,\"Alice,Bob,Chuan,Diego\"\n\n")); @@ -151,4 +154,23 @@ public void testCsvDemoContest() throws RaireServiceException { "2,NEN,Diego,Chuan,\"Alice,Chuan,Diego\",6.1,100,0.1,0.05,45,45,0,0,0,0,0\n" )); } + + /** + * A candidate list that is inconsistent with the stored assertions throws a RaireServiceException + * with WRONG_CANDIDATE_NAMES error code. + */ + @Test + public void wrongCandidatesIsAnError() { + testUtils.log(logger, "wrongCandidatesIsAnError"); + + GetAssertionsRequest request = new GetAssertionsRequest( + "CSV Demo Contest", List.of("Alicia", "Boba", "Chuan"), BigDecimal.valueOf(0.05) + ); + + RaireServiceException ex = assertThrows(RaireServiceException.class, + () -> getAssertionsCSVService.generateCSV(request) + ); + assertSame(ex.errorCode, WRONG_CANDIDATE_NAMES); + + } } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorTestsJsonAndCsv.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorJsonAndCsvTests.java similarity index 56% rename from src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorTestsJsonAndCsv.java rename to src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorJsonAndCsvTests.java index 7b725629..b14c126d 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorTestsJsonAndCsv.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceErrorJsonAndCsvTests.java @@ -23,7 +23,6 @@ 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.assertThrows; -import au.org.democracydevelopers.raireservice.persistence.repository.AssertionRepository; import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; import au.org.democracydevelopers.raireservice.testUtils; @@ -42,57 +41,90 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import org.springframework.transaction.annotation.Transactional; /** - * Tests of assertion retrieval in GetAssertionsJsonService. Data is preloaded into the database - * using src/test/resources/data.sql. Note that tests of GetAssertionsJsonService have been + * Tests of assertion retrieval causing errors in GetAssertionsJsonService and GetAssertionsCsvService. + * Data is preloaded into the database using src/test/resources/data.sql. Note that tests of + * GetAssertionsJsonService and GetAssertionsCsvService have been * spread across several test classes, each defined with respect to a different test container. - * FIXME Add CSV + * The tests for WRONG_CANDIDATE_NAMES are in the respective format-specific class. + * There is a json version and a csv version of each test. */ @ActiveProfiles("test-containers") @SpringBootTest @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsServiceErrorTestsJsonAndCsv { +public class GetAssertionsServiceErrorJsonAndCsvTests { private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsServiceErrorTestsJsonAndCsv.class); + GetAssertionsServiceErrorJsonAndCsvTests.class); @Autowired - AssertionRepository assertionRepository; + GetAssertionsJsonService getAssertionsJsonService; + @Autowired + GetAssertionsCsvService getAssertionsCsvService; + + /** + * Retrieval of assertions for an existing contest with no associated assertions will throw + * a RaireServiceException with error code NO_ASSERTIONS_PRESENT. (JSON) + */ + @Test + @Transactional + void existentContestNoAssertionsJSON(){ + testUtils.log(logger, "existentContestNoAssertionsJSON"); + GetAssertionsRequest request = new GetAssertionsRequest("No CVR Mayoral", + List.of(), BigDecimal.valueOf(0.1)); + + RaireServiceException ex = assertThrows(RaireServiceException.class, () -> + getAssertionsJsonService.getRaireSolution(request)); + assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); + } /** * Retrieval of assertions for an existing contest with no associated assertions will throw - * a RaireServiceException with error code NO_ASSERTIONS_PRESENT. + * a RaireServiceException with error code NO_ASSERTIONS_PRESENT. (CSV) */ @Test @Transactional - void existentContestNoAssertions(){ - testUtils.log(logger, "existentContestNoAssertions"); - GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); + void existentContestNoAssertionsCSV(){ + testUtils.log(logger, "existentContestNoAssertionsCSV"); GetAssertionsRequest request = new GetAssertionsRequest("No CVR Mayoral", - List.of(), new BigDecimal("0.10")); + List.of(), BigDecimal.valueOf(0.1)); RaireServiceException ex = assertThrows(RaireServiceException.class, () -> - service.getRaireSolution(request)); + getAssertionsCsvService.generateCSV(request)); assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); } /** * Retrieval of assertions for a non-existent contest will throw a RaireServiceException - * with error code NO_ASSERTIONS_PRESENT. + * with error code NO_ASSERTIONS_PRESENT. (JSON) * Note that this should not happen because it should be caught by request validation. */ @Test @Transactional - void nonExistentContestNoAssertions(){ - testUtils.log(logger, "nonExistentContestNoAssertions"); - GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); + void nonExistentContestNoAssertionsJSON(){ + testUtils.log(logger, "nonExistentContestNoAssertionsJSON"); GetAssertionsRequest request = new GetAssertionsRequest("Non-Existent Contest Name", - List.of(), new BigDecimal("0.10")); + List.of(), BigDecimal.valueOf(0.1)); RaireServiceException ex = assertThrows(RaireServiceException.class, () -> - service.getRaireSolution(request)); + getAssertionsJsonService.getRaireSolution(request)); assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); } + /** + * Retrieval of assertions for a non-existent contest will throw a RaireServiceException + * with error code NO_ASSERTIONS_PRESENT. (CSV) + * Note that this should not happen because it should be caught by request validation. + */ + @Test + @Transactional + void nonExistentContestNoAssertionsCSV(){ + testUtils.log(logger, "nonExistentContestNoAssertionsCSV"); + GetAssertionsRequest request = new GetAssertionsRequest("Non-Existent Contest Name", + List.of(), BigDecimal.valueOf(0.1)); + RaireServiceException ex = assertThrows(RaireServiceException.class, () -> + getAssertionsCsvService.generateCSV(request)); + assertEquals(RaireErrorCode.NO_ASSERTIONS_PRESENT, ex.errorCode); + } } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceSimpleAssertionsTests.java b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceJsonTests.java similarity index 83% rename from src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceSimpleAssertionsTests.java rename to src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceJsonTests.java index 1bb2cc33..acf9fd71 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsJsonServiceSimpleAssertionsTests.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/service/GetAssertionsServiceJsonTests.java @@ -20,6 +20,7 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra package au.org.democracydevelopers.raireservice.service; +import static au.org.democracydevelopers.raireservice.testUtils.correctMetadata; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -35,8 +36,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import au.org.democracydevelopers.raireservice.request.GetAssertionsRequest; import au.org.democracydevelopers.raireservice.service.RaireServiceException.RaireErrorCode; import au.org.democracydevelopers.raireservice.testUtils; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import java.math.BigDecimal; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -62,21 +61,14 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra @SpringBootTest @AutoConfigureTestDatabase(replace = Replace.NONE) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) -public class GetAssertionsJsonServiceSimpleAssertionsTests { +public class GetAssertionsServiceJsonTests { private static final Logger logger = LoggerFactory.getLogger( - GetAssertionsJsonServiceSimpleAssertionsTests.class); + GetAssertionsServiceJsonTests.class); @Autowired AssertionRepository assertionRepository; - /** - * To facilitate easier checking of retrieved/saved assertion content. - */ - private static final Gson GSON = - new GsonBuilder().serializeNulls().disableHtmlEscaping().create(); - - /** * Retrieve assertions for a contest that has one NEB assertion. */ @@ -86,15 +78,13 @@ void retrieveAssertionsExistentContestOneNEBAssertion() throws RaireServiceExcep testUtils.log(logger, "retrieveAssertionsExistentContestOneNEBAssertion"); GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); GetAssertionsRequest request = new GetAssertionsRequest("One NEB Assertion Contest", - List.of("Alice", "Bob"), new BigDecimal("0.10")); + List.of("Alice", "Bob"), BigDecimal.valueOf(0.1)); RaireSolution solution = service.getRaireSolution(request); // Check that the metadata has been constructed appropriately - final String metadata = "{\"candidates\":[\"Alice\",\"Bob\"]," + - "\"contest\":\"One NEB Assertion Contest\",\"risk_limit\":0.10}"; - - assertEquals(metadata, GSON.toJson(solution.metadata)); + assertTrue(correctMetadata(List.of("Alice","Bob"), "One NEB Assertion Contest", + BigDecimal.valueOf(0.1), solution.metadata, BigDecimal.class)); // The RaireSolution contains a RaireResultOrError, but the error should be null. assertNull(solution.solution.Err); @@ -113,8 +103,9 @@ void retrieveAssertionsExistentContestOneNEBAssertion() throws RaireServiceExcep assertEquals(0, ((NotEliminatedBefore)aad.assertion).winner); assertEquals(1, ((NotEliminatedBefore)aad.assertion).loser); - // Check that current risk is 1.00 - assertEquals(new BigDecimal("1.00"), aad.status.get(Metadata.STATUS_RISK)); + // Check that current risk is 1. + BigDecimal risk = (BigDecimal) aad.status.get(Metadata.STATUS_RISK); + assertEquals(0, risk.compareTo(BigDecimal.valueOf(1))); } /** @@ -126,15 +117,14 @@ void retrieveAssertionsExistentContestOneNENAssertion() throws RaireServiceExcep testUtils.log(logger, "retrieveAssertionsExistentContestOneNENAssertion"); GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); GetAssertionsRequest request = new GetAssertionsRequest("One NEN Assertion Contest", - List.of("Alice", "Charlie", "Diego", "Bob"), new BigDecimal("0.10")); + List.of("Alice", "Charlie", "Diego", "Bob"), BigDecimal.valueOf(0.1)); RaireSolution solution = service.getRaireSolution(request); // Check that the metadata has been constructed appropriately - final String metadata = "{\"candidates\":[\"Alice\",\"Charlie\",\"Diego\",\"Bob\"]," + - "\"contest\":\"One NEN Assertion Contest\",\"risk_limit\":0.10}"; - - assertEquals(metadata, GSON.toJson(solution.metadata)); + assertTrue(correctMetadata(List.of("Alice","Charlie","Diego","Bob"), + "One NEN Assertion Contest", BigDecimal.valueOf(0.1), solution.metadata, + BigDecimal.class)); // The RaireSolution contains a RaireResultOrError, but the error should be null. assertNull(solution.solution.Err); @@ -156,8 +146,9 @@ void retrieveAssertionsExistentContestOneNENAssertion() throws RaireServiceExcep int[] continuing = {0, 1, 2, 3}; assertArrayEquals(continuing, ((NotEliminatedNext)aad.assertion).continuing); - // Check that current risk is 1.00 - assertEquals(new BigDecimal("1.00"), aad.status.get(Metadata.STATUS_RISK)); + // Check that current risk is 1. + BigDecimal risk = (BigDecimal) aad.status.get(Metadata.STATUS_RISK); + assertEquals(0, risk.compareTo(BigDecimal.valueOf(1))); } /** @@ -170,11 +161,11 @@ void retrieveAssertionsInconsistentRequest1() { testUtils.log(logger, "retrieveAssertionsInconsistentRequest1"); GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); GetAssertionsRequest request = new GetAssertionsRequest("One NEN NEB Assertion Contest", - List.of("Alice", "Charlie", "Diego", "Bob"), new BigDecimal("0.10")); + List.of("Alice", "Charlie", "Diego", "Bob"), BigDecimal.valueOf(0.1)); RaireServiceException ex = assertThrows(RaireServiceException.class, () -> service.getRaireSolution(request)); - assertEquals(RaireErrorCode.INTERNAL_ERROR, ex.errorCode); + assertEquals(RaireErrorCode.WRONG_CANDIDATE_NAMES, ex.errorCode); assertTrue(StringUtils.containsIgnoreCase(ex.getMessage(), "Candidate list provided as parameter is inconsistent")); } @@ -189,11 +180,11 @@ void retrieveAssertionsInconsistentRequest2() { testUtils.log(logger, "retrieveAssertionsInconsistentRequest2"); GetAssertionsJsonService service = new GetAssertionsJsonService(assertionRepository); GetAssertionsRequest request = new GetAssertionsRequest("One NEN NEB Assertion Contest", - List.of(), new BigDecimal("0.10")); + List.of(), BigDecimal.valueOf(0.1)); RaireServiceException ex = assertThrows(RaireServiceException.class, () -> service.getRaireSolution(request)); - assertEquals(RaireErrorCode.INTERNAL_ERROR, ex.errorCode); + assertEquals(RaireErrorCode.WRONG_CANDIDATE_NAMES, ex.errorCode); assertTrue(StringUtils.containsIgnoreCase(ex.getMessage(), "Candidate list provided as parameter is inconsistent")); } diff --git a/src/test/java/au/org/democracydevelopers/raireservice/testUtils.java b/src/test/java/au/org/democracydevelopers/raireservice/testUtils.java index ad4bd94f..e547dadb 100644 --- a/src/test/java/au/org/democracydevelopers/raireservice/testUtils.java +++ b/src/test/java/au/org/democracydevelopers/raireservice/testUtils.java @@ -22,15 +22,15 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra import static java.util.Collections.max; +import au.org.democracydevelopers.raire.algorithm.RaireResult; +import au.org.democracydevelopers.raire.assertions.NotEliminatedBefore; +import au.org.democracydevelopers.raire.assertions.NotEliminatedNext; import au.org.democracydevelopers.raireservice.persistence.entity.Assertion; +import au.org.democracydevelopers.raireservice.service.Metadata; import au.org.democracydevelopers.raireservice.util.DoubleComparator; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; +import java.lang.reflect.Type; import java.math.BigDecimal; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.slf4j.Logger; @@ -40,12 +40,6 @@ the raire assertion generation engine (https://github.com/DemocracyDevelopers/ra */ public class testUtils { - /** - * To facilitate easier checking of retrieved/saved assertion content. - */ - private static final Gson GSON = - new GsonBuilder().serializeNulls().disableHtmlEscaping().create(); - /** * Comparator for doubles within a specific tolerance. */ @@ -59,128 +53,58 @@ public static void log(Logger logger, String test){ } /** - * Utility to check that the API json response to a get assertions request contains the right metadata. - * @param candidates the expected list of candidate names + * Utility to check that the response to a get assertions request contains the right metadata. Use + * this one for API tests, where the value has been serialized & deserialized. + * + * @param candidates the expected list of candidate names * @param contestName the expected contest name - * @param riskLimit the expected risk limit - * @param APIResponseBody the body of the response - * @return true if the response json contains a 'metadata' field, with fields matching the candidates, - * contestname and riskLimit. + * @param riskLimit the expected risk limit + * @param metadata the metadata from the response, in which the riskLimit is interpreted as a + * double by the deserializer. + * @param riskLimitClass the class in which the risk limit is expressed. Use BigDecimal for things + * derived directly from the service, double for values that have been + * serialized and deserialized via the API. + * @return true if the response's metadata fields match the candidates, contestname and riskLimit. */ - public static boolean correctMetadata(List candidates, String contestName, double riskLimit, - String APIResponseBody) { - JsonObject data = GSON.fromJson(APIResponseBody, JsonObject.class); - JsonObject metadataElement = data.get("metadata").getAsJsonObject(); - - String retrievedContestName = metadataElement.get("contest").getAsString(); - double retrievedRiskLimit = metadataElement.get("risk_limit").getAsDouble(); - JsonArray retrievedCandidatesJson = metadataElement.getAsJsonArray("candidates"); - List retrievedCandidates = new ArrayList<>(); - for (JsonElement jsonElement : retrievedCandidatesJson) { - retrievedCandidates.add(jsonElement.getAsString()); + public static boolean correctMetadata(List candidates, String contestName, + BigDecimal riskLimit, Map metadata, Type riskLimitClass) throws ClassCastException { + + BigDecimal retrievedRiskLimit; + if(riskLimitClass == Double.class) { + retrievedRiskLimit = BigDecimal.valueOf((double) metadata.get(Metadata.RISK_LIMIT)); + } else if (riskLimitClass == BigDecimal.class) { + retrievedRiskLimit = (BigDecimal) metadata.get(Metadata.RISK_LIMIT); + } else { + // We can only deal with doubles and BigDecimals. + return false; } + String retrievedContestName = metadata.get(Metadata.CONTEST).toString(); + List retrievedCandidates = (List) metadata.get(Metadata.CANDIDATES); + return contestName.equals(retrievedContestName) - && doubleComparator.compare(riskLimit, retrievedRiskLimit) == 0 + && riskLimit.compareTo(retrievedRiskLimit) == 0 && setsNoDupesEqual(candidates, retrievedCandidates); } /** - * Check that the APIResponseBody's solution has the expected margin and difficulty. + * Check that the RaireResult's solution has the expected margin and difficulty. * @param margin expected margin * @param difficulty expected difficulty - * @param APIResponseBody the body of the response - * @return true if the APIResponseBody's data matches the expected values. + * @param numAssertions the expected number of assertions + * @param result the RaireResult in the body of the response + * @return true if the result's data matches the expected values. */ public static boolean correctSolutionData(int margin, double difficulty, int numAssertions, - String APIResponseBody) { - JsonObject data = GSON.fromJson(APIResponseBody, JsonObject.class); - JsonObject solutionElement = (JsonObject) data.get("solution").getAsJsonObject().get("Ok"); - int retrievedMargin = solutionElement.get("margin").getAsInt(); - double retrievedDifficulty = solutionElement.get("difficulty").getAsDouble(); - JsonArray assertions = solutionElement.getAsJsonArray("assertions"); + RaireResult result) { + + int retrievedMargin = result.margin; + double retrievedDifficulty = result.difficulty; return retrievedMargin == margin && doubleComparator.compare(retrievedDifficulty, difficulty) == 0 - && assertions.size() == numAssertions; - } - - /** - * Utility to check the relevant assertion data values, from an API response. - * @param type either NEN or NEB - * @param margin the expected raw margin - * @param difficulty the expected difficulty - * @param winner the expected winner, as an int - * @param loser the expected loser, as an int - * @param assumedContinuing the list of indices of assumed-continuing candidates (for NEN assertions) - * @param APIResponseBody the response body to be tested - * @param index the index of the assertion to be checked, in the assertion array. - * @return true if the assertion's data match all the expected values. Candidate indices are compared - * as strings. - */ - public static boolean correctIndexedAPIAssertionData(String type, int margin, double difficulty, - int winner, int loser, List assumedContinuing, double risk, String APIResponseBody, - int index) { - JsonObject data = GSON.fromJson(APIResponseBody, JsonObject.class); - String assertion = data.get("solution").getAsJsonObject().get("Ok").getAsJsonObject() - .get("assertions").getAsJsonArray().get(index).getAsJsonObject().toString(); - return correctAPIAssertionData(type, margin, difficulty, winner, loser, assumedContinuing, risk, - assertion); + && result.assertions.length == numAssertions; } - /** - * Check that the data in an assertion expressed as json (of the form exported in the - * get-assertions API) matches the data entered as function parameters. - * @param type the assertion type (NEB or NEN). - * @param margin the absolute margin - * @param difficulty raire-java estimated difficulty - * @param winner the assertion winner's index in the candidate list - * @param loser the assertion loser's index in the candidate list - * @param assumedContinuing if NEB, blank; if NEN, the list of indices of the candidates assumed - * to be continuing. - * @param risk the current risk estimate. - * @param assertionAsJson the assertion to be tested, as a json string. - * @return true if the input data matches the data extracted from the assertion as json. - */ - public static boolean correctAPIAssertionData(String type, int margin, double difficulty, - int winner, int loser, List assumedContinuing, double risk, String assertionAsJson) { - // It makes no sense to call this with NEN and a nonempty assumedContinuing. - assert (type.equals("NEN") || assumedContinuing.isEmpty()); - - JsonObject data = GSON.fromJson(assertionAsJson, JsonObject.class); - JsonObject assertionData = data.get("assertion").getAsJsonObject(); - - int retrievedMargin = data.get("margin").getAsInt(); - double difficultyElement = data.get("difficulty").getAsDouble(); - String retrievedType = assertionData.get("type").getAsString(); - int retrievedLoser = assertionData.get("loser").getAsInt(); - int retrievedWinner = assertionData.get("winner").getAsInt(); - double retrievedRisk = data.get("status").getAsJsonObject().get("risk").getAsDouble(); - - boolean assumedContinuingRight; - if(retrievedType.equals("NEN")) { - // type is NEN. Get 'assumed continuing' and compare it with expected. - assert (assertionData.has("continuing")); - JsonArray retrievedAssumedContinuingData - = assertionData.getAsJsonArray("continuing").getAsJsonArray(); - List retrievedContinuing = new ArrayList<>(); - for (JsonElement jsonElement : retrievedAssumedContinuingData) { - retrievedContinuing.add(jsonElement.getAsInt()); - } - assumedContinuingRight = setsNoDupesEqual(assumedContinuing, retrievedContinuing); - } else { // type is NEB. There should be no 'assumed continuing'. - assumedContinuingRight = !assertionData.has("continuing"); - } - - return type.equals(retrievedType) - && margin == retrievedMargin - && doubleComparator.compare(difficulty, difficultyElement) == 0 - && loser == retrievedLoser - && winner == retrievedWinner - && doubleComparator.compare(risk, retrievedRisk) == 0 - && assumedContinuingRight; - } - - /** * Utility to check the relevant assertion attributes against expected values. * @param margin the expected raw margin @@ -204,6 +128,52 @@ public static boolean correctDBAssertionData(int margin, double dilutedMargin, d && setsNoDupesEqual(assertion.getAssumedContinuing(), assumedContinuing); } + /** + * Utility to check the relevant assertion attributes against expected values. + * @param type the type ("NEN" or "NEB") + * @param margin the expected raw margin + * @param difficulty the expected difficulty + * @param winner the expected winner + * @param loser the expected loser + * @param assertionAndDifficulty the (raire-java) assertionAndDifficulty to be checked + * @param assumedContinuing the list of candidate names expected to be in the + * 'assumed continuing' field. + * @return true if the assertion's type and data match all the expected values. + */ + public static boolean correctAssertionData(String type, int margin, double difficulty, int winner, + int loser, List assumedContinuing, double risk, + au.org.democracydevelopers.raire.assertions.AssertionAndDifficulty assertionAndDifficulty) { + + // Check for the right margin, difficulty and risk + boolean rightMarginAndDifficulty = + assertionAndDifficulty.margin == margin + && (doubleComparator.compare(assertionAndDifficulty.difficulty, difficulty)==0) + && (doubleComparator.compare( (double) assertionAndDifficulty.status.get("risk"), risk) == 0); + + if(assertionAndDifficulty.assertion instanceof NotEliminatedNext nen) { + // If it's an NEN assertion, check that that's the expected type, and that all the other data + // match + List nenAssumedContinuing = Arrays.stream(nen.continuing).boxed().toList(); + return type.equals("NEN") + && nen.winner == winner + && nen.loser == loser + && setsNoDupesEqual(nenAssumedContinuing, assumedContinuing) + && rightMarginAndDifficulty; + + } else if(assertionAndDifficulty.assertion instanceof NotEliminatedBefore neb) { + // If it's an NEB assertion, check that that's the expected type, that the assumedContinuing + // list is empty, and that all the other data match. + return type.equals("NEB") + && neb.winner == winner + && neb.loser == loser + && assumedContinuing.isEmpty() + && rightMarginAndDifficulty; + } + + // Not an instance of a type we recognise. + return false; + } + /** * Returns true if the attributes of the given assertion are equal to those provided as input * to this method. @@ -262,20 +232,20 @@ public static boolean difficultyMatchesMax(double expectedDifficulty, List boolean setsNoDupesEqual(List strings1, List strings2) { - List strings1WithoutDuplicates = strings1.stream().distinct().toList(); - List strings2WithoutDuplicates = strings2.stream().distinct().toList(); - // strings1 has no duplicates - return strings1WithoutDuplicates.size() == strings1.size() - // strings2 has no duplicates - && strings2WithoutDuplicates.size() == strings2.size() + private static boolean setsNoDupesEqual(List list1, List list2) { + List list1WithoutDuplicates = list1.stream().distinct().toList(); + List list2WithoutDuplicates = list2.stream().distinct().toList(); + // list1 has no duplicates + return list1WithoutDuplicates.size() == list1.size() + // list2 has no duplicates + && list2WithoutDuplicates.size() == list2.size() // they are the same size - && strings1.size() == strings2.size() + && list1.size() == list2.size() // and they have the same contents. - && strings1.containsAll(strings2); + && list1.containsAll(list2); } } diff --git a/src/test/resources/application-known-testcases.yml b/src/test/resources/application-known-testcases.yml index edd0b0ef..05eea78c 100644 --- a/src/test/resources/application-known-testcases.yml +++ b/src/test/resources/application-known-testcases.yml @@ -8,4 +8,5 @@ spring: ddl-auto: none sql: init: - data-locations: classpath:known_testcases_votes.sql \ No newline at end of file + data-locations: classpath:known_testcases_votes.sql, classpath:NSW2021Data/Byron_Mayoral.sql, + classpath:NSW2021Data/Byron_Mayoral_cvr_contest_info.sql diff --git a/src/test/resources/known_testcases_votes.sql b/src/test/resources/known_testcases_votes.sql index c16df481..d76b6eba 100644 --- a/src/test/resources/known_testcases_votes.sql +++ b/src/test/resources/known_testcases_votes.sql @@ -6,6 +6,9 @@ INSERT INTO county (id, name) values (8, 'TiedWinnersCounty'); INSERT INTO county (id, name) values (9, 'GuideToRaireCounty'); INSERT INTO county (id, name) values (10, 'SimpleCounty1'); INSERT INTO county (id, name) values (11, 'SimpleCounty2'); +-- leave a space for Byron, 12, from the NSW2021Data directory +INSERT INTO county (id, name) values (13, 'TimeoutCheckingWinnersCounty'); + -- A test assertion to allow for some basic sanity checking. INSERT INTO assertion (assertion_type, contest_name, difficulty, diluted_margin, loser, margin, current_risk, estimated_samples_to_audit, one_vote_over_count, one_vote_under_count, optimistic_samples_to_audit, other_count, two_vote_over_count, two_vote_under_count, version, winner) values ('NEB', 'Sanity Check NEB Assertion Contest', 1.1, 0.32, 'Bob', 320, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Alice'); @@ -36,6 +39,7 @@ INSERT INTO contest (county_id, id, version, description, name, sequence_number, INSERT INTO contest (county_id, id, version, description, name, sequence_number, votes_allowed, winners_allowed) values (10, 999993, 0, 'IRV', 'Simple Contest', 5, 3, 1); INSERT INTO contest (county_id, id, version, description, name, sequence_number, votes_allowed, winners_allowed) values (10, 999994, 0, 'IRV', 'Cross-county Simple Contest', 7, 3, 1); INSERT INTO contest (county_id, id, version, description, name, sequence_number, votes_allowed, winners_allowed) values (11, 999995, 0, 'IRV', 'Cross-county Simple Contest', 8, 3, 1); +INSERT INTO contest (county_id, id, version, description, name, sequence_number, votes_allowed, winners_allowed) values (13, 999996, 0, 'IRV', 'Time out checking winners contest', 9, 20, 1); -- CVRs @@ -251,3 +255,46 @@ INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) val -- 1 (C,A) in county 11. INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (30012, 12, 'Type 1', 1, 10, '3-1-12', 12, 'UPLOADED', 3); INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (30012, 11, '["Chuan","Alice"]', 999995, 0); + +-- A contest designed to make it time out checking winners. There are 20 candidates and 20 votes, +-- all the same except for a cyclical offset. +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40001, 1, 'Type 1', 1, 13, '1-1-1', 1, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40001, 13, '["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40002, 2, 'Type 1', 1, 13, '1-1-2', 2, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40002, 13, '["B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40003, 3, 'Type 1', 1, 13, '1-1-3', 3, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40003, 13, '["C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40004, 4, 'Type 1', 1, 13, '1-1-4', 4, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40004, 13, '["D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40005, 5, 'Type 1', 1, 13, '1-1-5', 5, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40005, 13, '["E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40006, 6, 'Type 1', 1, 13, '1-1-6', 6, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40006, 13, '["F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40007, 7, 'Type 1', 1, 13, '1-1-7', 7, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40007, 13, '["G","H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40008, 8, 'Type 1', 1, 13, '1-1-8', 8, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40008, 13, '["H","I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40009, 9, 'Type 1', 1, 13, '1-1-9', 9, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40009, 13, '["I","J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40010, 10, 'Type 1', 1, 13, '1-1-10', 10, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40010, 13, '["J","K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40011, 11, 'Type 1', 1, 13, '1-1-11', 11, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40011, 13, '["K","L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40012, 12, 'Type 1', 1, 13, '1-1-12', 12, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40012, 13, '["L","M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40013, 13, 'Type 1', 1, 13, '1-1-13', 13, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40013, 13, '["M","N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K","L"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40014, 14, 'Type 1', 1, 13, '1-1-14', 14, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40014, 13, '["N","O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K","L","M"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40015, 15, 'Type 1', 1, 13, '1-1-14', 15, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40015, 13, '["O","P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K","L","M","N"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40016, 16, 'Type 1', 1, 13, '1-1-16', 16, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40016, 13, '["P","Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40017, 17, 'Type 1', 1, 13, '1-1-17', 17, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40017, 13, '["Q","R","S","T","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40018, 18, 'Type 1', 1, 13, '1-1-18', 18, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40018, 13, '["R","S","T","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40019, 19, 'Type 1', 1, 13, '1-1-19', 19, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40019, 13, '["S","T","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R"]', 999996, 0); +INSERT INTO cast_vote_record (id, cvr_number, ballot_type, batch_id, county_id, imprinted_id, record_id, record_type, scanner_id) values (40020, 20, 'Type 1', 1, 13, '1-1-20', 20, 'UPLOADED', 3); +INSERT INTO cvr_contest_info (cvr_id, county_id, choices, contest_id, index) values (40020, 13, '["T","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S"]', 999996, 0); diff --git a/src/test/resources/simple_assertions_csv_challenges.sql b/src/test/resources/simple_assertions_csv_challenges.sql index 0ee7e702..085e96d8 100644 --- a/src/test/resources/simple_assertions_csv_challenges.sql +++ b/src/test/resources/simple_assertions_csv_challenges.sql @@ -34,13 +34,13 @@ INSERT INTO assertion_assumed_continuing values (6, 'Diego'); INSERT INTO county (id, name) VALUES (2,'Lots of tricky characters County'); INSERT INTO contest (county_id, id, version, description, name, sequence_number, votes_allowed, winners_allowed) VALUES (2,2,0,'IRV','Lots of tricky characters Contest',2,4,1); INSERT INTO assertion (assertion_type, contest_name, difficulty, diluted_margin, loser, margin, current_risk, estimated_samples_to_audit, one_vote_over_count, one_vote_under_count, optimistic_samples_to_audit, other_count, two_vote_over_count, two_vote_under_count, version, winner) values -('NEB', 'Lots of tricky characters Contest', 2.1, 0.32, 'Breaking, Bob', 320, 0.04, 110, 0, 1, 100, 0, 0, 0, 0, 'Annoying, Alice'); +('NEB', 'Lots of tricky characters Contest', 2.1, 0.32, '"Breaking, Bob"', 320, 0.04, 110, 0, 1, 100, 0, 0, 0, 0, 'Annoying, Alice'); INSERT INTO assertion (assertion_type, contest_name, difficulty, diluted_margin, loser, margin, current_risk, estimated_samples_to_audit, one_vote_over_count, one_vote_under_count, optimistic_samples_to_audit, other_count, two_vote_over_count, two_vote_under_count, version, winner) values ('NEN', 'Lots of tricky characters Contest', 3.01, 0.12, 'O''Difficult, Diego', 240, 0.04, 120, 1, 0, 110, 0, 0, 0, 0, 'Challenging, Chuan'); INSERT INTO assertion_assumed_continuing values (8, 'Annoying, Alice'); INSERT INTO assertion_assumed_continuing values (8, 'Challenging, Chuan'); INSERT INTO assertion_assumed_continuing values (8, 'O''Difficult, Diego'); -INSERT INTO assertion_assumed_continuing values (8, '""Breaking, Bob""'); +INSERT INTO assertion_assumed_continuing values (8, '"Breaking, Bob"'); -- This contest has sensible correlated values for making a demo csv INSERT INTO county (id, name) VALUES (3,'CSV Demo County');