From 2f8ccf33d108537ea7cfe398085a25a7bc926406 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Fri, 21 Nov 2025 12:29:05 +0100 Subject: feat: add alternative endpoints for request (#196) This allows for requests to (with optional path-prefix "/api"): * POST /{usecase} * POST /{usecase}/etl/patient-record => as DNPM:DIP * DELETE /{usecase}/{ID} * DELETE /{usecase}/etl/patient-record/{ID} * DELETE /{usecase}/etl/patient/{ID} => as DNPM:DIP Where {usecase} is one of: * mtbfile * mtb => as DNPM:DIP--- .../processor/input/MtbFileRestControllerTest.kt | 165 +++++++++++++--- .../processor/config/AppSecurityConfiguration.kt | 4 + .../etl/processor/input/MtbFileRestController.kt | 12 +- .../dev/dnpm/etl/processor/pseudonym/extensions.kt | 216 ++++++++++++--------- .../processor/input/MtbFileRestControllerTest.kt | 69 +++++++ .../dnpm/etl/processor/pseudonym/ExtensionsTest.kt | 68 +++---- 6 files changed, 380 insertions(+), 154 deletions(-) (limited to 'src') diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt index e966898..35551a9 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt @@ -33,8 +33,9 @@ import java.time.Instant import java.util.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.* import org.springframework.beans.factory.annotation.Autowired @@ -92,10 +93,23 @@ class MtbFileRestControllerTest { .check(any()) } - @Test - fun testShouldGrantPermissionToSendMtbFile() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldGrantPermissionToSendMtbFile(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(user("onkostarserver").roles("MTBFILE")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) @@ -105,10 +119,23 @@ class MtbFileRestControllerTest { verify(requestProcessor, times(1)).processMtbFile(any()) } - @Test - fun testShouldGrantPermissionToSendMtbFileToAdminUser() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldGrantPermissionToSendMtbFileToAdminUser(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(user("onkostarserver").roles("ADMIN")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) @@ -118,10 +145,23 @@ class MtbFileRestControllerTest { verify(requestProcessor, times(1)).processMtbFile(any()) } - @Test - fun testShouldDenyPermissionToSendMtbFile() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldDenyPermissionToSendMtbFile(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(anonymous()) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) @@ -131,10 +171,23 @@ class MtbFileRestControllerTest { verify(requestProcessor, never()).processMtbFile(any()) } - @Test - fun testShouldDenyPermissionToSendMtbFileForUser() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldDenyPermissionToSendMtbFileForUser(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(user("fakeuser").roles("USER")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) @@ -144,21 +197,53 @@ class MtbFileRestControllerTest { verify(requestProcessor, never()).processMtbFile(any()) } - @Test - fun testShouldGrantPermissionToDeletePatientData() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile/TEST_12345678", + "/mtbfile/etl/patient-record/TEST_12345678", + "/mtbfile/etl/patient/TEST_12345678", + "/mtb/TEST_12345678", + "/mtb/etl/patient-record/TEST_12345678", + "/mtb/etl/patient/TEST_12345678", + "/api/mtbfile/TEST_12345678", + "/api/mtbfile/etl/patient-record/TEST_12345678", + "/api/mtbfile/etl/patient/TEST_12345678", + "/api/mtb/TEST_12345678", + "/api/mtb/etl/patient-record/TEST_12345678", + "/api/mtb/etl/patient/TEST_12345678", + ] + ) + fun testShouldGrantPermissionToDeletePatientData(url: String) { mockMvc - .delete("/mtbfile/12345678") { with(user("onkostarserver").roles("MTBFILE")) } + .delete(url) { with(user("onkostarserver").roles("MTBFILE")) } .andExpect { status { isAccepted() } } verify(requestProcessor, times(1)) .processDeletion(anyValueClass(), eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)) } - @Test - fun testShouldDenyPermissionToDeletePatientData() { - mockMvc - .delete("/mtbfile/12345678") { with(anonymous()) } - .andExpect { status { isUnauthorized() } } + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile/TEST_12345678", + "/mtbfile/etl/patient-record/TEST_12345678", + "/mtbfile/etl/patient/TEST_12345678", + "/mtb/TEST_12345678", + "/mtb/etl/patient-record/TEST_12345678", + "/mtb/etl/patient/TEST_12345678", + "/api/mtbfile/TEST_12345678", + "/api/mtbfile/etl/patient-record/TEST_12345678", + "/api/mtbfile/etl/patient/TEST_12345678", + "/api/mtb/TEST_12345678", + "/api/mtb/etl/patient-record/TEST_12345678", + "/api/mtb/etl/patient/TEST_12345678", + ] + ) + fun testShouldDenyPermissionToDeletePatientData(url: String) { + mockMvc.delete(url) { with(anonymous()) }.andExpect { status { isUnauthorized() } } verify(requestProcessor, never()).processDeletion(anyValueClass(), any()) } @@ -176,10 +261,23 @@ class MtbFileRestControllerTest { ] ) inner class WithOidcEnabled { - @Test - fun testShouldGrantPermissionToSendMtbFileToAdminUser() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldGrantPermissionToSendMtbFileToAdminUser(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(user("onkostarserver").roles("ADMIN")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) @@ -189,10 +287,23 @@ class MtbFileRestControllerTest { verify(requestProcessor, times(1)).processMtbFile(any()) } - @Test - fun testShouldGrantPermissionToSendMtbFileToUser() { + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun testShouldGrantPermissionToSendMtbFileToUser(url: String) { mockMvc - .post("/mtbfile") { + .post(url) { with(user("onkostarserver").roles("USER")) contentType = MediaType.APPLICATION_JSON content = ObjectMapper().writeValueAsString(mtbFile) diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt index d44303b..6b7cab8 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt @@ -89,6 +89,8 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon http { authorizeHttpRequests { authorize("/configs/**", hasRole("ADMIN")) + authorize("/api/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) + authorize("/api/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/report/**", hasAnyRole("ADMIN", "USER")) @@ -154,6 +156,8 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon http { authorizeHttpRequests { authorize("/configs/**", hasRole("ADMIN")) + authorize("/api/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN")) + authorize("/api/mtb/**", hasAnyRole("MTBFILE", "ADMIN")) authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN")) authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN")) authorize("/report/**", hasRole("ADMIN")) diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt index 5a43242..071e3cd 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt @@ -31,7 +31,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @RestController -@RequestMapping(path = ["mtbfile", "mtb"]) +@RequestMapping(path = ["mtbfile", "mtb", "api/mtbfile", "api/mtb"]) class MtbFileRestController( private val requestProcessor: RequestProcessor, private val consentEvaluator: ConsentEvaluator, @@ -44,8 +44,12 @@ class MtbFileRestController( } @PostMapping( + path = ["", "etl/patient-record"], consumes = - [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] + [ + MediaType.APPLICATION_JSON_VALUE, + CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE, + ], ) fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity { val consentEvaluation = consentEvaluator.check(mtbFile) @@ -60,7 +64,9 @@ class MtbFileRestController( return ResponseEntity.accepted().build() } - @DeleteMapping(path = ["{patientId}"]) + @DeleteMapping( + path = ["{patientId}", "etl/patient-record/{patientId}", "etl/patient/{patientId}"] + ) fun deleteData(@PathVariable patientId: String): ResponseEntity { logger.debug("Accepted patient ID to process deletion") requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE) diff --git a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt index 387119f..23032e8 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/pseudonym/extensions.kt @@ -37,13 +37,21 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { this.episodesOfCare?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.carePlans?.filterNotNull()?.forEach { carePlan -> - carePlan.patient.id = patientPseudonym - carePlan.rebiopsyRequests?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - carePlan.histologyReevaluationRequests?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - carePlan.medicationRecommendations?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - carePlan.studyEnrollmentRecommendations?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - carePlan.procedureRecommendations?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - carePlan.geneticCounselingRecommendation?.patient?.id = patientPseudonym + carePlan.patient.id = patientPseudonym + carePlan.rebiopsyRequests?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + carePlan.histologyReevaluationRequests?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + carePlan.medicationRecommendations?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + carePlan.studyEnrollmentRecommendations?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + carePlan.procedureRecommendations?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + carePlan.geneticCounselingRecommendation?.patient?.id = patientPseudonym } this.diagnoses?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.guidelineTherapies?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } @@ -58,26 +66,34 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { it.results.tumorCellContent?.patient?.id = patientPseudonym } this.ngsReports?.filterNotNull()?.forEach { ngsReport -> - ngsReport.patient?.id = patientPseudonym - ngsReport.results?.simpleVariants?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - ngsReport.results?.copyNumberVariants?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - ngsReport.results?.dnaFusions?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - ngsReport.results?.rnaFusions?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - ngsReport.results?.tumorCellContent?.patient?.id = patientPseudonym - ngsReport.results?.brcaness?.patient?.id = patientPseudonym - ngsReport.results?.tmb?.patient?.id = patientPseudonym - ngsReport.results?.hrdScore?.patient?.id = patientPseudonym + ngsReport.patient?.id = patientPseudonym + ngsReport.results?.simpleVariants?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + ngsReport.results?.copyNumberVariants?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } + ngsReport.results?.dnaFusions?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + ngsReport.results?.rnaFusions?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + ngsReport.results?.tumorCellContent?.patient?.id = patientPseudonym + ngsReport.results?.brcaness?.patient?.id = patientPseudonym + ngsReport.results?.tmb?.patient?.id = patientPseudonym + ngsReport.results?.hrdScore?.patient?.id = patientPseudonym } this.ihcReports?.filterNotNull()?.forEach { ihcReports -> - ihcReports.patient?.id = patientPseudonym - ihcReports.results?.msiMmr?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - ihcReports.results?.proteinExpression?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + ihcReports.patient?.id = patientPseudonym + ihcReports.results?.msiMmr?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + ihcReports.results?.proteinExpression?.filterNotNull()?.forEach { + it.patient?.id = patientPseudonym + } } this.responses?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.specimens?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.priorDiagnosticReports?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.performanceStatus?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } - this.systemicTherapies?.filterNotNull()?.forEach { systemicTherapy -> systemicTherapy.history?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } } + this.systemicTherapies?.filterNotNull()?.forEach { systemicTherapy -> + systemicTherapy.history?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } + } this.followUps?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } this.msiFindings?.filterNotNull()?.forEach { it.patient?.id = patientPseudonym } @@ -86,8 +102,8 @@ infix fun Mtb.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { if (researchConsent.contains("patient")) { // here we expect only a patient reference any other data like display // need to be removed, since may contain unsecure data - researchConsent.remove("patient") - researchConsent["patient"] = mapOf("reference" to "Patient/$patientPseudonym") + researchConsent.remove("patient") + researchConsent["patient"] = mapOf("reference" to "Patient/$patientPseudonym") } } } @@ -108,8 +124,8 @@ infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) { } this.episodesOfCare?.filterNotNull()?.forEach { episodeOfCare -> - episodeOfCare.apply { id = id?.let(::anonymize) } - episodeOfCare.diagnoses?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } + episodeOfCare.apply { id = id?.let(::anonymize) } + episodeOfCare.diagnoses?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } } this.carePlans?.onEach { carePlan -> @@ -118,127 +134,147 @@ infix fun Mtb.anonymizeContentWith(pseudonymizeService: PseudonymizeService) { this.geneticCounselingRecommendation?.apply { this.id = this.id?.let(::anonymize) } this.rebiopsyRequests?.filterNotNull()?.forEach { rebiopsyRequest -> - rebiopsyRequest.id = rebiopsyRequest.id?.let(::anonymize) - rebiopsyRequest.tumorEntity?.id = rebiopsyRequest.tumorEntity?.id?.let(::anonymize) + rebiopsyRequest.id = rebiopsyRequest.id?.let(::anonymize) + rebiopsyRequest.tumorEntity?.id = rebiopsyRequest.tumorEntity?.id?.let(::anonymize) } this.histologyReevaluationRequests?.filterNotNull()?.forEach { histologyReevaluationRequest -> - histologyReevaluationRequest.id = histologyReevaluationRequest.id?.let(::anonymize) - histologyReevaluationRequest.specimen?.id = histologyReevaluationRequest.specimen?.id?.let(::anonymize) + histologyReevaluationRequest.id = histologyReevaluationRequest.id?.let(::anonymize) + histologyReevaluationRequest.specimen?.id = + histologyReevaluationRequest.specimen?.id?.let(::anonymize) } this.medicationRecommendations?.filterNotNull()?.forEach { medicationRecommendations -> - medicationRecommendations.id = medicationRecommendations.id?.let(::anonymize) - medicationRecommendations.supportingVariants?.filterNotNull()?.forEach { it.variant?.id = it.variant?.id?.let(::anonymize) } - medicationRecommendations.reason?.id = medicationRecommendations.reason?.id?.let(::anonymize) + medicationRecommendations.id = medicationRecommendations.id?.let(::anonymize) + medicationRecommendations.supportingVariants?.filterNotNull()?.forEach { + it.variant?.id = it.variant?.id?.let(::anonymize) + } + medicationRecommendations.reason?.id = + medicationRecommendations.reason?.id?.let(::anonymize) } this.reason?.id = this.reason?.id?.let(::anonymize) - this.studyEnrollmentRecommendations?.filterNotNull()?.forEach { studyEnrollmentRecommendation -> - studyEnrollmentRecommendation.reason?.id = studyEnrollmentRecommendation.reason?.id?.let(::anonymize) + this.studyEnrollmentRecommendations?.filterNotNull()?.forEach { studyEnrollmentRecommendation + -> + studyEnrollmentRecommendation.reason?.id = + studyEnrollmentRecommendation.reason?.id?.let(::anonymize) } this.procedureRecommendations?.filterNotNull()?.forEach { procedureRecommendation -> - procedureRecommendation.id = procedureRecommendation.id?.let(::anonymize) - procedureRecommendation.supportingVariants?.filterNotNull()?.forEach { it.variant?.id = it.variant?.id?.let(::anonymize) } - procedureRecommendation.reason?.id = procedureRecommendation.reason?.id?.let(::anonymize) + procedureRecommendation.id = procedureRecommendation.id?.let(::anonymize) + procedureRecommendation.supportingVariants?.filterNotNull()?.forEach { + it.variant?.id = it.variant?.id?.let(::anonymize) + } + procedureRecommendation.reason?.id = procedureRecommendation.reason?.id?.let(::anonymize) } - this.studyEnrollmentRecommendations?.filterNotNull()?.forEach { studyEnrollmentRecommendation -> - studyEnrollmentRecommendation.id = studyEnrollmentRecommendation.id?.let(::anonymize) - studyEnrollmentRecommendation.supportingVariants.forEach { it.variant?.id = it?.variant?.id?.let(::anonymize) } + this.studyEnrollmentRecommendations?.filterNotNull()?.forEach { studyEnrollmentRecommendation + -> + studyEnrollmentRecommendation.id = studyEnrollmentRecommendation.id?.let(::anonymize) + studyEnrollmentRecommendation.supportingVariants.forEach { + it.variant?.id = it?.variant?.id?.let(::anonymize) + } } } } this.responses?.filterNotNull()?.forEach { response -> - response.id = response.id?.let(::anonymize) - response.therapy?.id = response.therapy?.id?.let(::anonymize) + response.id = response.id?.let(::anonymize) + response.therapy?.id = response.therapy?.id?.let(::anonymize) } this.diagnoses?.filterNotNull()?.forEach { diagnose -> - diagnose.id = diagnose.id?.let(::anonymize) - diagnose.histology?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } + diagnose.id = diagnose.id?.let(::anonymize) + diagnose.histology?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } } this.ngsReports?.filterNotNull()?.forEach { ngsReport -> - ngsReport.id = ngsReport.id?.let(::anonymize) - ngsReport.results?.tumorCellContent?.id = ngsReport.results.tumorCellContent?.id?.let(::anonymize) - ngsReport.results?.tumorCellContent?.specimen?.id = - ngsReport.results?.tumorCellContent?.specimen?.id?.let(::anonymize) - ngsReport.results?.rnaFusions?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } - ngsReport.results?.simpleVariants?.filterNotNull()?.forEach { + ngsReport.id = ngsReport.id?.let(::anonymize) + ngsReport.results?.tumorCellContent?.id = + ngsReport.results.tumorCellContent?.id?.let(::anonymize) + ngsReport.results?.tumorCellContent?.specimen?.id = + ngsReport.results?.tumorCellContent?.specimen?.id?.let(::anonymize) + ngsReport.results?.rnaFusions?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } + ngsReport.results?.simpleVariants?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) it.transcriptId?.value = it.transcriptId?.value?.let(::anonymize) } - ngsReport.results?.tmb?.id = ngsReport.results?.tmb?.id?.let(::anonymize) - ngsReport.results?.tmb?.specimen?.id = ngsReport.results?.tmb?.specimen?.id?.let(::anonymize) + ngsReport.results?.tmb?.id = ngsReport.results?.tmb?.id?.let(::anonymize) + ngsReport.results?.tmb?.specimen?.id = ngsReport.results?.tmb?.specimen?.id?.let(::anonymize) - ngsReport.results?.brcaness?.id = ngsReport.results?.brcaness?.id?.let(::anonymize) - ngsReport.results?.brcaness?.specimen?.id = ngsReport.results?.brcaness?.specimen?.id?.let(::anonymize) - ngsReport.results?.copyNumberVariants?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } - ngsReport.results?.hrdScore?.id = ngsReport.results?.hrdScore?.id?.let(::anonymize) - ngsReport.results?.hrdScore?.specimen?.id = ngsReport.results?.hrdScore?.specimen?.id?.let(::anonymize) - ngsReport.results?.rnaSeqs?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } - ngsReport.results?.dnaFusions?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } - ngsReport.specimen?.id = ngsReport.specimen?.id?.let(::anonymize) + ngsReport.results?.brcaness?.id = ngsReport.results?.brcaness?.id?.let(::anonymize) + ngsReport.results?.brcaness?.specimen?.id = + ngsReport.results?.brcaness?.specimen?.id?.let(::anonymize) + ngsReport.results?.copyNumberVariants?.filterNotNull()?.forEach { + it.id = it.id?.let(::anonymize) + } + ngsReport.results?.hrdScore?.id = ngsReport.results?.hrdScore?.id?.let(::anonymize) + ngsReport.results?.hrdScore?.specimen?.id = + ngsReport.results?.hrdScore?.specimen?.id?.let(::anonymize) + ngsReport.results?.rnaSeqs?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } + ngsReport.results?.dnaFusions?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } + ngsReport.specimen?.id = ngsReport.specimen?.id?.let(::anonymize) } this.histologyReports?.filterNotNull()?.forEach { histologyReport -> - histologyReport.id = histologyReport.id?.let(::anonymize) - histologyReport.results?.tumorCellContent?.id = histologyReport.results?.tumorCellContent?.id?.let(::anonymize) - histologyReport.results?.tumorCellContent?.specimen?.id = - histologyReport.results?.tumorCellContent?.specimen?.id?.let(::anonymize) + histologyReport.id = histologyReport.id?.let(::anonymize) + histologyReport.results?.tumorCellContent?.id = + histologyReport.results?.tumorCellContent?.id?.let(::anonymize) + histologyReport.results?.tumorCellContent?.specimen?.id = + histologyReport.results?.tumorCellContent?.specimen?.id?.let(::anonymize) - histologyReport.results?.tumorMorphology?.id = histologyReport.results?.tumorMorphology?.id?.let(::anonymize) - histologyReport.results?.tumorMorphology?.specimen?.id = - histologyReport.results?.tumorMorphology?.specimen?.id?.let(::anonymize) - histologyReport.specimen?.id = histologyReport.specimen?.id?.let(::anonymize) + histologyReport.results?.tumorMorphology?.id = + histologyReport.results?.tumorMorphology?.id?.let(::anonymize) + histologyReport.results?.tumorMorphology?.specimen?.id = + histologyReport.results?.tumorMorphology?.specimen?.id?.let(::anonymize) + histologyReport.specimen?.id = histologyReport.specimen?.id?.let(::anonymize) } this.claimResponses?.filterNotNull()?.forEach { claimResponse -> - claimResponse.id = claimResponse.id?.let(::anonymize) - claimResponse.claim?.id = claimResponse.claim?.id?.let(::anonymize) + claimResponse.id = claimResponse.id?.let(::anonymize) + claimResponse.claim?.id = claimResponse.claim?.id?.let(::anonymize) } this.claims?.filterNotNull()?.forEach { claim -> - claim.id = claim.id?.let(::anonymize) - claim.recommendation?.id = claim.recommendation?.id?.let(::anonymize) + claim.id = claim.id?.let(::anonymize) + claim.recommendation?.id = claim.recommendation?.id?.let(::anonymize) } this.familyMemberHistories?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } this.guidelineProcedures?.filterNotNull()?.forEach { guidelineProcedure -> - guidelineProcedure.id = guidelineProcedure.id?.let(::anonymize) - guidelineProcedure.reason?.id = guidelineProcedure.reason?.id?.let(::anonymize) - guidelineProcedure.basedOn?.id = guidelineProcedure.basedOn?.id?.let(::anonymize) + guidelineProcedure.id = guidelineProcedure.id?.let(::anonymize) + guidelineProcedure.reason?.id = guidelineProcedure.reason?.id?.let(::anonymize) + guidelineProcedure.basedOn?.id = guidelineProcedure.basedOn?.id?.let(::anonymize) } this.guidelineTherapies?.filterNotNull()?.forEach { guidelineTherapy -> - guidelineTherapy.id = guidelineTherapy.id?.let(::anonymize) - guidelineTherapy.reason?.id = guidelineTherapy.reason?.id?.let(::anonymize) - guidelineTherapy.basedOn?.id = guidelineTherapy.basedOn?.id?.let(::anonymize) + guidelineTherapy.id = guidelineTherapy.id?.let(::anonymize) + guidelineTherapy.reason?.id = guidelineTherapy.reason?.id?.let(::anonymize) + guidelineTherapy.basedOn?.id = guidelineTherapy.basedOn?.id?.let(::anonymize) } this.ihcReports?.filterNotNull()?.forEach { ihcReport -> - ihcReport.id = ihcReport.id?.let(::anonymize) - ihcReport.specimen?.id = ihcReport.specimen?.id?.let(::anonymize) - ihcReport.results?.proteinExpression?.filterNotNull()?.forEach { it.id = it.id.let(::anonymize) } + ihcReport.id = ihcReport.id?.let(::anonymize) + ihcReport.specimen?.id = ihcReport.specimen?.id?.let(::anonymize) + ihcReport.results?.proteinExpression?.filterNotNull()?.forEach { + it.id = it.id.let(::anonymize) + } } this.msiFindings?.filterNotNull()?.forEach { msiFinding -> - msiFinding.id = msiFinding.id?.let(::anonymize) - msiFinding.specimen?.id = msiFinding.specimen?.id?.let(::anonymize) + msiFinding.id = msiFinding.id?.let(::anonymize) + msiFinding.specimen?.id = msiFinding.specimen?.id?.let(::anonymize) } this.performanceStatus?.filterNotNull()?.forEach { it.id = it.id?.let(::anonymize) } this.priorDiagnosticReports?.filterNotNull()?.forEach { priorDiagnosticReport -> - priorDiagnosticReport.id = priorDiagnosticReport.id?.let(::anonymize) - priorDiagnosticReport.specimen?.id = priorDiagnosticReport.specimen?.id?.let(::anonymize) + priorDiagnosticReport.id = priorDiagnosticReport.id?.let(::anonymize) + priorDiagnosticReport.specimen?.id = priorDiagnosticReport.specimen?.id?.let(::anonymize) } this.specimens?.filterNotNull()?.forEach { specimen -> - specimen.id = specimen.id?.let(::anonymize) - specimen.diagnosis?.id = specimen.diagnosis?.id?.let(::anonymize) + specimen.id = specimen.id?.let(::anonymize) + specimen.diagnosis?.id = specimen.diagnosis?.id?.let(::anonymize) } this.systemicTherapies?.filterNotNull()?.forEach { systemicTherapy -> - systemicTherapy.history?.filterNotNull()?.forEach { history -> - history.id = history.id?.let(::anonymize) - history.reason?.id = history.reason?.id?.let(::anonymize) - history.basedOn?.id = history.basedOn?.id?.let(::anonymize) + systemicTherapy.history?.filterNotNull()?.forEach { history -> + history.id = history.id?.let(::anonymize) + history.reason?.id = history.reason?.id?.let(::anonymize) + history.basedOn?.id = history.basedOn?.id?.let(::anonymize) } } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt index 95858f4..ce72ba3 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt @@ -25,6 +25,7 @@ import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.consent.ConsentEvaluation import dev.dnpm.etl.processor.consent.ConsentEvaluator import dev.dnpm.etl.processor.consent.TtpConsentStatus +import dev.dnpm.etl.processor.input.Dnpm21MtbFile.Companion.buildMtb import dev.dnpm.etl.processor.services.RequestProcessor import dev.pcvolkmer.mv64e.mtb.* import java.time.Instant @@ -36,6 +37,7 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.ArgumentsSource +import org.junit.jupiter.params.provider.ValueSource import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -112,6 +114,51 @@ class MtbFileRestControllerTest { } } + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile", + "/mtbfile/etl/patient-record", + "/mtb", + "/mtb/etl/patient-record", + "/api/mtbfile", + "/api/mtbfile/etl/patient-record", + "/api/mtb", + "/api/mtb/etl/patient-record", + ] + ) + fun shouldAcceptPostRequests(url: String) { + whenever(consentEvaluator.check(any())) + .thenReturn(ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true)) + + val mtb = + buildMtb( + MvhMetadata.builder() + .modelProjectConsent( + ModelProjectConsent.builder() + .provisions( + listOf( + Provision.builder() + .date(Date()) + .type(ConsentProvision.PERMIT) + .purpose(ModelProjectConsentPurpose.SEQUENCING) + .build() + ) + ) + .build() + ) + .build() + ) + + mockMvc + .post(url) { + content = objectMapper.writeValueAsString(mtb) + contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON + } + .andExpect { status { isAccepted() } } + } + @Test fun shouldProcessDeleteRequest() { mockMvc.delete("/mtbfile/TEST_12345678").andExpect { status { isAccepted() } } @@ -123,6 +170,28 @@ class MtbFileRestControllerTest { ) verify(consentEvaluator, times(0)).check(any()) } + + @ParameterizedTest + @ValueSource( + strings = + [ + "/mtbfile/TEST_12345678", + "/mtbfile/etl/patient-record/TEST_12345678", + "/mtbfile/etl/patient/TEST_12345678", + "/mtb/TEST_12345678", + "/mtb/etl/patient-record/TEST_12345678", + "/mtb/etl/patient/TEST_12345678", + "/api/mtbfile/TEST_12345678", + "/api/mtbfile/etl/patient-record/TEST_12345678", + "/api/mtbfile/etl/patient/TEST_12345678", + "/api/mtb/TEST_12345678", + "/api/mtb/etl/patient-record/TEST_12345678", + "/api/mtb/etl/patient/TEST_12345678", + ] + ) + fun shouldAcceptDeleteRequests(url: String) { + mockMvc.delete(url).andExpect { status { isAccepted() } } + } } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt index 2b4cd34..84b081a 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt @@ -251,44 +251,44 @@ class ExtensionsTest { assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.specimens.first().diagnosis.id) } - @Test - fun shouldNotThrowAnyExceptionOnMissingMsiId( - @Mock pseudonymizeService: PseudonymizeService - ) { + @Test + fun shouldNotThrowAnyExceptionOnMissingMsiId(@Mock pseudonymizeService: PseudonymizeService) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" + doAnswer { + it.arguments[0] + "PSEUDO-ID" } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - - val mtbFile = - Mtb().apply { - this.patient = - Patient().apply { - this.id = "PID" - this.birthDate = Date.from(Instant.now()) - this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } - } - this.msiFindings = listOf( - null, - Msi.builder().id("1").build(), - Msi.builder(). build(), - Msi.builder().specimen(null).build(), - Msi.builder().specimen(Reference.builder().build()).build() - ) - } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) - mtbFile.pseudonymizeWith(pseudonymizeService) - mtbFile.anonymizeContentWith(pseudonymizeService) + doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - assertThat( mtbFile.msiFindings ).isNotNull - assertThat(mtbFile.msiFindings[1]).satisfiesAnyOf( + val mtbFile = + Mtb().apply { + this.patient = + Patient().apply { + this.id = "PID" + this.birthDate = Date.from(Instant.now()) + this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + } + this.msiFindings = + listOf( + null, + Msi.builder().id("1").build(), + Msi.builder().build(), + Msi.builder().specimen(null).build(), + Msi.builder().specimen(Reference.builder().build()).build(), + ) + } + + mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.anonymizeContentWith(pseudonymizeService) + + assertThat(mtbFile.msiFindings).isNotNull + assertThat(mtbFile.msiFindings[1]) + .satisfiesAnyOf( { assertThat(it.id).isNull() }, - { assertThat(it.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098")} + { assertThat(it.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") }, ) - } + } } -- cgit v1.2.3