Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

73 generate assertions endpoint #139

Merged
merged 20 commits into from
Jul 2, 2024
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
23d9a81
Merge branch 'refs/heads/74-get-assertions-endpoint' into 73-generate…
vteague Jun 23, 2024
c09f056
Merge remote-tracking branch 'refs/remotes/origin/main' into 73-gener…
vteague Jun 25, 2024
9265349
Refactor raire-related endpoints into an abstract class and two child…
vteague Jun 25, 2024
8a1024f
First draft of GenerateAssertions endpoint.
vteague Jun 25, 2024
24dc849
Basic tests now passing.
vteague Jun 26, 2024
39a1a18
Improved notes about winner storage (which currently is not implement…
vteague Jun 26, 2024
21f91f1
Better exception handling.
vteague Jun 26, 2024
6404949
Refactored test data into utils.
vteague Jun 27, 2024
07fd1fd
Refactored test data into utils.
vteague Jun 27, 2024
0f093da
Start on mocking raire service.
vteague Jun 27, 2024
efc3163
Mocking correct response from raire service.
vteague Jun 27, 2024
c0c1991
Almost-complete tests including mocking of invalid responses from the…
vteague Jun 28, 2024
578ccbb
Redid error handling in GenerateAssertions endpoint to use validatePa…
vteague Jun 28, 2024
997de6e
Test for, and mock of, TIED_WINNERS error.
vteague Jun 28, 2024
d56c1f0
GenerateAssertionsTests.java inheriting from TestClassWithDatabase, t…
vteague Jun 28, 2024
b2d1471
Separate endpoint for test that are expected to cause certain kinds o…
vteague Jun 28, 2024
c4f6ab6
Separate endpoint for all tests.
vteague Jun 28, 2024
ee79f0d
Correct order of logging vs returning errors.
vteague Jun 30, 2024
9a242cc
Correct endpoints.
vteague Jun 30, 2024
f330a81
Added test cases with plurality tied votes.
vteague Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor raire-related endpoints into an abstract class and two child…
…ren.
vteague committed Jun 25, 2024
commit 9265349a1aec7a390e104b04a6ca31b7be6b44dc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
Democracy Developers IRV extensions to colorado-rla.
@copyright 2024 Colorado Department of State
These IRV extensions are designed to connect to a running instance of the raire
service (https://github.com/DemocracyDevelopers/raire-service), in order to
generate assertions that can be audited using colorado-rla.
The colorado-rla IRV extensions are 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.
The colorado-rla IRV extensions are 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 <https://www.gnu.org/licenses/>.
*/

package au.org.democracydevelopers.corla.endpoint;

import au.org.democracydevelopers.corla.model.ContestType;
import com.google.gson.Gson;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import us.freeandfair.corla.asm.ASMEvent;
import us.freeandfair.corla.controller.ContestCounter;
import us.freeandfair.corla.endpoint.AbstractDoSDashboardEndpoint;
import us.freeandfair.corla.model.*;
import java.util.List;

/**
* An abstract endpoint for communicating with raire. Includes all the information for collecting IRV contests
* and making a request to raire, including the location of the raire service.
* Used by GetAssertions and GenerateAssertions.
*/
public abstract class AbstractAllIrvEndpoint extends AbstractDoSDashboardEndpoint {

/**
* Class-wide logger
*/
private static final Logger LOGGER = LogManager.getLogger(AbstractAllIrvEndpoint.class);

/**
* GSON, for serialising requests.
*/
protected final Gson gson = new Gson();

/**
* Identify RAIRE service URL from config.
*/
protected static final String RAIRE_URL = "raire_url";

/**
* RAIRE error code key.
*/
protected static final String RAIRE_ERROR_CODE = "error_code";

/**
* RAIRE service endpoint name.
*/
protected static final String RAIRE_ENDPOINT = "/raire/get-assertions";

/**
* The httpClient used for making requests to the raire-service.
*/
protected final CloseableHttpClient httpClient = HttpClients.createDefault();


/**
* The event to return for this endpoint.
*/
protected final ThreadLocal<ASMEvent> my_event = new ThreadLocal<>();

/**
* @return State admin authorization is necessary for this endpoint.
*/
public AuthorizationType requiredAuthorization() { return AuthorizationType.STATE; }

/**
* {@inheritDoc}
*/
@Override
protected ASMEvent endpointEvent() {
return my_event.get();
}

/**
* {@inheritDoc}
*/
@Override
protected void reset() {
my_event.set(null);
}

/**
* Get all the ContestResults whose contests are consistently IRV.
* @return A list of all ContestResults for IRV contests.
* @throws RuntimeException if it encounters contests with a mix of IRV and any other contest type.
*/
public static List<ContestResult> getIRVContestResults() {
final String prefix = "[getIRVContestResults]";
final String msg = "Inconsistent contest types:";

// Find all the ContestResults with any that match IRV.
List<ContestResult> results = ContestCounter.countAllContests().stream()
.peek(cr -> cr.setAuditReason(AuditReason.OPPORTUNISTIC_BENEFITS))
.filter(cr -> cr.getContests().stream().map(Contest::description)
.anyMatch(d -> d.equalsIgnoreCase(ContestType.IRV.toString()))).toList();

// The above should be sufficient, but just in case, check that each contest we found _all_ matches IRV, and
// throw a RuntimeException if not.
for (final ContestResult cr : results) {
if (cr.getContests().stream().map(Contest::description)
.anyMatch(d -> !d.equalsIgnoreCase(ContestType.IRV.toString()))) {
LOGGER.error(String.format("%s %s %s", prefix, msg, cr.getContestName()));
throw new RuntimeException(msg + cr.getContestName());
}
}

return results;
}
}
Original file line number Diff line number Diff line change
@@ -216,7 +216,7 @@ public void getAssertions(final ZipOutputStream zos, final BigDecimal riskLimit,

// Iterate through all IRV Contests, sending a request to the raire-service for each one's assertions and
// collating the responses.
final List<ContestResult> IRVContestResults = IRVContestCollector.getIRVContestResults();
final List<ContestResult> IRVContestResults = AbstractAllIrvEndpoint.getIRVContestResults();
for (final ContestResult cr : IRVContestResults) {

// Find the winner (there should only be one), candidates and contest name.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -176,8 +176,8 @@ public static String[][] TwoIRVContests() {
@Test(dataProvider = "TwoIRVContests")
public void rightFileNamesInZip(String contestName1, String contestName2, String suffix) throws Exception {
testUtils.log(LOGGER, "rightFileNamesInZip");
try (MockedStatic<IRVContestCollector> mockIRVContestResults = Mockito.mockStatic(IRVContestCollector.class)) {
mockIRVContestResults.when(IRVContestCollector::getIRVContestResults).thenReturn(mockedIRVContestResults);
try (MockedStatic<AbstractAllIrvEndpoint> mockIRVContestResults = Mockito.mockStatic(AbstractAllIrvEndpoint.class)) {
mockIRVContestResults.when(AbstractAllIrvEndpoint::getIRVContestResults).thenReturn(mockedIRVContestResults);

GetAssertions endpoint = new GetAssertions();
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
@@ -216,9 +216,9 @@ public static String[][] SampleBadEndpoints() {
@Test(dataProvider ="SampleBadEndpoints", expectedExceptions = RuntimeException.class)
public void badEndpointThrowsRuntimeException(String url, String suffix) throws Exception {
testUtils.log(LOGGER, "badEndpointThrowsRuntimeException");
try (MockedStatic<IRVContestCollector> mockIRVContestResults
= Mockito.mockStatic(IRVContestCollector.class)) {
mockIRVContestResults.when(IRVContestCollector::getIRVContestResults)
try (MockedStatic<AbstractAllIrvEndpoint> mockIRVContestResults
= Mockito.mockStatic(AbstractAllIrvEndpoint.class)) {
mockIRVContestResults.when(AbstractAllIrvEndpoint::getIRVContestResults)
.thenReturn(mockedIRVContestResults);

GetAssertions endpoint = new GetAssertions();
@@ -246,9 +246,9 @@ public static String[][] SampleBadUrls() {
@Test(dataProvider ="SampleBadUrls", expectedExceptions = {MalformedURLException.class, URISyntaxException.class})
public void badUrlThrowsUrlException(String url, String suffix) throws Exception {
testUtils.log(LOGGER, "badUrlThrowsUrlException");
try (MockedStatic<IRVContestCollector> mockIRVContestResults
= Mockito.mockStatic(IRVContestCollector.class)) {
mockIRVContestResults.when(IRVContestCollector::getIRVContestResults)
try (MockedStatic<AbstractAllIrvEndpoint> mockIRVContestResults
= Mockito.mockStatic(AbstractAllIrvEndpoint.class)) {
mockIRVContestResults.when(AbstractAllIrvEndpoint::getIRVContestResults)
.thenReturn(mockedIRVContestResults);

GetAssertions endpoint = new GetAssertions();