diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/model/Administrator.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/model/Administrator.java index d3b23737..47084bb3 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/model/Administrator.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/model/Administrator.java @@ -14,23 +14,10 @@ import static us.freeandfair.corla.util.EqualsHashcodeHelper.*; import java.io.Serializable; +import java.time.Clock; import java.time.Instant; -import javax.persistence.Cacheable; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Index; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; -import javax.persistence.Version; +import javax.persistence.*; import us.freeandfair.corla.persistence.PersistentEntity; @@ -106,6 +93,12 @@ public class Administrator implements PersistentEntity, Serializable { * The last logout time. */ private Instant my_last_logout_time; + + /** + * A clock that is only used for testing + */ + @Transient + private Clock my_clock; /** * Constructs a new Administrator with default values. @@ -133,7 +126,25 @@ public Administrator(final String the_username, my_full_name = the_full_name; my_county = the_county; } - + + /** + * Same as above, but injects a clock for use with testing. + * + * @param the_username + * @param the_type + * @param the_full_name + * @param the_county + * @param clock + */ + public Administrator(final String the_username, + final AdministratorType the_type, + final String the_full_name, + final County the_county, + final Clock clock) { + this(the_username, the_type, the_full_name, the_county); + my_clock = clock; + } + /** * {@inheritDoc} */ @@ -197,7 +208,12 @@ public Instant lastLoginTime() { * Updates the last login time to the current time. */ public void updateLastLoginTime() { - my_last_login_time = Instant.now(); + // If we're not testing, there will be no clock + if (my_clock == null) { + my_last_login_time = Instant.now(); + } else { + my_last_login_time = Instant.now(my_clock); + } } /** @@ -211,7 +227,12 @@ public Instant lastLogoutTime() { * Updates the last logout time to the current time. */ public void updateLastLogoutTime() { - my_last_logout_time = Instant.now(); + // If we're not testing, there will be no clock + if (my_clock == null) { + my_last_logout_time = Instant.now(); + } else { + my_last_logout_time = Instant.now(my_clock); + } } /** diff --git a/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java b/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java index d7772a84..a290ca67 100644 --- a/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java +++ b/server/eclipse-project/src/main/java/us/freeandfair/corla/query/ExportQueries.java @@ -11,10 +11,8 @@ import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; -import au.org.democracydevelopers.corla.endpoint.AbstractAllIrvEndpoint; import au.org.democracydevelopers.corla.model.ContestType; import au.org.democracydevelopers.corla.model.GenerateAssertionsSummary; import au.org.democracydevelopers.corla.model.IRVComparisonAudit; diff --git a/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AdministratorTest.java b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AdministratorTest.java new file mode 100644 index 00000000..66e4723c --- /dev/null +++ b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AdministratorTest.java @@ -0,0 +1,115 @@ +package us.freeandfair.corla.model; + +import org.testng.annotations.Test; +import us.freeandfair.corla.persistence.Persistence; +import us.freeandfair.corla.util.TestClassWithDatabase; + +import java.time.Clock; +import java.time.Instant; + +import static org.testng.Assert.assertNotEquals; +import static org.testng.AssertJUnit.*; +import static us.freeandfair.corla.util.EqualsHashcodeHelper.nullableHashCode; + +@Test +public class AdministratorTest extends TestClassWithDatabase { + + @Test + public static void testGettersAndSetters() { + String expectedUsername = "testname"; + Administrator.AdministratorType expectedType = Administrator.AdministratorType.STATE; + String expectedFullname = "fulltestname"; + + // Make the clock stuck + Clock testClock = Clock.fixed(Instant.now(), Clock.systemDefaultZone().getZone()); + Administrator admin = new Administrator(expectedUsername, + expectedType, + expectedFullname, + null, + testClock); + + + assertNull(admin.id()); + assertNull(admin.version()); + Persistence.saveOrUpdate(admin); + + // this is a database constraint + assertNotNull(admin.id()); + + Long expectedID = 42L; + admin.setID(expectedID); + assertEquals(expectedID, admin.id()); + + assertEquals(expectedUsername, admin.username()); + assertEquals(expectedFullname, admin.fullName()); + assertEquals(expectedType, admin.type()); + assertNull(admin.county()); + + // Because we're using a stopped clock, this time will be the same for both + Instant expectedTime = testClock.instant(); + assertNull(admin.lastLoginTime()); + admin.updateLastLoginTime(); + assertEquals(expectedTime, admin.lastLoginTime()); + + assertNull(admin.lastLogoutTime()); + admin.updateLastLogoutTime(); + assertEquals(expectedTime, admin.lastLogoutTime()); + + String expected_string = "Administrator [username=" + expectedUsername + ", type=" + + expectedType+ ", full_name=" + expectedFullname+ ", county=" + + null + ", last_login_time=" + expectedTime + + ", last_logout_time=" + expectedTime + "]"; + + assertEquals(expected_string, admin.toString()); + + /** + * Now test an admin without a clock set. This means we can't test timing-specific things, + * but we can at least test that the code paths behave the way we expect. + */ + admin = new Administrator(expectedUsername, + expectedType, + expectedFullname, + null); + + assertNull(admin.lastLoginTime()); + assertNull(admin.lastLogoutTime()); + + admin.updateLastLoginTime(); + assertNotNull(admin.lastLoginTime()); + admin.updateLastLogoutTime(); + assertNotNull(admin.lastLogoutTime()); + assert(admin.lastLoginTime() != admin.lastLogoutTime()); + } + + @Test + public static void testEquality() { + String firstAdminUsername = "first"; + String secondAdminUsername = "second"; + Administrator.AdministratorType expectedType = Administrator.AdministratorType.STATE; + String expectedFullname = "fulltestname"; + + Administrator firstAdmin = new Administrator(firstAdminUsername, expectedType, expectedFullname, null); + Administrator secondAdmin = new Administrator(secondAdminUsername, expectedType, expectedFullname, null); + + assertTrue(firstAdmin.equals(firstAdmin)); + assertFalse(firstAdmin.equals(secondAdmin)); + + // Now test that non-admin objects result in false + assertFalse(firstAdmin.equals(firstAdminUsername)); + } + + @Test + public static void testHash() { + String expectedUsername = "testname"; + Administrator.AdministratorType expectedType = Administrator.AdministratorType.STATE; + String expectedFullname = "fulltestname"; + + Administrator admin = new Administrator(expectedUsername, + expectedType, + expectedFullname, + null); + + assertEquals(admin.hashCode(), nullableHashCode(expectedUsername)); + + } +} diff --git a/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditBoardTest.java b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditBoardTest.java new file mode 100644 index 00000000..6e8a3633 --- /dev/null +++ b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditBoardTest.java @@ -0,0 +1,59 @@ +package us.freeandfair.corla.model; + +import org.testng.annotations.Test; +import us.freeandfair.corla.util.TestClassWithDatabase; + +import java.lang.reflect.Array; +import java.time.Instant; + +import java.util.ArrayList; + +import static org.testng.AssertJUnit.*; +import static us.freeandfair.corla.util.EqualsHashcodeHelper.nullableHashCode; + +public class AuditBoardTest extends TestClassWithDatabase { + + @Test + public static void testGettersAndSetters () { + + AuditBoard defaultAB = new AuditBoard(); + assertEquals(new ArrayList(), defaultAB.members()); + assertNull(defaultAB.signInTime()); + assertNull(defaultAB.signOutTime()); + + ArrayList electors = new ArrayList<>(); + + electors.add(new Elector("firstname", "lastname", "party")); + + // TODO: do we want a stopped clock here too? + Instant sign_in_time = Instant.now(); + + AuditBoard ab = new AuditBoard(electors, sign_in_time); + + assertEquals(electors, ab.members()); + + assertEquals(sign_in_time, ab.signInTime()); + + Instant sign_out_time = Instant.now(); + + ab.setSignOutTime(sign_out_time); + assertEquals(sign_out_time, ab.signOutTime()); + + String expectedToString = "AuditBoard [members=" + electors + ", sign_in_time=" + + sign_in_time + ", sign_out_time=" + sign_out_time + "]"; + + assertEquals(expectedToString, ab.toString()); + + assertEquals(nullableHashCode(sign_in_time), ab.hashCode()); + + assertFalse(ab.equals("Not an auditboard")); + ArrayList otherElectors = new ArrayList<>(); + otherElectors.add(new Elector("otherfirstname", "otherlastname", "otherparty")); + + AuditBoard otherAB = new AuditBoard(otherElectors, sign_in_time); + + assertFalse(ab.equals(otherAB)); + assertTrue(ab.equals(ab)); + + } +} diff --git a/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditInfoTest.java b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditInfoTest.java index 7ddad3be..147cffd2 100644 --- a/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditInfoTest.java +++ b/server/eclipse-project/src/test/java/us/freeandfair/corla/model/AuditInfoTest.java @@ -23,8 +23,9 @@ import us.freeandfair.corla.csv.ContestNameParser; import us.freeandfair.corla.json.VersionExclusionStrategy; +import us.freeandfair.corla.util.TestClassWithDatabase; -public class AuditInfoTest { +public class AuditInfoTest extends TestClassWithDatabase { public Gson gson; @BeforeClass @@ -33,7 +34,7 @@ public void AuditInfoTest() { } @Test() - private void canonicalContestsTest() { + private void testCanonicalContests() { Map> contestMap = new TreeMap>(); Set contests = new HashSet(); @@ -79,7 +80,7 @@ public String contestsFromJSON(final String json) { // @SuppressWarnings("PMD.DoNotUseThreads") // @SuppressFBWarnings(value = {"URF_UNREAD_FIELD"}, // justification = "JSON blobs are big") - public void parsingTest() { + public void testParsing() { final String json = "{\"election_date\":\"2018-07-31T06:00:00.000Z\",\"election_type\":\"coordinated\",\"public_meeting_date\":\"2018-08-07T06:00:00.000Z\",\"risk_limit\":0.05,\"upload_file\":[{\"preview\":\"blob:http://localhost:3000/54a4f865-9d2a-b442-881a-8630050977dc\",\"contents\":'\"CountyName\",\"ContestName\"\n\"Boulder\",\"Kombucha - DEM\"\n\"Boulder\",\"Kale - DEM\"\n\"Denver\",\"IPA - DEM\"\n\"Denver\",\"Porter - REP\"\n\"Chaffee\",\"Hooligan Race - DEM\"\n\"Chaffee\",\"Pole Pedal Paddle - DEM\"\n\"Chaffee\",\"Gunbarrel Challenge - REP\"\n'}]}"; String contests = contestsFromJSON(json);