package uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support.runrequest; import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; import org.apache.commons.io.FileUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.kubek2k.springockito.annotations.ReplaceWithMock; import org.kubek2k.springockito.annotations.SpringockitoContextLoader; import org.kubek2k.springockito.annotations.WrapWithSpy; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import uk.ac.ox.zoo.seeg.abraid.mp.common.AbstractCommonSpringIntegrationTests; import uk.ac.ox.zoo.seeg.abraid.mp.common.config.ConfigurationService; import uk.ac.ox.zoo.seeg.abraid.mp.common.dao.ModelRunDao; import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.*; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.CovariateService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.DiseaseService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.EmailService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.GeometryService; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support.GitSourceCodeManager; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support.ModelRunOccurrencesSelectorHelper; import uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support.ModelRunWorkflowException; import uk.ac.ox.zoo.seeg.abraid.mp.common.web.RasterFilePathFactory; import uk.ac.ox.zoo.seeg.abraid.mp.common.web.WebServiceClient; import uk.ac.ox.zoo.seeg.abraid.mp.common.web.WebServiceClientException; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; 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.Matchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * Contains integration tests for the ModelRunRequester class. * * Copyright (c) 2014 University of Oxford */ @ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:uk/ac/ox/zoo/seeg/abraid/mp/common/config/beans.xml") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class ModelRunRequesterIntegrationTest extends AbstractCommonSpringIntegrationTests { @Rule public TemporaryFolder testFolder = new TemporaryFolder(); ///CHECKSTYLE:SUPPRESS VisibilityModifier @Autowired private DiseaseService diseaseService; @Autowired private GeometryService geometryService; @ReplaceWithMock @Autowired protected CovariateService covariateService; @ReplaceWithMock @Autowired protected WebServiceClient webServiceClient; @WrapWithSpy @Autowired protected ModelWrapperWebService modelWrapperWebService; @Autowired private ModelRunRequester modelRunRequester; @Autowired private ModelRunDao modelRunDao; @Autowired private GitSourceCodeManager gitSourceCodeManager; @ReplaceWithMock @Autowired private EmailService emailService; @ReplaceWithMock @Autowired private ConfigurationService configurationService; @ReplaceWithMock @Autowired private RasterFilePathFactory rasterFilePathFactory; private static final String DATA_DIR = "Common/test/uk/ac/ox/zoo/seeg/abraid/mp/common/service/workflow/support/runrequest/data/testdata/"; private static final String URL = "http://api:key-to-access-model-wrapper@localhost:8080/modelwrapper/api/model/run"; @Before public void before() throws Exception { when(configurationService.getModelRepositoryUrl()).thenReturn("https://github.com/SEEG-Oxford/seegSDM.git"); when(configurationService.getModelRepositoryVersion()).thenReturn("0.1-9"); gitSourceCodeManager.updateRepository(); when(rasterFilePathFactory.getAdminRaster(0)).thenReturn(new File(DATA_DIR + "admin/a0.tif")); when(rasterFilePathFactory.getAdminRaster(1)).thenReturn(new File(DATA_DIR + "admin/a1.tif")); when(rasterFilePathFactory.getAdminRaster(2)).thenReturn(new File(DATA_DIR + "admin/a2.tif")); when(rasterFilePathFactory.getExtentGaulRaster(false)).thenReturn(new File(DATA_DIR + "SmallRaster.tif")); when(rasterFilePathFactory.getExtentGaulRaster(true)).thenReturn(new File("doesnt exist")); } @Test public void requestModelRunSucceedsWithBatching() throws IOException { requestModelRunSucceeds(DateTime.now(), DateTime.now().plusDays(2)); } @Test public void requestModelRunSucceedsWithoutBatching() throws IOException { requestModelRunSucceeds(null, null); } private void requestModelRunSucceeds(DateTime batchStartDate, DateTime batchEndDate) throws IOException { // Arrange int diseaseGroupId = 87; setDiseaseGroupParametersToEnsureSelectorReturnsOccurrences(diseaseGroupId); DateTime now = DateTime.now(); DateTimeUtils.setCurrentMillisFixed(now.getMillis()); String responseJson = "{}"; mockPostRequest(responseJson); // Note that this includes code to assert the request JSON // Act List<DiseaseOccurrence> occurrences = selectOccurrencesForModelRun(diseaseGroupId); modelRunRequester.requestModelRun(diseaseGroupId, occurrences, batchStartDate, batchEndDate); // Assert List<ModelRun> modelRuns = modelRunDao.getAll(); assertThat(modelRuns).hasSize(1); ModelRun modelRun = modelRuns.get(0); assertThat(modelRun.getName()).startsWith("deng_"); assertThat(modelRun.getDiseaseGroupId()).isEqualTo(diseaseGroupId); assertThat(modelRun.getRequestServer()).isEqualTo(URI.create(URL).getHost()); assertThat(modelRun.getRequestDate()).isEqualTo(now); assertThat(modelRun.getBatchStartDate()).isEqualTo(batchStartDate); assertThat(modelRun.getBatchEndDate()).isEqualTo(batchEndDate); assertThat(modelRun.getOccurrenceDataRangeStartDate().isEqual(DateTime.parse("2014-02-24T17:35:29.000Z"))).isTrue(); assertThat(modelRun.getOccurrenceDataRangeEndDate().isEqual(DateTime.parse("2014-02-27T08:06:46.000Z"))).isTrue(); } private void setDiseaseGroupParametersToEnsureSelectorReturnsOccurrences(int diseaseGroupId) throws IOException { DiseaseGroup diseaseGroup = diseaseService.getDiseaseGroupById(diseaseGroupId); diseaseGroup.setMinDataVolume(27); diseaseGroup.setOccursInAfrica(null); diseaseGroup.setModelMode("Bhatt2013"); List<CovariateFile> covariateFiles = Arrays.asList( createMockCovariateFile("a"), createMockCovariateFile("b"), createMockCovariateFile("c/d") ); createMockCovariateFile("e"); when(covariateService.getCovariateFilesByDiseaseGroup(diseaseGroup)).thenReturn(covariateFiles); when(covariateService.getCovariateDirectory()).thenReturn(testFolder.getRoot().getAbsolutePath()); } private CovariateFile createMockCovariateFile(String path) throws IOException { CovariateFile covariateFile = mock(CovariateFile.class); CovariateSubFile subObj = mock(CovariateSubFile.class); when(subObj.getFile()).thenReturn(path); when(covariateFile.getFiles()).thenReturn(Arrays.asList(subObj)); File file = Paths.get(testFolder.getRoot().getAbsolutePath(), path).toFile(); FileUtils.writeStringToFile(file, path); return covariateFile; } @Test public void requestModelRunWithErrorReturnedByModelThrowsException() throws IOException { // Arrange int diseaseGroupId = 87; setDiseaseGroupParametersToEnsureSelectorReturnsOccurrences(diseaseGroupId); String responseJson = "{\"errorText\":\"testerror\"}"; mockPostRequest(responseJson); // Note that this includes code to assert the request JSON // Act List<DiseaseOccurrence> occurrences = selectOccurrencesForModelRun(diseaseGroupId); catchException(modelRunRequester).requestModelRun(diseaseGroupId, occurrences, null, null); // Assert assertThat(caughtException()).isInstanceOf(ModelRunWorkflowException.class); } @Test public void requestModelRunWithWebClientExceptionThrowsException() throws IOException { // Arrange int diseaseGroupId = 87; setDiseaseGroupParametersToEnsureSelectorReturnsOccurrences(diseaseGroupId); String exceptionMessage = "Web service failed"; WebServiceClientException thrownException = new WebServiceClientException(exceptionMessage); when(webServiceClient.makePostRequestWithJSON(eq(URL), anyString())).thenThrow(thrownException); // Act List<DiseaseOccurrence> occurrences = selectOccurrencesForModelRun(diseaseGroupId); catchException(modelRunRequester).requestModelRun(diseaseGroupId, occurrences, null, null); // Assert assertThat(caughtException()).isInstanceOf(ModelRunWorkflowException.class); } @Test public void requestModelRunWithNoDiseaseOccurrencesThrowsException() { // Arrange int diseaseGroupId = 87; // Act List<DiseaseOccurrence> occurrences = new ArrayList<>(); catchException(modelRunRequester).requestModelRun(diseaseGroupId, occurrences, null, null); // Assert assertThat(caughtException()).isInstanceOf(ModelRunWorkflowException.class); assertThat(caughtException()).hasMessage("Cannot request a model run because there are no occurrences"); } private List<DiseaseOccurrence> selectOccurrencesForModelRun(int diseaseGroupId) { ModelRunOccurrencesSelectorHelper selector = new ModelRunOccurrencesSelectorHelper(diseaseService, geometryService, emailService, diseaseGroupId, false); return selector.selectModelRunDiseaseOccurrences(); } private void mockPostRequest(final String responseJson) { when(webServiceClient.makePostRequestWithBinary(eq(URL), any(File.class))).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws IOException, ZipException { File data = (File) invocationOnMock.getArguments()[1]; ZipFile zipFile = new ZipFile(data); File unzipped = testFolder.newFile(); unzipped.delete(); zipFile.extractAll(unzipped.getAbsolutePath()); assertRequestMetadataJson(FileUtils.readFileToString(Paths.get(unzipped.getAbsolutePath(), "metadata.json").toFile())); assertRequestCovariates(Paths.get(unzipped.getAbsolutePath(), "covariates").toFile()); assertRequestOccurrenceCsv(Paths.get(unzipped.getAbsolutePath(), "data/occurrences.csv").toFile()); assertRequestExtentTif(Paths.get(unzipped.getAbsolutePath(), "data/extent.tif").toFile()); assertRequestAdminRasters(Paths.get(unzipped.getAbsolutePath(), "admins").toFile()); return responseJson; } }); } private void assertRequestOccurrenceCsv(File csv) throws IOException { List<String> splitFeatures = getSplitFeatures(FileUtils.readFileToString(csv)); assertSplitFeatures(splitFeatures); } private void assertRequestMetadataJson(String requestJson) { Pattern regexp = Pattern.compile("\\{\"disease\"\\:\\{(.+?)},\"runName\"\\:\"(.+?)\"\\}"); Matcher matcher = regexp.matcher(requestJson); assertThat(matcher.find()).isTrue(); assertThat(matcher.groupCount()).isEqualTo(2); assertThat(matcher.group(1).split(",")).containsOnly("\"id\":87", "\"name\":\"Dengue\"", "\"abbreviation\":\"deng\"", "\"global\":false"); assertThat(matcher.group(2)).startsWith("deng_"); } private void assertRequestCovariates(File covariatesDir) { Collection<File> files = FileUtils.listFiles(covariatesDir, null, true); List<File> indexable = new ArrayList<>(files); assertThat(files).hasSize(3); assertThat(indexable.get(0).getAbsolutePath()).isEqualTo(Paths.get(covariatesDir.getAbsolutePath(), "a").toString()); assertThat(indexable.get(0)).hasContent("a"); assertThat(indexable.get(1).getAbsolutePath()).isEqualTo(Paths.get(covariatesDir.getAbsolutePath(), "b").toString()); assertThat(indexable.get(1)).hasContent("b"); assertThat(indexable.get(2).getAbsolutePath()).isEqualTo(Paths.get(covariatesDir.getAbsolutePath(), "c", "d").toString()); assertThat(indexable.get(2)).hasContent("c/d"); } private List<String> getSplitFeatures(String features) { return Arrays.asList(features.split("\n")); } private void assertSplitFeatures(List<String> splitFeatures) { assertThat(splitFeatures).hasSize(27 + 1); assertThat(splitFeatures).contains( "Longitude,Latitude,Weight,Admin,GAUL,Disease,Date", "121.06667,14.53333,0.95,-999,NA,87,2014-02-27", "-46.60972,-20.71889,0.825,-999,NA,87,2014-02-26", "-42.91651,-22.17062,0.775,2,9970,87,2014-02-26", "-42.66564,-22.18996,0.675,1,683,87,2014-02-26", "-43.04112,-22.81555,0.85,2,9966,87,2014-02-26", "-54.66252,-28.05186,0.775,2,10593,87,2014-02-26", "-54.0,-30.0,0.625,1,685,87,2014-02-26", "-67.81,-9.97472,0.8,-999,NA,87,2014-02-26", "-76.42313,8.84621,0.925,-999,NA,87,2014-02-26", "73.85674,18.52043,0.975,-999,NA,87,2014-02-26", "102.25616,2.20569,0.975,-999,NA,87,2014-02-26", "-45.88694,-23.17944,0.8,-999,NA,87,2014-02-25", "114.0,1.0,1.0,-999,NA,87,2014-02-25", "-47.09179,-21.76979,0.775,-999,NA,87,2014-02-25", "-49.06055,-22.31472,0.9,-999,NA,87,2014-02-25", "103.80805,1.29162,0.875,-999,NA,87,2014-02-25", "126.08934,7.30416,0.7,1,67161,87,2014-02-25", "126.33333,7.16667,0.85,2,24269,87,2014-02-25", "126.0,7.5,0.75,2,24266,87,2014-02-25", "126.17626,7.51252,0.975,-999,NA,87,2014-02-25", "-98.28333,26.08333,0.9,-999,NA,87,2014-02-25", "39.21917,21.51694,0.85,-999,NA,87,2014-02-24", "-51.38889,-22.12556,0.85,-999,NA,87,2014-02-24", "177.46666,-17.61667,0.825,-999,NA,87,2014-02-24", "177.41667,-17.8,0.925,-999,NA,87,2014-02-24", "-61.5,-17.5,0.7,1,40449,87,2014-02-24", "-80.63333,-5.2,0.875,-999,NA,87,2014-02-24" ); } private void assertRequestExtentTif(File extent) { assertThat(extent).hasContentEqualTo(new File(DATA_DIR + "integration_extent.tif")); } private void assertRequestAdminRasters(File dir) { Collection<File> files = FileUtils.listFiles(dir, null, true); List<File> indexable = new ArrayList<>(files); assertThat(files).hasSize(3); assertThat(indexable.get(0).getAbsolutePath()).isEqualTo(Paths.get(dir.toString(), "admin0.tif").toString()); assertThat(indexable.get(0)).hasContentEqualTo(Paths.get(DATA_DIR, "admin", "a0.tif").toFile()); assertThat(indexable.get(1).getAbsolutePath()).isEqualTo(Paths.get(dir.toString(), "admin1.tif").toString()); assertThat(indexable.get(1)).hasContentEqualTo(Paths.get(DATA_DIR, "admin", "a1.tif").toFile()); assertThat(indexable.get(2).getAbsolutePath()).isEqualTo(Paths.get(dir.toString(), "admin2.tif").toString()); assertThat(indexable.get(2)).hasContentEqualTo(Paths.get(DATA_DIR, "admin", "a2.tif").toFile()); } }