/* * This file is part of ETL-Processor * * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package dev.dnpm.etl.processor.input import com.fasterxml.jackson.databind.ObjectMapper import dev.dnpm.etl.processor.ArgProvider import dev.dnpm.etl.processor.CustomMediaType 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 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.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 import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.whenever import org.springframework.core.io.ClassPathResource import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.post import org.springframework.test.web.servlet.setup.MockMvcBuilders import java.time.Instant import java.util.* @ExtendWith(MockitoExtension::class) class MtbFileRestControllerTest { private val objectMapper = ObjectMapper() @Nested inner class RequestsForDnpmDataModel21 { private lateinit var mockMvc: MockMvc private lateinit var requestProcessor: RequestProcessor @BeforeEach fun setup(@Mock requestProcessor: RequestProcessor) { this.requestProcessor = requestProcessor val controller = MtbFileRestController(requestProcessor) this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build() } @Test fun shouldRespondPostRequest() { val mtbFileContent = ClassPathResource("mv64e-mtb-fake-patient.json") .inputStream .readAllBytes() .toString(Charsets.UTF_8) whenever { requestProcessor.processMtbFile(any()) }.thenReturn(true) mockMvc .post("/mtb") { content = mtbFileContent contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON } .andExpect { status { isAccepted() } } verify(requestProcessor, times(1)).processMtbFile(any()) } @ParameterizedTest @ArgumentsSource(Dnpm21MtbFile::class) fun shouldProcessPostRequest(mtb: Mtb) { whenever { requestProcessor.processMtbFile(any()) }.thenReturn(true) mockMvc .post("/mtbfile") { content = objectMapper.writeValueAsString(mtb) contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON } .andExpect { status { isAccepted() } } } @Test fun shouldNotAcceptInvalidPostRequest() { mockMvc .post("/mtbfile") { content = "{}" contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON } .andExpect { status { isBadRequest() } } } @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) { val mtb = buildMtb( MvhMetadata.builder() .modelProjectConsent( ModelProjectConsent.builder() .provisions( listOf( Provision.builder() .date(Date()) .type(ConsentProvision.PERMIT) .purpose(ModelProjectConsentPurpose.SEQUENCING) .build() ) ) .build() ) .build() ) whenever { requestProcessor.processMtbFile(any()) }.thenReturn(true) 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() } } verify(requestProcessor, times(1)) .processDeletion( anyValueClass(), org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE), ) } @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() } } } } } class Dnpm21MtbFile : ArgProvider( // No Metadata and broad consent given => process Arguments.of(buildMtb(null), TtpConsentStatus.BROAD_CONSENT_GIVEN, "process"), // No model project consent and broad consent given => process Arguments.of( buildMtb( MvhMetadata.builder() .modelProjectConsent(ModelProjectConsent.builder().build()) .build() ) ), // Model project consent given and no broad consent => process Arguments.of( buildMtb( MvhMetadata.builder() .modelProjectConsent( ModelProjectConsent.builder() .provisions( listOf( Provision.builder() .date(Date()) .type(ConsentProvision.PERMIT) .purpose(ModelProjectConsentPurpose.SEQUENCING) .build() ) ) .build() ) .build() ) ), // Model project consent given and broad consent given => process Arguments.of( buildMtb( MvhMetadata.builder() .modelProjectConsent( ModelProjectConsent.builder() .provisions( listOf( Provision.builder() .date(Date()) .type(ConsentProvision.PERMIT) .purpose(ModelProjectConsentPurpose.SEQUENCING) .build() ) ) .build() ) .build() ) ), ) { companion object { fun buildMtb(metadata: MvhMetadata?): Mtb { return Mtb.builder() .patient( Patient.builder() .id("TEST_12345678") .birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z"))) .gender(GenderCoding.builder().code(GenderCodingCode.MALE).build()) .build() ) .metadata(metadata) .episodesOfCare( listOf( MtbEpisodeOfCare.builder() .id("1") .patient(Reference.builder().id("TEST_12345678").build()) .build() ) ) .build() } } }