package uk.ac.ox.zoo.seeg.abraid.mp.dataacquisition.acquirers;
import com.vividsolutions.jts.geom.Point;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.*;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.DiseaseService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.LocationService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.DiseaseOccurrenceValidationService;
import uk.ac.ox.zoo.seeg.abraid.mp.common.util.GeometryUtils;
import uk.ac.ox.zoo.seeg.abraid.mp.dataacquisition.qc.PostQCManager;
import uk.ac.ox.zoo.seeg.abraid.mp.dataacquisition.qc.QCManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.googlecode.catchexception.CatchException.catchException;
import static com.googlecode.catchexception.CatchException.caughtException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
/**
* Tests the DiseaseOccurrenceDataAcquirer class.
* Copyright (c) 2014 University of Oxford
*/
public class DiseaseOccurrenceDataAcquirerTest {
private LocationService locationService;
private DiseaseService diseaseService;
private DiseaseOccurrenceValidationService diseaseOccurrenceValidationService;
private QCManager qcManager;
private PostQCManager postQcManager;
private DiseaseOccurrenceDataAcquirer acquirer;
@Before
public void setUp() {
diseaseService = mock(DiseaseService.class);
locationService = mock(LocationService.class);
diseaseOccurrenceValidationService = mock(DiseaseOccurrenceValidationService.class);
qcManager = mock(QCManager.class);
postQcManager = mock(PostQCManager.class);
acquirer = new DiseaseOccurrenceDataAcquirer(diseaseService, locationService,
diseaseOccurrenceValidationService, qcManager, postQcManager, 365);
}
@Test
public void acquireDoesNotSaveIfOccurrenceAlreadyExists() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
locationIsKnownToAlreadyExist(occurrence);
mockDiseaseOccurrenceAlreadyExists(occurrence, true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(result).isFalse();
verify(diseaseService, never()).saveDiseaseOccurrence(any(DiseaseOccurrence.class));
}
@Test
public void acquireDoSaveEvenIfOccurrenceAlreadyForBias() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.setBiasDisease(mock(DiseaseGroup.class));
locationIsKnownToAlreadyExist(occurrence);
mockDiseaseOccurrenceAlreadyExists(occurrence, true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(result).isTrue();
verify(diseaseService).saveDiseaseOccurrence(occurrence);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsWithoutGeoNameOnEitherLocation() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
Location existingLocation = new Location();
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsWithGeoNameOnBothLocations() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.getLocation().setGeoNameId(123);
Location existingLocation = new Location();
existingLocation.setGeoNameId(123);
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsWithGeoNameOnlyOnExistingLocation() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
Location existingLocation = new Location();
existingLocation.setGeoNameId(123);
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsMultipleTimesWithoutGeoNameOnEitherLocation() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
Location existingLocation1 = new Location();
Location existingLocation2 = new Location();
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation1, existingLocation2));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation1);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsMultipleTimesWithGeoNameOnlyOnOneExistingLocation() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
Location existingLocation1 = new Location();
Location existingLocation2 = new Location();
existingLocation2.setGeoNameId(123);
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation1, existingLocation2));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation2);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsMultipleTimesWithGeoNameOnAllLocations() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.getLocation().setGeoNameId(123);
Location existingLocation1 = new Location();
existingLocation1.setGeoNameId(123);
Location existingLocation2 = new Location();
existingLocation2.setGeoNameId(321);
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation1, existingLocation2));
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation1);
assertThat(occurrence.getLocation().hasPassedQc()).isFalse();
verify(qcManager, never()).performQC(any(Location.class));
verify(postQcManager, never()).runPostQCProcesses(any(Location.class));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesUsingExistingLocationIfLocationAlreadyExistsAfterPerformingQC() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
Location currentLocation = occurrence.getLocation();
Location existingLocation = new Location();
Point pointAfterQc = GeometryUtils.createPoint(50, 60);
mockGetLocationsByPointAndPrecision(currentLocation.getGeom(), currentLocation.getPrecision(),
new ArrayList<Location>());
mockGetLocationsByPointAndPrecision(pointAfterQc, currentLocation.getPrecision(),
Arrays.asList(existingLocation));
mockRunQCWithPointChange(currentLocation, pointAfterQc, true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation()).isSameAs(existingLocation);
verify(qcManager).performQC(same(currentLocation));
verify(postQcManager).runPostQCProcesses(same(currentLocation));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesIfLocationIsNew() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
new ArrayList<Location>());
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesIfLocationIsNewWithGeoName() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.getLocation().setGeoNameId(123);
Location existingLocation1 = new Location();
Location existingLocation2 = new Location();
existingLocation2.setGeoNameId(321);
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
Arrays.asList(existingLocation1, existingLocation2));
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, false, result);
}
@Test
public void acquireSavesGoldStandardDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = createGoldStandardOccurrence();
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
new ArrayList<Location>());
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, true, result);
}
@Test
public void acquireRejectsNullDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = null;
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(result).isFalse();
verify(diseaseService, never()).saveDiseaseOccurrence(any(DiseaseOccurrence.class));
}
@Test
public void acquireRejectsOutdatedDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.setOccurrenceDate(DateTime.now().minusYears(1).minusDays(1));
// Act
catchException(acquirer).acquire(occurrence);
// Assert
assertThat(caughtException()).hasMessage("Occurrence date for occurrence is older than the max allowable age");
verify(diseaseService, never()).saveDiseaseOccurrence(any(DiseaseOccurrence.class));
}
@Test
public void acquireSavesOutdatedGoldStandardCSVDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = createGoldStandardOccurrence();
occurrence.setOccurrenceDate(DateTime.now().minusYears(1).minusDays(1));
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
new ArrayList<Location>());
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, true, result);
}
@Test
public void acquireSavesOutdatedNoGoldStandardCSVDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = createGoldStandardOccurrence();
occurrence.setOccurrenceDate(DateTime.now().minusYears(1).minusDays(1));
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
new ArrayList<Location>());
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, true, result);
}
@Test
public void acquireRejectsFutureDiseaseOccurrence() {
// Arrange
DiseaseOccurrence occurrence = createDefaultDiseaseOccurrence();
occurrence.setOccurrenceDate(DateTime.now().plusDays(2));
// Act
catchException(acquirer).acquire(occurrence);
// Assert
assertThat(caughtException()).hasMessage("Occurrence date for occurrence is in the future");
verify(diseaseService, never()).saveDiseaseOccurrence(any(DiseaseOccurrence.class));
}
@Test
public void acquireSavesOccurrenceJustInTheFuture() {
// Arrange -- This is to protect against date time zone edge cases
DiseaseOccurrence occurrence = createGoldStandardOccurrence();
occurrence.setOccurrenceDate(DateTime.now().plusDays(1));
mockGetLocationsByPointAndPrecision(occurrence.getLocation().getGeom(), occurrence.getLocation().getPrecision(),
new ArrayList<Location>());
mockRunQC(occurrence.getLocation(), true);
// Act
boolean result = acquirer.acquire(occurrence);
// Assert
assertThat(occurrence.getLocation().hasPassedQc()).isTrue();
verify(postQcManager).runPostQCProcesses(same(occurrence.getLocation()));
verifySuccessfulSave(occurrence, true, result);
}
private DiseaseOccurrence createDefaultDiseaseOccurrence() {
DiseaseOccurrence occurrence = new DiseaseOccurrence();
Location location = new Location(20, 10, LocationPrecision.ADMIN1);
occurrence.setLocation(location);
occurrence.setOccurrenceDate(DateTime.now());
setAlert(occurrence, ProvenanceNames.HEALTHMAP);
return occurrence;
}
private DiseaseOccurrence createGoldStandardOccurrence() {
DiseaseOccurrence occurrence = new DiseaseOccurrence();
Location location = new Location(20, 10, LocationPrecision.ADMIN1);
occurrence.setLocation(location);
occurrence.setOccurrenceDate(DateTime.now());
setAlert(occurrence, ProvenanceNames.MANUAL_GOLD_STANDARD);
return occurrence;
}
private DiseaseOccurrence createManualCSVOccurrence() {
DiseaseOccurrence occurrence = new DiseaseOccurrence();
Location location = new Location(20, 10, LocationPrecision.ADMIN1);
occurrence.setLocation(location);
occurrence.setOccurrenceDate(DateTime.now());
setAlert(occurrence, ProvenanceNames.MANUAL);
return occurrence;
}
private void setAlert(DiseaseOccurrence occurrence, String provenanceName) {
Feed feed = new Feed("Manual data", new Provenance(provenanceName));
Alert alert = new Alert();
alert.setFeed(feed);
occurrence.setAlert(alert);
}
private void locationIsKnownToAlreadyExist(DiseaseOccurrence occurrence) {
occurrence.setLocation(new Location(1));
verify(locationService, never()).getLocationsByPointAndPrecision(any(Point.class),
any(LocationPrecision.class));
}
private void mockDiseaseOccurrenceAlreadyExists(DiseaseOccurrence occurrence, boolean response) {
when(diseaseService.doesDiseaseOccurrenceExist(same(occurrence))).thenReturn(response);
}
private void mockGetLocationsByPointAndPrecision(Point point, LocationPrecision precision,
List<Location> existingLocations) {
when(locationService.getLocationsByPointAndPrecision(argThat(new PointMatcher(point)),
eq(precision))).thenReturn(existingLocations);
}
private void mockRunQC(Location location, boolean result) {
when(qcManager.performQC(same(location))).thenReturn(result);
}
private void mockRunQCWithPointChange(final Location location, final Point pointAfterQc,
final boolean result) {
doAnswer(new Answer() {
@Override
public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
location.setGeom(pointAfterQc);
return result;
}
}).when(qcManager).performQC(same(location));
}
private void verifySuccessfulSave(DiseaseOccurrence occurrence, boolean isGoldStandard, boolean result) {
verify(diseaseOccurrenceValidationService).addValidationParametersWithChecks(same(occurrence));
verify(diseaseService).saveDiseaseOccurrence(same(occurrence));
assertThat(result).isTrue();
}
/**
* Uses Point.equalsExact() instead of Point.equals() (the latter seems unreliable).
*/
static class PointMatcher extends ArgumentMatcher<Point> {
private final Point expected;
public PointMatcher(Point expected) {
this.expected = expected;
}
@Override
public boolean matches(Object actual) {
if (expected == actual) {
return true;
}
if (expected == null || actual == null || actual.getClass() != Point.class) {
return false;
}
return expected.equalsExact((Point) actual);
}
}
}