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

Standardize CVR identification across audit logs and RCTab output #913

Draft
wants to merge 13 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/main/java/network/brightspots/rcv/CastVoteRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,13 @@ boolean doesUseLastAllowedRanking() {
return usesLastAllowedRanking;
}

// This represents the canonical ID used for audit logs and RCTab CVR
String getId() {
return suppliedId != null ? suppliedId : computedId;
return !isNullOrBlank(computedId) ? computedId : suppliedId;
}

String getSuppliedId() {
return suppliedId != null ? suppliedId : "";
}

// logs the outcome for this CVR for this round for auditing purposes
Expand All @@ -138,11 +143,7 @@ void logRoundOutcome(

StringBuilder logStringBuilder = new StringBuilder();
logStringBuilder.append("[Round] ").append(round).append(" [CVR] ");
if (!isNullOrBlank(computedId)) {
logStringBuilder.append(computedId);
} else {
logStringBuilder.append(suppliedId);
}
logStringBuilder.append(getId());
if (outcomeType == VoteOutcomeType.IGNORED) {
logStringBuilder.append(" [was ignored] ");
} else if (outcomeType == VoteOutcomeType.EXHAUSTED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ private int parseCvrFile(
String computedId =
Stream.of(tabulatorId, batchId, Integer.toString(recordId))
.filter(s -> s != null && !s.isBlank())
.collect(Collectors.joining("|"));
.collect(Collectors.joining("-")); // using a dash since this is an Id

// filter out records which are not current and replace them with adjudicated ones
HashMap adjudicatedData = (HashMap) session.get("Original");
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/network/brightspots/rcv/OutputWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -696,14 +696,15 @@ String writeRcTabCvrCsv(
CSVFormat format = CSVFormat.DEFAULT.builder().setNullString("").build();
csvPrinter = new CSVPrinter(writer, format);
// print header:
// ContestId, TabulatorId, BatchId, RecordId, Precinct, Precinct Portion, rank 1 selection,
// rank 2 selection, ... rank maxRanks selection
// RCTab CVR Id, ContestId, TabulatorId, BatchId, RecordId, Precinct, Precinct Portion,
yezr marked this conversation as resolved.
Show resolved Hide resolved
// rank 1 selection, rank 2 selection, ... rank maxRanks selection
csvPrinter.print("Source Filepath");
csvPrinter.print("CVR Provider");
csvPrinter.print("Contest Id");
csvPrinter.print("RCTab CVR Id");
csvPrinter.print("Tabulator Id");
csvPrinter.print("Batch Id");
csvPrinter.print("Record Id");
csvPrinter.print("Vendor Id");
csvPrinter.print("Precinct");
csvPrinter.print("Precinct Portion");

Expand Down Expand Up @@ -740,9 +741,10 @@ String writeRcTabCvrCsv(
csvPrinter.print(currentSourceData.source.getFilePath());
csvPrinter.print(currentSourceData.source.getProvider());
csvPrinter.print(castVoteRecord.getContestId());
csvPrinter.print(castVoteRecord.getId());
csvPrinter.print(castVoteRecord.getTabulatorId());
csvPrinter.print(castVoteRecord.getSlice(TabulateBySlice.BATCH));
csvPrinter.print(castVoteRecord.getId());
csvPrinter.print(castVoteRecord.getSuppliedId());
csvPrinter.print(castVoteRecord.getSlice(ContestConfig.TabulateBySlice.PRECINCT));
csvPrinter.print(castVoteRecord.getPrecinctPortion());
printRankings(currentSourceData.source.getUndeclaredWriteInLabel(), maxRank,
Expand Down
196 changes: 196 additions & 0 deletions src/test/java/network/brightspots/rcv/CastVoteRecordTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* RCTab
* Copyright (c) 2025 Ranked Choice Voting Resource Center.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

/*
* Purpose: These unit tests test various components of the CastVoteRecord class.
* Design: Passing these tests ensures that changes to code have not altered how the CastVoteRecord
* class works in unexpected ways. (Warning: these unit tests do not provide full test coverage of
* the CastVoteRecord class.)
* Conditions: During automated testing.
* Version history: see https://github.com/BrightSpots/rcv.
*/

package network.brightspots.rcv;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayList;
import java.util.List;
import javafx.util.Pair;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/**
* Unit tests for the CastVoteRecord.
*/
public class CastVoteRecordTests {
String defaultComputedId = "computed123";
String defualtSuppliedId = "supplied456";
String idWithSpecialCharacters = "~!@#$%^&*(){}|:<>?[]`,./";
String precinct = "precinct1";
String batchId = "batch1";
boolean usesLastAllowedRanking = false;
List<Pair<Integer, String>> rankings = new ArrayList<>();

@Nested
@DisplayName("getId() tests")
class GetIdTests {
@Test
@DisplayName("returns computedId when present and not blank")
void returnsComputedIdWhenPresent() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
defaultComputedId,
defualtSuppliedId,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = defaultComputedId;
String actualId = cvr.getId();

// Assert
assertEquals(expectId, actualId);
}

@Test
@DisplayName("returns suppliedId when computedId is null")
void returnsSuppliedIdWhenComputedIdNull() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
null,
defualtSuppliedId,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = defualtSuppliedId;
String actualId = cvr.getId();

// Assert
assertEquals(expectId, actualId);
}

@Test
@DisplayName("returns suppliedId when computedId is blank")
void returnsSuppliedIdWhenComputedIdBlank() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
"",
defualtSuppliedId,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = defualtSuppliedId;
String actualId = cvr.getId();

// Assert
assertEquals(expectId, actualId);
}

@Test
@DisplayName("returns computedId with special characters exactly as is")
void handlesSpecialCharactersInComputedId() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
idWithSpecialCharacters,
defualtSuppliedId,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = idWithSpecialCharacters;
String actualId = cvr.getId();

// Assert
assertEquals(expectId, actualId);
}
}

@Nested
@DisplayName("getSuppliedId() tests")
class GetSuppliedIdTests {
@Test
@DisplayName("returns suppliedId when present")
void returnsSuppliedIdWhenPresent() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
defaultComputedId,
defualtSuppliedId,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = defualtSuppliedId;
String actualId = cvr.getSuppliedId();

// Assert
assertEquals(expectId, actualId);
}

@Test
@DisplayName("returns empty string when suppliedId is null")
nurse-the-code marked this conversation as resolved.
Show resolved Hide resolved
void returnsEmptyStringWhenNull() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
defaultComputedId,
null,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = "";
String actualId = cvr.getSuppliedId();

// Assert
assertEquals(expectId, actualId);
}

@Test
@DisplayName("returns suppliedId with special characters exactly as is")
void handlesSpecialCharacters() {
// Arrange
CastVoteRecord cvr = new CastVoteRecord(
defaultComputedId,
idWithSpecialCharacters,
precinct,
batchId,
usesLastAllowedRanking,
rankings
);

// Act
String expectId = idWithSpecialCharacters;
String actualId = cvr.getSuppliedId();

// Assert
assertEquals(expectId, actualId);
}
}
}
Loading
Loading