diff --git a/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java new file mode 100644 index 000000000..a9d596bcf --- /dev/null +++ b/src/main/java/org/breedinginsight/api/model/v1/request/query/GenotypeImportQuery.java @@ -0,0 +1,69 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.breedinginsight.api.model.v1.request.query; + +import io.micronaut.core.annotation.Introspected; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Introspected +public class GenotypeImportQuery extends QueryParams { + + private String sampleSubmissionId; + private String projectNameForSampleSubmission; + private String sampleSubmissionCreatedBy; + private String genotypingFileName; + private String genotypingImportDate; + private String genotypingImportBy; + + public SearchRequest constructSearchRequest() { + List filters = new ArrayList<>(); + + if (!StringUtils.isBlank(getSampleSubmissionId())) { + filters.add(constructFilterRequest("sampleSubmissionId", getSampleSubmissionId())); + } + if (!StringUtils.isBlank(getProjectNameForSampleSubmission())) { + filters.add(constructFilterRequest("projectNameForSampleSubmission", getProjectNameForSampleSubmission())); + } + if (!StringUtils.isBlank(getSampleSubmissionCreatedBy())) { + filters.add(constructFilterRequest("sampleSubmissionCreatedBy", getSampleSubmissionCreatedBy())); + } + if (!StringUtils.isBlank(getGenotypingFileName())) { + filters.add(constructFilterRequest("genotypingFileName", getGenotypingFileName())); + } + if (!StringUtils.isBlank(getGenotypingImportDate())) { + filters.add(constructFilterRequest("genotypingImportDate", getGenotypingImportDate())); + } + if (!StringUtils.isBlank(getGenotypingImportBy())) { + filters.add(constructFilterRequest("genotypingImportBy", getGenotypingImportBy())); + } + + return new SearchRequest(filters); + } + + private FilterRequest constructFilterRequest(String field, String value) { + return FilterRequest.builder() + .field(field) + .value(value) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java index 5f8540cae..9d07bf79c 100644 --- a/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java +++ b/src/main/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadController.java @@ -1,3 +1,19 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.breedinginsight.api.v1.controller.geno; import io.micronaut.http.HttpResponse; @@ -6,18 +22,26 @@ import io.micronaut.http.multipart.CompletedFileUpload; import lombok.extern.slf4j.Slf4j; import org.brapi.client.v2.model.exceptions.ApiException; -import org.breedinginsight.api.auth.AuthenticatedUser; -import org.breedinginsight.api.auth.ProgramSecured; -import org.breedinginsight.api.auth.ProgramSecuredRole; -import org.breedinginsight.api.auth.SecurityService; +import org.breedinginsight.api.auth.*; +import org.breedinginsight.api.model.v1.request.query.GenotypeImportQuery; +import org.breedinginsight.api.model.v1.request.query.SearchRequest; +import org.breedinginsight.api.model.v1.response.DataResponse; import org.breedinginsight.api.model.v1.response.Response; +import org.breedinginsight.api.model.v1.validators.QueryValid; import org.breedinginsight.api.v1.controller.metadata.AddMetadata; import org.breedinginsight.brapps.importer.model.response.ImportResponse; +import org.breedinginsight.model.GenotypeImportDetails; +import org.breedinginsight.model.Program; +import org.breedinginsight.services.ProgramService; import org.breedinginsight.services.exceptions.AuthorizationException; import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.geno.GenotypeService; +import org.breedinginsight.utilities.response.ResponseUtils; +import org.breedinginsight.utilities.response.mappers.GenotypeImportQueryMapper; import javax.inject.Inject; +import javax.validation.Valid; +import java.util.Optional; import java.util.UUID; @Slf4j @@ -25,11 +49,38 @@ public class GenotypeDataUploadController { private final GenotypeService genoService; private final SecurityService securityService; + private final ProgramService programService; + private final GenotypeImportQueryMapper genotypeImportQueryMapper; @Inject - public GenotypeDataUploadController(GenotypeService genoService, SecurityService securityService) { + public GenotypeDataUploadController(GenotypeService genoService, SecurityService securityService, + ProgramService programService, GenotypeImportQueryMapper genotypeImportQueryMapper) { this.genoService = genoService; this.securityService = securityService; + this.programService = programService; + this.genotypeImportQueryMapper = genotypeImportQueryMapper; + } + + @Get("programs/{programId}/geno/imports{?genotypeImportQuery*}") + @Produces(MediaType.APPLICATION_JSON) + @ProgramSecured(roleGroups = ProgramSecuredRoleGroup.PROGRAM_SCOPED_ROLES) + public HttpResponse>> getGenotypeImports( + @PathVariable UUID programId, + @QueryValue @QueryValid(using = GenotypeImportQueryMapper.class) @Valid GenotypeImportQuery genotypeImportQuery) { + Optional program = programService.getById(programId); + if (program.isEmpty()) { + log.info("programId not found: {}", programId.toString()); + return HttpResponse.notFound(); + } + + SearchRequest searchRequest = genotypeImportQuery.constructSearchRequest(); + + return ResponseUtils.getQueryResponse( + genoService.getGenotypeImports(programId), + genotypeImportQueryMapper, + searchRequest, + genotypeImportQuery + ); } @Post("programs/{programId}/submissions/{submissionId}/geno/import") diff --git a/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java b/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java new file mode 100644 index 000000000..c34f7baae --- /dev/null +++ b/src/main/java/org/breedinginsight/daos/GenotypeImportDAO.java @@ -0,0 +1,85 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.breedinginsight.daos; + +import io.micronaut.http.HttpStatus; +import org.breedinginsight.dao.db.tables.BiUserTable; +import org.breedinginsight.dao.db.tables.daos.GenotypeImportDao; +import org.breedinginsight.dao.db.tables.pojos.GenotypeImportEntity; +import org.breedinginsight.model.GenotypeImportDetails; +import org.jooq.Configuration; +import org.jooq.DSLContext; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.UUID; + +import static org.breedinginsight.dao.db.Tables.*; + +@Singleton +public class GenotypeImportDAO extends GenotypeImportDao { + + private final DSLContext dsl; + + @Inject + public GenotypeImportDAO(Configuration config, DSLContext dsl) { + super(config); + this.dsl = dsl; + } + + public void createGenotypeImportLink(UUID submissionId, UUID importerImportId, UUID userId) { + OffsetDateTime now = OffsetDateTime.now(); + + insert(GenotypeImportEntity.builder() + .id(UUID.randomUUID()) + .sampleSubmissionId(submissionId) + .importerImportId(importerImportId) + .createdAt(now) + .updatedAt(now) + .createdBy(userId) + .updatedBy(userId) + .build()); + } + + public List getGenotypeImportsByProgramId(UUID programId) { + BiUserTable sampleSubmissionCreatedByUser = BI_USER.as("sampleSubmissionCreatedByUser"); + BiUserTable genotypingImportByUser = BI_USER.as("genotypingImportByUser"); + + return dsl.select( + SAMPLE_SUBMISSION.ID, + SAMPLE_SUBMISSION.NAME, + sampleSubmissionCreatedByUser.NAME, + IMPORTER_IMPORT.UPLOAD_FILE_NAME, + IMPORTER_IMPORT.CREATED_AT, + genotypingImportByUser.NAME) + .from(GENOTYPE_IMPORT) + .join(SAMPLE_SUBMISSION).on(GENOTYPE_IMPORT.SAMPLE_SUBMISSION_ID.eq(SAMPLE_SUBMISSION.ID)) + .join(sampleSubmissionCreatedByUser).on(SAMPLE_SUBMISSION.CREATED_BY.eq(sampleSubmissionCreatedByUser.ID)) + .join(IMPORTER_IMPORT).on(GENOTYPE_IMPORT.IMPORTER_IMPORT_ID.eq(IMPORTER_IMPORT.ID)) + .join(IMPORTER_PROGRESS).on(IMPORTER_IMPORT.IMPORTER_PROGRESS_ID.eq(IMPORTER_PROGRESS.ID)) + .join(genotypingImportByUser).on(IMPORTER_IMPORT.USER_ID.eq(genotypingImportByUser.ID)) + .where(SAMPLE_SUBMISSION.PROGRAM_ID.eq(programId)) + .and(IMPORTER_IMPORT.PROGRAM_ID.eq(programId)) + .and(IMPORTER_PROGRESS.STATUSCODE.eq((short) HttpStatus.OK.getCode())) + .orderBy(IMPORTER_IMPORT.CREATED_AT.desc()) + .fetch(record -> GenotypeImportDetails + .parseSqlRecord(record, sampleSubmissionCreatedByUser, genotypingImportByUser)); + } + +} \ No newline at end of file diff --git a/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java new file mode 100644 index 000000000..9082750ed --- /dev/null +++ b/src/main/java/org/breedinginsight/model/GenotypeImportDetails.java @@ -0,0 +1,65 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.breedinginsight.model; + +import io.micronaut.core.annotation.Introspected; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; +import org.breedinginsight.dao.db.tables.BiUserTable; +import org.jooq.Record; + +import java.time.OffsetDateTime; +import java.util.UUID; + +import static org.breedinginsight.dao.db.Tables.IMPORTER_IMPORT; +import static org.breedinginsight.dao.db.Tables.SAMPLE_SUBMISSION; + +@Getter +@Setter +@Accessors(chain = true) +@ToString +@SuperBuilder +@NoArgsConstructor +@Introspected +@Jacksonized +public class GenotypeImportDetails { + private UUID sampleSubmissionId; + private String projectNameForSampleSubmission; + private String sampleSubmissionCreatedBy; + private String genotypingFileName; + private OffsetDateTime genotypingImportDate; + private String genotypingImportBy; + + public static GenotypeImportDetails parseSqlRecord(Record record, + BiUserTable sampleSubmissionCreatedByUser, + BiUserTable genotypingImportByUser) { + return GenotypeImportDetails.builder() + .sampleSubmissionId(record.get(SAMPLE_SUBMISSION.ID)) + .projectNameForSampleSubmission(record.get(SAMPLE_SUBMISSION.NAME)) + .sampleSubmissionCreatedBy(record.get(sampleSubmissionCreatedByUser.NAME)) + .genotypingFileName(record.get(IMPORTER_IMPORT.UPLOAD_FILE_NAME)) + .genotypingImportDate(record.get(IMPORTER_IMPORT.CREATED_AT)) + .genotypingImportBy(record.get(genotypingImportByUser.NAME)) + .build(); + } +} diff --git a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java index 7db2cd4cd..3ed088137 100644 --- a/src/main/java/org/breedinginsight/services/geno/GenotypeService.java +++ b/src/main/java/org/breedinginsight/services/geno/GenotypeService.java @@ -1,3 +1,19 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.breedinginsight.services.geno; import io.micronaut.http.multipart.CompletedFileUpload; @@ -6,11 +22,15 @@ import org.breedinginsight.model.GermplasmGenotype; import org.breedinginsight.services.exceptions.AuthorizationException; import org.breedinginsight.services.exceptions.DoesNotExistException; +import org.breedinginsight.model.GenotypeImportDetails; +import java.util.List; import java.util.UUID; public interface GenotypeService { ImportResponse submitGenotypeData(UUID userId, UUID programId, UUID submissionId, CompletedFileUpload uploadedFile) throws DoesNotExistException, AuthorizationException, ApiException; GermplasmGenotype retrieveGenotypeData(UUID programId, UUID germplasmId) throws DoesNotExistException, AuthorizationException, ApiException; + + List getGenotypeImports(UUID programId); } diff --git a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java index 076d7c981..562ec57bc 100644 --- a/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java +++ b/src/main/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImpl.java @@ -42,9 +42,11 @@ import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; +import org.breedinginsight.daos.GenotypeImportDAO; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.SampleSubmissionDAO; import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.GenotypeImportDetails; import org.breedinginsight.model.GermplasmGenotype; import org.breedinginsight.model.Program; import org.breedinginsight.model.User; @@ -96,7 +98,7 @@ public class GigwaGenotypeServiceImpl implements GenotypeService { private final ImportDAO importDAO; private final SampleSubmissionDAO sampleSubmissionDAO; private final BrAPISampleDAO sampleDAO; - + private final GenotypeImportDAO genotypeImportDAO; private final ImportMappingDAO importMappingDAO; private final SimpleStorageService storageService; @@ -122,6 +124,7 @@ public GigwaGenotypeServiceImpl(@Property(name = "gigwa.host") String gigwaHost, ImportDAO importDAO, SampleSubmissionDAO sampleSubmissionDAO, BrAPISampleDAO sampleDAO, + GenotypeImportDAO genotypeImportDAO, ImportMappingDAO importMappingDAO, @Named("genotype") SimpleStorageService storageService, S3Client s3Client, @@ -134,6 +137,7 @@ public GigwaGenotypeServiceImpl(@Property(name = "gigwa.host") String gigwaHost, this.username = username; this.password = password; this.referenceSource = referenceSource; + this.genotypeImportDAO = genotypeImportDAO; this.gson = new GsonBuilder().create(); this.programDAO = programDAO; this.userDAO = userDAO; @@ -266,6 +270,12 @@ public GermplasmGenotype retrieveGenotypeData(UUID programId, UUID germplasmId) } } + @Override + public List getGenotypeImports(UUID programId) { + + return genotypeImportDAO.getGenotypeImportsByProgramId(programId); + } + private boolean validateSamples(Program program, UUID submissionId, byte[] fileContents, ImportUpload upload) throws DoesNotExistException, ApiException { log.debug("Validating samples in submitted VCF file for submission: " + submissionId); @@ -335,6 +345,7 @@ private boolean validateSamples(Program program, UUID submissionId, byte[] fileC log.debug("VCF samples are valid!"); return true; } + private boolean validateVcfHeader(String[] headerParts) { if(headerParts.length < 8) { return false; @@ -420,6 +431,7 @@ private List fetchCallsets(BrAPIClient genoBrAPIClient, List { + + private final Map> fields; + + public GenotypeImportQueryMapper() { + fields = Map.ofEntries( + Map.entry("sampleSubmissionId", GenotypeImportDetails::getSampleSubmissionId), + Map.entry("projectNameForSampleSubmission", GenotypeImportDetails::getProjectNameForSampleSubmission), + Map.entry("sampleSubmissionCreatedBy", GenotypeImportDetails::getSampleSubmissionCreatedBy), + Map.entry("genotypingFileName", GenotypeImportDetails::getGenotypingFileName), + Map.entry("genotypingImportDate", GenotypeImportDetails::getGenotypingImportDate), + Map.entry("genotypingImportBy", GenotypeImportDetails::getGenotypingImportBy) + ); + } + + @Override + public boolean exists(String fieldName) { + return getFields().containsKey(fieldName); + } + + @Override + public Function getField(String fieldName) throws NullPointerException { + if (fields.containsKey(fieldName)) return fields.get(fieldName); + else throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql b/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql new file mode 100644 index 000000000..9d123fd90 --- /dev/null +++ b/src/main/resources/db/migration/V1.36.0__create_genotype_import_table.sql @@ -0,0 +1,55 @@ +/* + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +-- Join table linking sample submissions to genotype imports +create table genotype_import +( + like base_entity including defaults including constraints including indexes, + sample_submission_id uuid not null, + importer_import_id uuid not null, + like base_edit_track_entity including all +); + +alter table genotype_import + add constraint fk_gi_sample_submission + foreign key (sample_submission_id) + references sample_submission (id); + +alter table genotype_import + add constraint fk_gi_importer_import + foreign key (importer_import_id) + references importer_import (id); + +alter table genotype_import + add constraint fk_gi_created_by + foreign key (created_by) + references bi_user (id); + +alter table genotype_import + add constraint fk_gi_updated_by + foreign key (updated_by) + references bi_user (id); + +alter table genotype_import + add constraint uq_gi_importer_import_id + unique (importer_import_id); + +create index idx_gi_sample_submission_id + on genotype_import (sample_submission_id); + +create index idx_gi_importer_import_id + on genotype_import (importer_import_id); \ No newline at end of file diff --git a/src/test/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadControllerIntegrationTest.java b/src/test/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadControllerIntegrationTest.java index 7398b6fa7..a25118a57 100644 --- a/src/test/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadControllerIntegrationTest.java +++ b/src/test/java/org/breedinginsight/api/v1/controller/geno/GenotypeDataUploadControllerIntegrationTest.java @@ -17,6 +17,9 @@ package org.breedinginsight.api.v1.controller.geno; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import io.kowalski.fannypack.FannyPack; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; @@ -25,8 +28,8 @@ import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; import io.micronaut.http.client.multipart.MultipartBody; -import io.micronaut.http.netty.cookies.NettyCookie; import io.micronaut.http.multipart.CompletedFileUpload; +import io.micronaut.http.netty.cookies.NettyCookie; import io.micronaut.test.annotation.MockBean; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import org.breedinginsight.DatabaseTest; @@ -35,8 +38,12 @@ import org.breedinginsight.brapps.importer.model.response.ImportResponse; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.UserDAO; +import org.breedinginsight.model.GenotypeImportDetails; import org.breedinginsight.model.Program; +import org.breedinginsight.model.ProgramBrAPIEndpoints; import org.breedinginsight.model.User; +import org.breedinginsight.services.ProgramService; +import org.breedinginsight.services.exceptions.DoesNotExistException; import org.breedinginsight.services.geno.GenotypeService; import org.jooq.DSLContext; import org.junit.jupiter.api.BeforeAll; @@ -46,8 +53,15 @@ import javax.inject.Inject; import java.io.File; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; import java.util.UUID; +import static io.micronaut.http.HttpRequest.GET; import static io.micronaut.http.HttpRequest.POST; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -75,6 +89,9 @@ public class GenotypeDataUploadControllerIntegrationTest extends DatabaseTest { @Inject private UserDAO userDAO; + @Inject + private ProgramService programService; + private Program program; private User testUser; @@ -83,6 +100,11 @@ GenotypeService genotypeService() { return mock(GenotypeService.class); } + @MockBean(ProgramService.class) + ProgramService programService() { + return mock(ProgramService.class); + } + @BeforeAll void setup() { FannyPack fp = FannyPack.fill("src/test/resources/sql/ProgramSecuredAnnotationRuleIntegrationTest.sql"); @@ -95,6 +117,7 @@ void setup() { @BeforeEach void resetMocks() { reset(genotypeService); + reset(programService); } @Test @@ -105,11 +128,12 @@ void uploadDataUsesSubmissionScopedRouteAndServiceContract() throws Exception { ImportResponse importResponse = new ImportResponse(); importResponse.setImportId(importId); importResponse.setProgress(ImportProgress.builder() - .statuscode((short) HttpStatus.ACCEPTED.getCode()) - .build()); + .statuscode((short) HttpStatus.ACCEPTED.getCode()) + .build()); + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); doReturn(importResponse).when(genotypeService) - .submitGenotypeData(eq(testUser.getId()), eq(program.getId()), eq(submissionId), any(CompletedFileUpload.class)); + .submitGenotypeData(eq(testUser.getId()), eq(program.getId()), eq(submissionId), any(CompletedFileUpload.class)); HttpResponse response = client.exchange( POST(String.format("/programs/%s/submissions/%s/geno/import", program.getId(), submissionId), multipartBody()) @@ -137,9 +161,205 @@ void experimentScopedUploadRouteIsRemoved() { verifyNoInteractions(genotypeService); } + @Test + void getGenotypeImportsReturnsPagedAndSortedResponse() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("22222222-2222-2222-2222-222222222222")) + .projectNameForSampleSubmission("Newer Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("newer.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("Importer B") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(older, newer))) // mutable list required because ResponseUtils sorts in place + .when(genotypeService) + .getGenotypeImports(program.getId()); + + HttpResponse response = client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=1&sortField=genotypingImportDate&sortOrder=DESC", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(2, pagination.get("totalCount").getAsInt()); + assertEquals(1, pagination.get("pageSize").getAsInt()); + assertEquals(2, pagination.get("totalPages").getAsInt()); + assertEquals(1, pagination.get("currentPage").getAsInt()); + assertEquals(1, data.size()); + + JsonObject firstRow = data.get(0).getAsJsonObject(); + assertEquals("22222222-2222-2222-2222-222222222222", firstRow.get("sampleSubmissionId").getAsString()); + assertEquals("Newer Submission", firstRow.get("projectNameForSampleSubmission").getAsString()); + assertEquals("Test User", firstRow.get("sampleSubmissionCreatedBy").getAsString()); + assertEquals("newer.vcf", firstRow.get("genotypingFileName").getAsString()); + assertEquals("Importer B", firstRow.get("genotypingImportBy").getAsString()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verify(genotypeService).getGenotypeImports(program.getId()); + } + + @Test + void getGenotypeImportsReturnsNotFoundWhenProgramLookupFails() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.empty()).when(programService).getById(program.getId()); + + HttpClientResponseException exception = assertThrows(HttpClientResponseException.class, () -> client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=10", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst()); + + assertEquals(HttpStatus.NOT_FOUND, exception.getStatus()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verifyNoInteractions(genotypeService); + } + + @Test + void getGenotypeImportsRejectsInvalidSortField() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + + HttpClientResponseException exception = assertThrows(HttpClientResponseException.class, () -> client.exchange( + GET(String.format("/programs/%s/geno/imports?page=1&pageSize=10&sortField=badField&sortOrder=DESC", program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst()); + + assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); + + verify(programService).getBrapiEndpoints(program.getId()); + verifyNoInteractions(genotypeService); + } + + @Test + void getGenotypeImportsFiltersByProjectNameForSampleSubmission() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("22222222-2222-2222-2222-222222222222")) + .projectNameForSampleSubmission("Newer Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("newer.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("Importer B") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(older, newer))) //safe mutable list + .when(genotypeService) + .getGenotypeImports(program.getId()); + String param = URLEncoder.encode("Newer Submission", StandardCharsets.UTF_8); + HttpResponse response = client.exchange( + GET(String.format( + "/programs/%s/geno/imports?page=1&pageSize=10&projectNameForSampleSubmission=" + param, + program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(1, pagination.get("totalCount").getAsInt()); + assertEquals(10, pagination.get("pageSize").getAsInt()); + assertEquals(1, pagination.get("totalPages").getAsInt()); + assertEquals(1, pagination.get("currentPage").getAsInt()); + assertEquals(1, data.size()); + + JsonObject firstRow = data.get(0).getAsJsonObject(); + assertEquals("22222222-2222-2222-2222-222222222222", firstRow.get("sampleSubmissionId").getAsString()); + assertEquals("Newer Submission", firstRow.get("projectNameForSampleSubmission").getAsString()); + assertEquals("newer.vcf", firstRow.get("genotypingFileName").getAsString()); + + verify(programService).getBrapiEndpoints(program.getId()); + verify(programService).getById(program.getId()); + verify(genotypeService).getGenotypeImports(program.getId()); + } + + @Test + void getGenotypeImportsReturnsEmptyDataWhenFiltersDoNotMatch() throws DoesNotExistException { + doReturn(getBrAPIEndpoints()).when(programService).getBrapiEndpoints(program.getId()); + doReturn(Optional.of(program)).when(programService).getById(program.getId()); + + GenotypeImportDetails row = GenotypeImportDetails.builder() + .sampleSubmissionId(UUID.fromString("11111111-1111-1111-1111-111111111111")) + .projectNameForSampleSubmission("Older Submission") + .sampleSubmissionCreatedBy("Test User") + .genotypingFileName("older.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("Importer A") + .build(); + + doReturn(new ArrayList<>(Arrays.asList(row))) //safe mutable list + .when(genotypeService) + .getGenotypeImports(program.getId()); + + HttpResponse response = client.exchange( + GET(String.format( + "/programs/%s/geno/imports?page=1&pageSize=10&projectNameForSampleSubmission=DoesNotMatch", + program.getId())) + .cookie(new NettyCookie("phylo-token", "test-registered-user")), + String.class + ).blockingFirst(); + + assertEquals(HttpStatus.OK, response.getStatus()); + + JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject(); + JsonObject pagination = body.getAsJsonObject("metadata").getAsJsonObject("pagination"); + JsonArray data = body.getAsJsonObject("result").getAsJsonArray("data"); + + assertEquals(0, pagination.get("totalCount").getAsInt()); + assertEquals(0, data.size()); + + verify(programService, times(1)).getBrapiEndpoints(program.getId()); + verify(programService, times(1)).getById(program.getId()); + verify(genotypeService, times(1)).getGenotypeImports(program.getId()); + } + private MultipartBody multipartBody() { return MultipartBody.builder() - .addPart("file", new File("src/test/resources/files/geno/sample.vcf")) - .build(); + .addPart("file", new File("src/test/resources/files/geno/sample.vcf")) + .build(); + } + + private ProgramBrAPIEndpoints getBrAPIEndpoints() { + return ProgramBrAPIEndpoints.builder() + .coreUrl(Optional.of("http://localhost:8081/")) + .phenoUrl(Optional.of("http://localhost:8081/")) + .genoUrl(Optional.of("http://localhost:8081/")) + .build(); } } diff --git a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java index c05223578..7acf51a28 100644 --- a/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java +++ b/src/test/java/org/breedinginsight/services/geno/impl/GigwaGenotypeServiceImplIntegrationTest.java @@ -5,11 +5,7 @@ import com.agorapulse.micronaut.amazon.awssdk.s3.SimpleStorageServiceConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import io.micronaut.context.ApplicationContext; -import io.micronaut.context.annotation.Context; -import io.micronaut.context.annotation.Factory; -import io.micronaut.context.annotation.Property; -import io.micronaut.context.annotation.Replaces; -import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.*; import io.micronaut.context.event.BeanCreatedEventListener; import io.micronaut.http.HttpStatus; import io.micronaut.http.MediaType; @@ -40,25 +36,22 @@ import org.brapi.v2.model.germ.BrAPIGermplasm; import org.breedinginsight.DatabaseTest; import org.breedinginsight.brapi.v2.dao.BrAPIGermplasmDAO; +import org.breedinginsight.brapi.v2.dao.impl.ImportMappingDAOImpl; import org.breedinginsight.brapps.importer.daos.BrAPISampleDAO; import org.breedinginsight.brapps.importer.daos.ImportDAO; import org.breedinginsight.brapps.importer.daos.ImportMappingDAO; -import org.breedinginsight.brapi.v2.dao.impl.ImportMappingDAOImpl; import org.breedinginsight.brapps.importer.model.ImportProgress; import org.breedinginsight.brapps.importer.model.ImportUpload; import org.breedinginsight.brapps.importer.model.mapping.ImportMapping; import org.breedinginsight.brapps.importer.model.response.ImportResponse; import org.breedinginsight.dao.db.tables.pojos.ImporterImportEntity; +import org.breedinginsight.daos.GenotypeImportDAO; import org.breedinginsight.daos.ProgramDAO; import org.breedinginsight.daos.SampleSubmissionDAO; import org.breedinginsight.daos.UserDAO; import org.breedinginsight.daos.impl.ProgramDAOImpl; import org.breedinginsight.daos.impl.UserDAOImpl; -import org.breedinginsight.model.BrAPIConstants; -import org.breedinginsight.model.GermplasmGenotype; -import org.breedinginsight.model.Program; -import org.breedinginsight.model.SampleSubmission; -import org.breedinginsight.model.User; +import org.breedinginsight.model.*; import org.breedinginsight.services.brapi.BrAPIClientProvider; import org.breedinginsight.services.brapi.BrAPIEndpointProvider; import org.breedinginsight.services.brapi.BrAPIProvider; @@ -133,6 +126,9 @@ public class GigwaGenotypeServiceImplIntegrationTest extends DatabaseTest { @Inject private BrAPIGermplasmDAO germplasmDAO; + @Inject + private GenotypeImportDAO genotypeImportDAO; + @Inject private ObjectMapper objectMapper; @@ -226,6 +222,17 @@ BrAPIGermplasmDAO germplasmDAO() { } } + @Factory + @Requires(property = "micronaut.test.active.spec", value = "org.breedinginsight.services.geno.impl.GigwaGenotypeServiceImplIntegrationTest") + static class GenotypeImportDaoTestFactory { + + @Singleton + @Replaces(GenotypeImportDAO.class) + GenotypeImportDAO genotypeImportDAO() { + return mock(GenotypeImportDAO.class); + } + } + private GenericContainer gigwa; private GenericContainer mongo; @@ -258,12 +265,12 @@ public GigwaGenotypeServiceImplIntegrationTest() { .withEnv("GIGWA.serversAllowedToImport", gigwaAllowedServer) .waitingFor( Wait.forHttp("/gigwa") - .forStatusCode(200) - .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); + .forStatusCode(200) + .withStartupTimeout(Duration.of(2, ChronoUnit.MINUTES))); gigwa.start(); localStackContainer = new LocalStackContainer(DockerImageName.parse("localstack/localstack") - .withTag("3.0.2")) + .withTag("3.0.2")) .withServices(LocalStackContainer.Service.S3) .withNetwork(super.getNetwork()) .withNetworkAliases("localstack") @@ -276,7 +283,7 @@ public GigwaGenotypeServiceImplIntegrationTest() { public Map getProperties() { Map properties = super.getProperties(); - properties.put("gigwa.host", "http://"+gigwa.getContainerIpAddress()+":"+gigwa.getMappedPort(8080)+"/"); + properties.put("gigwa.host", "http://" + gigwa.getContainerIpAddress() + ":" + gigwa.getMappedPort(8080) + "/"); properties.put("gigwa.username", "gigwadmin"); properties.put("gigwa.password", "nimda"); @@ -304,7 +311,12 @@ public void setup() throws IllegalAccessException, NoSuchFieldException { storageService = applicationContext.getBean(SimpleStorageService.class, Qualifiers.byName("genotype")); storageService.createBucket(); - } + } + + @BeforeEach + public void resetGenotypeImportDaoMock() { + reset(genotypeImportDAO); + } @AfterAll public void teardown() { @@ -322,36 +334,37 @@ public void testUpload() throws ApiException, AuthorizationException { assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> uploadGenoData(programId, programKey, submissionId, importId), "Upload did not complete within the time period"); assertTrue(storageService.exists(storageService.getDefaultBucketName(), programId + "/" + submissionId + "/" + importId + ".vcf"), "File was not uploaded to s3"); + verify(genotypeImportDAO).createGenotypeImportLink(eq(submissionId), eq(importId), any(UUID.class)); BrAPIClient brAPIClient = new BrAPIClient(gigwaHost + "gigwa/rest/brapi/v2"); Authentication authorizationToken = brAPIClient.getAuthentication("AuthorizationToken"); - if(authorizationToken instanceof OAuth) { - ((OAuth)authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); + if (authorizationToken instanceof OAuth) { + ((OAuth) authorizationToken).setAccessToken(gigwaGenoStorageService.getAuthToken()); } ProgramsApi programsApi = new ProgramsApi(brAPIClient); try { ApiResponse brAPIProgramListResponseApiResponse = programsApi.programsGet(ProgramQueryParams.builder() - .programDbId(programKey) - .build()); + .programDbId(programKey) + .build()); assertEquals(1, - brAPIProgramListResponseApiResponse.getBody() - .getResult() - .getData() - .size()); + brAPIProgramListResponseApiResponse.getBody() + .getResult() + .getData() + .size()); StudiesApi studiesApi = new StudiesApi(brAPIClient); ApiResponse brAPIStudyListResponseApiResponse = studiesApi.studiesGet(StudyQueryParams.builder() - .build()); + .build()); assertEquals(1, - brAPIStudyListResponseApiResponse.getBody() - .getResult() - .getData() - .stream() - .filter(brAPIStudy -> brAPIStudy.getStudyName() - .equals(submissionId.toString())) - .count()); + brAPIStudyListResponseApiResponse.getBody() + .getResult() + .getData() + .stream() + .filter(brAPIStudy -> brAPIStudy.getStudyName() + .equals(submissionId.toString())) + .count()); } catch (ApiException e) { System.err.println(e.getMessage()); System.err.println(e.getResponseBody()); @@ -373,13 +386,13 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep SamplesApi mockSamplesApi = spy(new SamplesApi()); BrAPISample sample = new BrAPISample().sampleName(sampleName) - .germplasmDbId(programKey + "§" + sampleName); + .germplasmDbId(programKey + "§" + sampleName); doReturn(List.of(sample)).when(sampleDAO) - .readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); + .readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); doReturn(new ApiResponse<>(200, - new HashMap<>(), - Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), - Optional.empty()))) + new HashMap<>(), + Pair.of(Optional.of(new BrAPISampleListResponse().result(new BrAPISampleListResponseResult().data(List.of(sample)))), + Optional.empty()))) .when(mockSamplesApi).searchSamplesPost(any(BrAPISampleSearchRequest.class)); doReturn(mockSamplesApi).when(brAPIEndpointProvider).get(any(BrAPIClient.class), eq(SamplesApi.class)); @@ -395,8 +408,8 @@ public void testFetchGermplasmGenotype() throws AuthorizationException, ApiExcep verify(sampleDAO).readSamplesByGermplasmIds(any(Program.class), eq(List.of(germplasm.getGermplasmDbId()))); verify(brAPIEndpointProvider, never()).get(any(BrAPIClient.class), eq(ObservationUnitsApi.class)); verify(mockSamplesApi).searchSamplesPost(argThat(searchRequest -> searchRequest.getGermplasmDbIds() != null && - searchRequest.getGermplasmDbIds().contains(programKey + "§" + sample.getSampleName()) && - (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); + searchRequest.getGermplasmDbIds().contains(programKey + "§" + sample.getSampleName()) && + (searchRequest.getObservationUnitDbIds() == null || searchRequest.getObservationUnitDbIds().isEmpty()))); assertNotNull(germplasmGenotype); assertFalse(germplasmGenotype.getCalls().isEmpty()); assertFalse(germplasmGenotype.getCallSets().isEmpty()); @@ -409,31 +422,80 @@ public void testSubmitValidFile() throws IOException, ApiException { String programKey = "TESTSUBMITVALID"; UUID submissionId = UUID.randomUUID(); - Scanner sc = new Scanner(new FileInputStream("src/test/resources/files/geno/sample.vcf"), "UTF-8"); - String[] headerParts = null; - boolean foundHeader = false; - while (sc.hasNextLine() && !foundHeader) { - String line = sc.nextLine(); - if(line.startsWith("#CHROM")) { - foundHeader = true; - headerParts = line.split("\t"); - } - } - assertTrue(foundHeader, "Could not find sample.vcf header file"); - - List samples = new ArrayList<>(); - for(int i = 9; i < headerParts.length; i++) { - samples.add(new BrAPISample().sampleName(headerParts[i])); - } + List samples = buildSamplesFromValidVcf(); setupMocksForSubmitGenoData(programId, submissionId, samples); AtomicReference importResponse = new AtomicReference<>(); - assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> importResponse.set(submitGenoData(programId, programKey, submissionId, "sample.vcf")), "Upload did not complete within the time period"); + assertTimeout(Duration.of(2, ChronoUnit.MINUTES), () -> + importResponse.set(submitGenoData(programId, programKey, submissionId, "sample.vcf")), + "Upload did not complete within the time period"); ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.ACCEPTED.getCode(), response.getProgress().getStatuscode(), "Error importing geno file: " + response.getProgress().getMessage()); + assertEquals((short) HttpStatus.ACCEPTED.getCode(), + response.getProgress().getStatuscode(), + "Error importing geno file: " + response.getProgress().getMessage()); + } + + @Test + public void testGetGenotypeImportsReturnsRowsIncludingSampleSubmissionId() { + UUID programId = UUID.randomUUID(); + UUID olderSubmissionId = UUID.randomUUID(); + UUID newerSubmissionId = UUID.randomUUID(); + + GenotypeImportDetails older = GenotypeImportDetails.builder() + .sampleSubmissionId(olderSubmissionId) + .projectNameForSampleSubmission("Submission " + olderSubmissionId) + .sampleSubmissionCreatedBy("system") + .genotypingFileName("sample.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-01T10:00:00Z")) + .genotypingImportBy("system") + .build(); + + GenotypeImportDetails newer = GenotypeImportDetails.builder() + .sampleSubmissionId(newerSubmissionId) + .projectNameForSampleSubmission("Submission " + newerSubmissionId) + .sampleSubmissionCreatedBy("system") + .genotypingFileName("sample.vcf") + .genotypingImportDate(OffsetDateTime.parse("2026-06-02T10:00:00Z")) + .genotypingImportBy("system") + .build(); + + doReturn(List.of(newer, older)).when(genotypeImportDAO).getGenotypeImportsByProgramId(programId); + + List rows = gigwaGenoStorageService.getGenotypeImports(programId); + + assertNotNull(rows); + assertEquals(2, rows.size()); + + assertEquals(newerSubmissionId, rows.get(0).getSampleSubmissionId()); + assertEquals("Submission " + newerSubmissionId, rows.get(0).getProjectNameForSampleSubmission()); + assertEquals("sample.vcf", rows.get(0).getGenotypingFileName()); + assertNotNull(rows.get(0).getGenotypingImportDate()); + assertEquals("system", rows.get(0).getSampleSubmissionCreatedBy()); + assertEquals("system", rows.get(0).getGenotypingImportBy()); + + assertEquals(olderSubmissionId, rows.get(1).getSampleSubmissionId()); + assertEquals("Submission " + olderSubmissionId, rows.get(1).getProjectNameForSampleSubmission()); + assertEquals("sample.vcf", rows.get(1).getGenotypingFileName()); + assertNotNull(rows.get(1).getGenotypingImportDate()); + assertEquals("system", rows.get(1).getSampleSubmissionCreatedBy()); + assertEquals("system", rows.get(1).getGenotypingImportBy()); + + verify(genotypeImportDAO).getGenotypeImportsByProgramId(programId); + } + + @Test + public void testGetGenotypeImportsReturnsEmptyListWhenNoImportsExist() { + UUID programId = UUID.randomUUID(); + doReturn(Collections.emptyList()).when(genotypeImportDAO).getGenotypeImportsByProgramId(programId); + + List rows = gigwaGenoStorageService.getGenotypeImports(programId); + + assertNotNull(rows); + assertTrue(rows.isEmpty()); + verify(genotypeImportDAO).getGenotypeImportsByProgramId(programId); } @Test @@ -448,7 +510,7 @@ public void testSubmitInvalidHeader() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("Header row is not valid VCF format", response.getProgress().getMessage()); } @@ -466,7 +528,7 @@ public void testSubmitMissingSubmissionSamples() throws ApiException { ImportResponse response = importResponse.get(); assertNotNull(response); assertNotNull(response.getProgress()); - assertEquals((short)HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); + assertEquals((short) HttpStatus.BAD_REQUEST.getCode(), response.getProgress().getStatuscode()); assertEquals("There are samples that are not linked to the selected submission", response.getProgress().getMessage()); } @@ -476,96 +538,126 @@ private void setupMocksForSubmitGenoData(UUID programId, UUID submissionId, List submission.setProgramId(programId); doReturn(List.of(submission)).when(sampleSubmissionDAO) - .getBySubmissionId(any(Program.class), eq(submissionId)); + .getBySubmissionId(any(Program.class), eq(submissionId)); doReturn(samples).when(sampleDAO) - .readSamplesBySubmissionIds(any(Program.class), eq(List.of(submissionId.toString()))); + .readSamplesBySubmissionIds(any(Program.class), eq(List.of(submissionId.toString()))); } private void uploadGenoData(UUID programId, String programKey, UUID submissionId, UUID importId) throws AuthorizationException, MimeTypeException, IOException, ApiException { Program program = Program.builder() - .id(programId) - .key(programKey) - .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) - .build(); + .id(programId) + .key(programKey) + .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) + .build(); doReturn(List.of(program)).when(programDAO) - .get(any(UUID.class)); + .get(any(UUID.class)); User user = User.builder() - .id(UUID.randomUUID()) - .build(); + .id(UUID.randomUUID()) + .build(); doReturn(Optional.of(user)).when(userDAO) - .getUser(any(UUID.class)); + .getUser(any(UUID.class)); ImportMapping mapping = ImportMapping.builder() - .build(); + .build(); doReturn(List.of(mapping)).when(importMappingDAO) - .getSystemMappingByName(any(String.class)); + .getSystemMappingByName(any(String.class)); doAnswer(invocation -> { var importEntity = invocation.getArgument(0, ImporterImportEntity.class); importEntity.setId(importId); return importEntity; }).when(importDAO) - .insert(any(ImporterImportEntity.class)); + .insert(any(ImporterImportEntity.class)); ImportProgress progress = ImportProgress.builder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedAt(OffsetDateTime.now()) - .updatedBy(user.getId()) - .statuscode((short) HttpStatus.ACCEPTED.getCode()) - .message("Uploading file") - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedAt(OffsetDateTime.now()) + .updatedBy(user.getId()) + .statuscode((short) HttpStatus.ACCEPTED.getCode()) + .message("Uploading file") + .build(); ImportUpload importUpload = ImportUpload.uploadBuilder() - .createdBy(user.getId()) - .createdAt(OffsetDateTime.now()) - .updatedBy(user.getId()) - .updatedAt(OffsetDateTime.now()) - .programId(program.getId()) - .importerProgressId(progress.getId()) - .importerMappingId(mapping.getId()) - .id(importId) - .build(); + .createdBy(user.getId()) + .createdAt(OffsetDateTime.now()) + .updatedBy(user.getId()) + .updatedAt(OffsetDateTime.now()) + .programId(program.getId()) + .importerProgressId(progress.getId()) + .importerMappingId(mapping.getId()) + .id(importId) + .build(); System.out.println("====================== program ID: " + program.getId() + " ==============="); System.out.println("=================== submission ID: " + submissionId + " ==============="); gigwaGenoStorageService.processSubmission(gigwaGenoStorageService.getAuthToken(), program, submissionId, new TestFileUpload("src/test/resources/files/geno/sample.vcf", MediaType.of("application/vcard")).getBytes(), "sample.vcf", importUpload, progress); } - private ImportResponse submitGenoData(UUID programId, String programKey, UUID submissionId, String file) throws AuthorizationException, IOException, ApiException, DoesNotExistException { + private ImportResponse submitGenoData(UUID programId, String programKey, UUID submissionId, String file) + throws AuthorizationException, IOException, ApiException, DoesNotExistException { Program program = Program.builder() - .id(programId) - .key(programKey) - .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) - .build(); - doReturn(List.of(program)).when(programDAO) - .get(any(UUID.class)); + .id(programId) + .key(programKey) + .brapiUrl(BrAPIConstants.SYSTEM_DEFAULT.getValue()) + .build(); + doReturn(List.of(program)).when(programDAO).get(any(UUID.class)); User user = User.builder() - .id(UUID.randomUUID()) - .build(); - doReturn(Optional.of(user)).when(userDAO) - .getUser(any(UUID.class)); + .id(UUID.randomUUID()) + .build(); + doReturn(Optional.of(user)).when(userDAO).getUser(any(UUID.class)); ImportMapping mapping = ImportMapping.builder() - .build(); - doReturn(List.of(mapping)).when(importMappingDAO) - .getSystemMappingByName(any(String.class)); + .id(UUID.randomUUID()) + .build(); + doReturn(List.of(mapping)).when(importMappingDAO).getSystemMappingByName(any(String.class)); + + doAnswer(invocation -> { + ImportProgress progress = invocation.getArgument(0, ImportProgress.class); + progress.setId(UUID.randomUUID()); + return null; + }).when(importDAO).createProgress(any(ImportProgress.class)); UUID importId = UUID.randomUUID(); doAnswer(invocation -> { - var importEntity = invocation.getArgument(0, ImporterImportEntity.class); + ImporterImportEntity importEntity = invocation.getArgument(0, ImporterImportEntity.class); importEntity.setId(importId); - return importEntity; - }).when(importDAO) - .insert(any(ImporterImportEntity.class)); + return null; + }).when(importDAO).insert(any(ImporterImportEntity.class)); doReturn(new BrAPIClient("", 300000)).when(programDAO).getCoreClient(any(UUID.class)); doReturn(new BrAPIClient("", 300000)).when(programDAO).getPhenoClient(any(UUID.class)); - System.out.println("====================== program ID: " + program.getId() + " ==============="); - System.out.println("=================== submission ID: " + submissionId + " ==============="); - return gigwaGenoStorageService.submitGenotypeData(user.getId(), programId, submissionId, new TestFileUpload("src/test/resources/files/geno/"+file, MediaType.of("application/vcard"))); + return gigwaGenoStorageService.submitGenotypeData( + user.getId(), + programId, + submissionId, + new TestFileUpload("src/test/resources/files/geno/" + file, MediaType.of("application/vcard")) + ); + } + + private List buildSamplesFromValidVcf() throws IOException { + try (Scanner sc = new Scanner(new FileInputStream("src/test/resources/files/geno/sample.vcf"), "UTF-8")) { + String[] headerParts = null; + boolean foundHeader = false; + while (sc.hasNextLine() && !foundHeader) { + String line = sc.nextLine(); + if (line.startsWith("#CHROM")) { + foundHeader = true; + headerParts = line.split("\t"); + } + } + + assertTrue(foundHeader, "Could not find sample.vcf header file"); + + List samples = new ArrayList<>(); + for (int i = 9; i < headerParts.length; i++) { + samples.add(new BrAPISample().sampleName(headerParts[i])); + } + + return samples; + } } private class TestFileUpload implements CompletedFileUpload {