From a8f8d5f137c9776a20b2bc91cd3bdd99c9b96991 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 11 Mar 2026 14:13:29 +0100 Subject: feat: save error request for invalid input (#264) --- .../processor/input/MtbFileRestControllerTest.kt | 277 ++-- .../etl/processor/services/RequestProcessorTest.kt | 1404 ++++++++++---------- 2 files changed, 852 insertions(+), 829 deletions(-) (limited to 'src/test/kotlin') 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 f3d669b..c8e5804 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt @@ -22,14 +22,11 @@ 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.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 -import java.util.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -50,134 +47,148 @@ 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() + private val objectMapper = ObjectMapper() - @Nested - inner class RequestsForDnpmDataModel21 { + @Nested + inner class RequestsForDnpmDataModel21 { - private lateinit var mockMvc: MockMvc + private lateinit var mockMvc: MockMvc - private lateinit var requestProcessor: RequestProcessor - private lateinit var consentEvaluator: ConsentEvaluator + private lateinit var requestProcessor: RequestProcessor - @BeforeEach - fun setup(@Mock requestProcessor: RequestProcessor, @Mock consentEvaluator: ConsentEvaluator) { - this.requestProcessor = requestProcessor - this.consentEvaluator = consentEvaluator - val controller = MtbFileRestController(requestProcessor, consentEvaluator) - this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build() - } + @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) + @Test + fun shouldRespondPostRequest() { + val mtbFileContent = + ClassPathResource("mv64e-mtb-fake-patient.json") + .inputStream + .readAllBytes() + .toString(Charsets.UTF_8) - mockMvc - .post("/mtb") { - content = mtbFileContent - contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON - } - .andExpect { status { isAccepted() } } + whenever { requestProcessor.processMtbFile(any()) }.thenReturn(true) - verify(requestProcessor, times(1)).processMtbFile(any()) - } + mockMvc + .post("/mtb") { + content = mtbFileContent + contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON + } + .andExpect { status { isAccepted() } } - @ParameterizedTest - @ArgumentsSource(Dnpm21MtbFile::class) - fun shouldProcessPostRequest(mtb: Mtb) { - mockMvc - .post("/mtbfile") { - content = objectMapper.writeValueAsString(mtb) - 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) - @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() - ) + mockMvc + .post("/mtbfile") { + content = objectMapper.writeValueAsString(mtb) + contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON + } + .andExpect { status { isAccepted() } } + } - mockMvc - .post(url) { - 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() } } + } - @Test - fun shouldProcessDeleteRequest() { - mockMvc.delete("/mtbfile/TEST_12345678").andExpect { status { isAccepted() } } + @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() + ) - verify(requestProcessor, times(1)) - .processDeletion( - anyValueClass(), - org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE), - ) - verify(consentEvaluator, times(0)).check(any()) - } + 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() } } - @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() } } + 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 : @@ -234,26 +245,26 @@ class Dnpm21MtbFile : ), ) { - 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() + 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() + } } - } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt index afa6872..70df248 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt @@ -20,10 +20,7 @@ package dev.dnpm.etl.processor.services import com.fasterxml.jackson.databind.ObjectMapper -import dev.dnpm.etl.processor.Fingerprint -import dev.dnpm.etl.processor.PatientId -import dev.dnpm.etl.processor.PatientPseudonym -import dev.dnpm.etl.processor.Tan +import dev.dnpm.etl.processor.* import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.consent.TtpConsentStatus import dev.dnpm.etl.processor.monitoring.Request @@ -35,10 +32,7 @@ import dev.dnpm.etl.processor.output.DnpmV2MtbFileRequest import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.RestMtbFileSender import dev.dnpm.etl.processor.pseudonym.PseudonymizeService -import dev.dnpm.etl.processor.randomRequestId import dev.pcvolkmer.mv64e.mtb.* -import java.time.Instant -import java.util.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested @@ -52,761 +46,779 @@ import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.whenever import org.springframework.context.ApplicationEventPublisher +import java.time.Instant +import java.util.* @ExtendWith(MockitoExtension::class) class RequestProcessorTest { - private lateinit var pseudonymizeService: PseudonymizeService - private lateinit var transformationService: TransformationService - private lateinit var sender: MtbFileSender - private lateinit var requestService: RequestService - private lateinit var applicationEventPublisher: ApplicationEventPublisher - private lateinit var appConfigProperties: AppConfigProperties - private lateinit var consentProcessor: ConsentProcessor - private lateinit var requestProcessor: RequestProcessor - - @BeforeEach - fun setup( - @Mock pseudonymizeService: PseudonymizeService, - @Mock transformationService: TransformationService, - @Mock sender: RestMtbFileSender, - @Mock requestService: RequestService, - @Mock applicationEventPublisher: ApplicationEventPublisher, - @Mock consentProcessor: ConsentProcessor, - ) { - this.pseudonymizeService = pseudonymizeService - this.transformationService = transformationService - this.sender = sender - this.requestService = requestService - this.applicationEventPublisher = applicationEventPublisher - this.appConfigProperties = AppConfigProperties() - this.consentProcessor = consentProcessor - - val objectMapper = ObjectMapper() - - requestProcessor = - RequestProcessor( - pseudonymizeService, - transformationService, - sender, - requestService, - objectMapper, - applicationEventPublisher, - appConfigProperties, - consentProcessor, - ) - } - - @Test - fun testShouldSendMtbFileDuplicationAndSaveUnknownRequestStatusAtFirst() { - doAnswer { - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("6vkiti5bk6ikwifpajpt7cygmd3dvw54d6lwfhzlynb3pqtzferq"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-08-08T02:00:00Z"), - ) - } - .whenever(requestService) - .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + private lateinit var pseudonymizeService: PseudonymizeService + private lateinit var transformationService: TransformationService + private lateinit var sender: MtbFileSender + private lateinit var requestService: RequestService + private lateinit var applicationEventPublisher: ApplicationEventPublisher + private lateinit var appConfigProperties: AppConfigProperties + private lateinit var consentProcessor: ConsentProcessor + private lateinit var requestProcessor: RequestProcessor - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) + @BeforeEach + fun setup( + @Mock pseudonymizeService: PseudonymizeService, + @Mock transformationService: TransformationService, + @Mock sender: RestMtbFileSender, + @Mock requestService: RequestService, + @Mock applicationEventPublisher: ApplicationEventPublisher, + @Mock consentProcessor: ConsentProcessor, + ) { + this.pseudonymizeService = pseudonymizeService + this.transformationService = transformationService + this.sender = sender + this.requestService = requestService + this.applicationEventPublisher = applicationEventPublisher + this.appConfigProperties = AppConfigProperties() + this.consentProcessor = consentProcessor + + val objectMapper = ObjectMapper() + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + objectMapper, + applicationEventPublisher, + appConfigProperties, + consentProcessor, + ) + } - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + @Test + fun testShouldSendMtbFileDuplicationAndSaveUnknownRequestStatusAtFirst() { + doAnswer { + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("6vkiti5bk6ikwifpajpt7cygmd3dvw54d6lwfhzlynb3pqtzferq"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2023-08-08T02:00:00Z"), + ) + } + .whenever(requestService) + .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2023-08-08T02:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + this.requestProcessor.processMtbFile(mtbFile) - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN) + } - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2023-08-08T02:00:00.00Z"))) - .build() - ) - .build() - ) + @Test + fun testShouldDetectMtbFileDuplicationAndSendDuplicationEvent() { + doAnswer { + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("4gcjwtjjtcczybsljxepdfpkaeusvd7g3vogfqpmphyffyzfx7dq"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2023-08-08T02:00:00Z"), ) - .build() - - this.requestProcessor.processMtbFile(mtbFile) - - val requestCaptor = argumentCaptor() - verify(requestService, times(1)).save(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN) - } - - @Test - fun testShouldDetectMtbFileDuplicationAndSendDuplicationEvent() { - doAnswer { - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("4gcjwtjjtcczybsljxepdfpkaeusvd7g3vogfqpmphyffyzfx7dq"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-08-08T02:00:00Z"), - ) } - .whenever(requestService) - .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + .whenever(requestService) + .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) + this.requestProcessor.processMtbFile(mtbFile) - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.DUPLICATION) + } - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + @Test + fun testShouldSendMtbFileAndSendSuccessEvent() { + doAnswer { + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("different"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2023-08-08T02:00:00Z"), + ) + } + .whenever(requestService) + .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + this.requestProcessor.processMtbFile(mtbFile) - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } + + @Test + fun testShouldSendMtbFileAndSendErrorEvent() { + doAnswer { + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("different"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2023-08-08T02:00:00Z"), + ) + } + .whenever(requestService) + .lastMtbFileRequestForPatientPseudonym(anyValueClass()) + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.ERROR) } + .whenever(sender) + .send(any()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } + .whenever(pseudonymizeService) + .genomDeTan(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .metadata( + MvhMetadata.builder() + .modelProjectConsent( + ModelProjectConsent.builder() + .provisions( + listOf( + Provision.builder() + .type(ConsentProvision.PERMIT) + .purpose(ModelProjectConsentPurpose.SEQUENCING) + .build() + ) + ) .build() ) .build() ) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() + + this.requestProcessor.processMtbFile(mtbFile) + + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) + } + + @Test + fun testShouldSendMtbFileAdditionIfInitialFileWasAccepted() { + + // One successful and accepted and one blocked initial + val lastRequests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2026-01-05T09:00:00Z"), + submissionAccepted = true, + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("blocked_initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.BLOCKED_INITIAL, + Tan.empty(), + Instant.parse("2026-01-05T10:00:00Z"), + submissionAccepted = false, + ), ) - .build() - - this.requestProcessor.processMtbFile(mtbFile) - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.DUPLICATION) - } - - @Test - fun testShouldSendMtbFileAndSendSuccessEvent() { - doAnswer { - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("different"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-08-08T02:00:00Z"), - ) - } - .whenever(requestService) - .lastMtbFileRequestForPatientPseudonym(anyValueClass()) - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) + doAnswer { lastRequests } + .whenever(requestService) + .allRequestsByPatientPseudonym(anyValueClass()) - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } + .whenever(pseudonymizeService) + .genomDeTan(anyValueClass()) - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() - ) + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + ObjectMapper(), + applicationEventPublisher, + AppConfigProperties(postInitialSubmissionBlock = true), + consentProcessor, ) - .build() - - this.requestProcessor.processMtbFile(mtbFile) - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) - } - - @Test - fun testShouldSendMtbFileAndSendErrorEvent() { - doAnswer { - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("different"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-08-08T02:00:00Z"), - ) - } - .whenever(requestService) - .lastMtbFileRequestForPatientPseudonym(anyValueClass()) - - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.ERROR) } - .whenever(sender) - .send(any()) - - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } - .whenever(pseudonymizeService) - .genomDeTan(anyValueClass()) - - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) - - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) - - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .metadata( - MvhMetadata.builder() - .modelProjectConsent( - ModelProjectConsent.builder() - .provisions( - listOf( - Provision.builder() - .type(ConsentProvision.PERMIT) - .purpose(ModelProjectConsentPurpose.SEQUENCING) - .build() - ) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .metadata(MvhMetadata()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() ) .build() ) - .build() - ) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() ) - ) - .build() + .build() - this.requestProcessor.processMtbFile(mtbFile) + this.requestProcessor.processMtbFile(mtbFile) - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) - } + val requestCaptor = argumentCaptor() + verify(sender, times(1)).send(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION) + assertThat(requestCaptor.firstValue.content.metadata.transferTan).isEqualTo("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2") - @Test - fun testShouldSendMtbFileAdditionIfInitialFileWasAccepted() { + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } - // One successful and accepted and one blocked initial - val lastRequests = - listOf( - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("initial"), - RequestType.MTB_FILE, - SubmissionType.INITIAL, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2026-01-05T09:00:00Z"), - submissionAccepted = true, - ), - Request( - 2L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("blocked_initial"), - RequestType.MTB_FILE, - SubmissionType.INITIAL, - RequestStatus.BLOCKED_INITIAL, - Tan.empty(), - Instant.parse("2026-01-05T10:00:00Z"), - submissionAccepted = false, - ), + @Test + fun testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() { + doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.UNKNOWN) } + .whenever(sender) + .send(any()) + + this.requestProcessor.processDeletion( + TEST_PATIENT_ID, + isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, ) - doAnswer { lastRequests } - .whenever(requestService) - .allRequestsByPatientPseudonym(anyValueClass()) + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN) + } - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) + @Test + fun testShouldSendDeleteRequestAndSendSuccessEvent() { + doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + this.requestProcessor.processDeletion( + TEST_PATIENT_ID, + isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, + ) - doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } - .whenever(pseudonymizeService) - .genomDeTan(anyValueClass()) + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + @Test + fun testShouldSendRequestWithoutConsent() { + doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) + + doAnswer { it.arguments.first() } + .whenever(transformationService) + .transform(any()) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .metadata(MvhMetadata()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() + + this.requestProcessor.processMtbFile( + mtbFile, + randomRequestId(), + ) + + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } + + @Test + fun testShouldSendDeleteRequestAndSendErrorEvent() { + doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + doAnswer { MtbFileSender.Response(status = RequestStatus.ERROR) } + .whenever(sender) + .send(any()) - requestProcessor = - RequestProcessor( - pseudonymizeService, - transformationService, - sender, - requestService, - ObjectMapper(), - applicationEventPublisher, - AppConfigProperties(postInitialSubmissionBlock = true), - consentProcessor, + this.requestProcessor.processDeletion( + TEST_PATIENT_ID, + isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, ) - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .metadata(MvhMetadata()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() - ) - ) - .build() - - this.requestProcessor.processMtbFile(mtbFile) - - val requestCaptor = argumentCaptor() - verify(sender, times(1)).send(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION) - assertThat(requestCaptor.firstValue.content.metadata.transferTan).isEqualTo("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2") - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) - } - - @Test - fun testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() { - doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.UNKNOWN) } - .whenever(sender) - .send(any()) - - this.requestProcessor.processDeletion( - TEST_PATIENT_ID, - isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, - ) - - val requestCaptor = argumentCaptor() - verify(requestService, times(1)).save(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.UNKNOWN) - } - - @Test - fun testShouldSendDeleteRequestAndSendSuccessEvent() { - doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) - - this.requestProcessor.processDeletion( - TEST_PATIENT_ID, - isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, - ) - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) - } + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) + } @Test - fun testShouldSendRequestWithoutConsent() { - doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) - - doAnswer { it.arguments.first() } - .whenever(transformationService) - .transform(any()) - - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .metadata(MvhMetadata()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() + fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() { + doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass()) + + this.requestProcessor.processDeletion( + TEST_PATIENT_ID, + isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, + ) + + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) + } + + @Test + fun testShouldNotDetectMtbFileDuplicationIfDuplicationNotConfigured() { + this.appConfigProperties.duplicationDetection = false + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) ) + .build() + + this.requestProcessor.processMtbFile(mtbFile) + + val eventCaptor = argumentCaptor() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } + + @Test + fun testShouldSaveRequestWithGenomDeTan() { + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } + .whenever(pseudonymizeService) + .genomDeTan(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + ObjectMapper(), + applicationEventPublisher, + AppConfigProperties(postInitialSubmissionBlock = true), + consentProcessor, ) - .build() - - this.requestProcessor.processMtbFile( - mtbFile, - randomRequestId(), - ) - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) - } - - @Test - fun testShouldSendDeleteRequestAndSendErrorEvent() { - doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.ERROR) } - .whenever(sender) - .send(any()) - - this.requestProcessor.processDeletion( - TEST_PATIENT_ID, - isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, - ) - - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) - } - - @Test - fun testShouldSendDeleteRequestWithPseudonymErrorAndSaveErrorRequestStatus() { - doThrow(RuntimeException()).whenever(pseudonymizeService).patientPseudonym(anyValueClass()) - - this.requestProcessor.processDeletion( - TEST_PATIENT_ID, - isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE, - ) - - val requestCaptor = argumentCaptor() - verify(requestService, times(1)).save(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) - } - - @Test - fun testShouldNotDetectMtbFileDuplicationIfDuplicationNotConfigured() { - this.appConfigProperties.duplicationDetection = false - - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) - - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) - - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .metadata(MvhMetadata()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) ) - ) - .build() + .build() - this.requestProcessor.processMtbFile(mtbFile) + this.requestProcessor.processMtbFile(mtbFile) - val eventCaptor = argumentCaptor() - verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) - assertThat(eventCaptor.firstValue).isNotNull - assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) - } + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.tan).isEqualTo(Tan("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2")) + } - @Test - fun testShouldSaveRequestWithGenomDeTan() { - - doAnswer { false } - .whenever(requestService) - .isLastRequestWithKnownStatusDeletion(anyValueClass()) - - doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } - .whenever(sender) - .send(any()) - - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" } - .whenever(pseudonymizeService) - .genomDeTan(anyValueClass()) - - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) - - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) - - requestProcessor = - RequestProcessor( - pseudonymizeService, - transformationService, - sender, - requestService, - ObjectMapper(), - applicationEventPublisher, - AppConfigProperties(postInitialSubmissionBlock = true), - consentProcessor, - ) + @Nested + inner class WithInitialSubmissionBlock { + + private lateinit var pseudonymizeService: PseudonymizeService + private lateinit var transformationService: TransformationService + private lateinit var sender: MtbFileSender + private lateinit var requestService: RequestService + private lateinit var applicationEventPublisher: ApplicationEventPublisher + private lateinit var appConfigProperties: AppConfigProperties + private lateinit var consentProcessor: ConsentProcessor + private lateinit var requestProcessor: RequestProcessor + + @BeforeEach + fun setup( + @Mock pseudonymizeService: PseudonymizeService, + @Mock transformationService: TransformationService, + @Mock sender: RestMtbFileSender, + @Mock requestService: RequestService, + @Mock applicationEventPublisher: ApplicationEventPublisher, + @Mock consentProcessor: ConsentProcessor, + ) { + this.pseudonymizeService = pseudonymizeService + this.transformationService = transformationService + this.sender = sender + this.requestService = requestService + this.applicationEventPublisher = applicationEventPublisher + this.appConfigProperties = AppConfigProperties() + this.consentProcessor = consentProcessor + + val objectMapper = ObjectMapper() + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + objectMapper, + applicationEventPublisher, + appConfigProperties, + consentProcessor, + ) + } + + @Test + fun testShouldNotSendMtbFileIfInitialFileWasSent() { - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .metadata(MvhMetadata()) - .episodesOfCare( + // One failed attempt and one successful but not accepted + val lastRequests = listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.ERROR, + Tan.empty(), + Instant.parse("2026-01-05T09:00:00Z"), + submissionAccepted = false, + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("blocked_initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2026-01-05T10:00:00Z"), + submissionAccepted = false, + ), ) - ) - .build() - this.requestProcessor.processMtbFile(mtbFile) + doAnswer { lastRequests } + .whenever(requestService) + .allRequestsByPatientPseudonym(anyValueClass()) - val requestCaptor = argumentCaptor() - verify(requestService, times(1)).save(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.tan).isEqualTo(Tan("f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2")) - } + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) - @Nested - inner class WithInitialSubmissionBlock { + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) - private lateinit var pseudonymizeService: PseudonymizeService - private lateinit var transformationService: TransformationService - private lateinit var sender: MtbFileSender - private lateinit var requestService: RequestService - private lateinit var applicationEventPublisher: ApplicationEventPublisher - private lateinit var appConfigProperties: AppConfigProperties - private lateinit var consentProcessor: ConsentProcessor - private lateinit var requestProcessor: RequestProcessor + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) - @BeforeEach - fun setup( - @Mock pseudonymizeService: PseudonymizeService, - @Mock transformationService: TransformationService, - @Mock sender: RestMtbFileSender, - @Mock requestService: RequestService, - @Mock applicationEventPublisher: ApplicationEventPublisher, - @Mock consentProcessor: ConsentProcessor, - ) { - this.pseudonymizeService = pseudonymizeService - this.transformationService = transformationService - this.sender = sender - this.requestService = requestService - this.applicationEventPublisher = applicationEventPublisher - this.appConfigProperties = AppConfigProperties() - this.consentProcessor = consentProcessor - - val objectMapper = ObjectMapper() - - requestProcessor = - RequestProcessor( - pseudonymizeService, - transformationService, - sender, - requestService, - objectMapper, - applicationEventPublisher, - appConfigProperties, - consentProcessor, - ) + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + ObjectMapper(), + applicationEventPublisher, + AppConfigProperties(postInitialSubmissionBlock = true), + consentProcessor, + ) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() + + this.requestProcessor.processMtbFile(mtbFile) + + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.BLOCKED_INITIAL) + + verify(applicationEventPublisher, times(0)).publishEvent(any()) + verify(sender, times(0)).send(any()) + } } @Test - fun testShouldNotSendMtbFileIfInitialFileWasSent() { - - // One failed attempt and one successful but not accepted - val lastRequests = - listOf( - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("initial"), - RequestType.MTB_FILE, - SubmissionType.INITIAL, - RequestStatus.ERROR, - Tan.empty(), - Instant.parse("2026-01-05T09:00:00Z"), - submissionAccepted = false, - ), - Request( - 2L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("blocked_initial"), - RequestType.MTB_FILE, - SubmissionType.INITIAL, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2026-01-05T10:00:00Z"), - submissionAccepted = false, - ), - ) - - doAnswer { lastRequests } - .whenever(requestService) - .allRequestsByPatientPseudonym(anyValueClass()) - - doAnswer { it.arguments[0] as String } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { it.arguments[0] }.whenever(transformationService).transform(any()) - - whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) - - requestProcessor = - RequestProcessor( - pseudonymizeService, - transformationService, - sender, - requestService, - ObjectMapper(), - applicationEventPublisher, - AppConfigProperties(postInitialSubmissionBlock = true), - consentProcessor, - ) - - val mtbFile = - Mtb.builder() - .patient(Patient.builder().id("123").build()) - .episodesOfCare( - listOf( - MtbEpisodeOfCare.builder() - .id("1") - .patient(Reference.builder().id("123").build()) - .period( - PeriodDate.builder() - .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) - .build() - ) - .build() - ) - ) - .build() - - this.requestProcessor.processMtbFile(mtbFile) - - val requestCaptor = argumentCaptor() - verify(requestService, times(1)).save(requestCaptor.capture()) - assertThat(requestCaptor.firstValue).isNotNull - assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.BLOCKED_INITIAL) - - verify(applicationEventPublisher, times(0)).publishEvent(any()) - verify(sender, times(0)).send(any()) + fun shouldCatchExceptionsWhenProcessingMtbFileAndSaveError() { + val invalidMtbFile = Mtb.builder().build() + + val success = this.requestProcessor.processMtbFile(invalidMtbFile) + + assertThat(success).isFalse() + + verify(sender, times(0)).send(any()) + + val requestCaptor = argumentCaptor() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isInstanceOf(Request::class.java) + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR) } - } - companion object { - val TEST_PATIENT_ID = PatientId("TEST_12345678901") - } + companion object { + val TEST_PATIENT_ID = PatientId("TEST_12345678901") + } } -- cgit v1.2.3