summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul-Christian Volkmer2026-03-11 14:13:29 +0100
committerGitHub2026-03-11 13:13:29 +0000
commita8f8d5f137c9776a20b2bc91cd3bdd99c9b96991 (patch)
treeb2df9c483c2324a4bc837b9c4fa127da8fd0333f
parent5178673955a69b14ff39bf8a2a73d50ef2fd9cd2 (diff)
feat: save error request for invalid input (#264)
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt442
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt57
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt410
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt277
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt1404
5 files changed, 1325 insertions, 1265 deletions
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 4c7de9c..ed9d910 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
@@ -29,8 +29,6 @@ import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.UserRoleRepository
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.extension.ExtendWith
@@ -51,6 +49,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.delete
import org.springframework.test.web.servlet.post
+import java.time.Instant
+import java.util.*
@WebMvcTest(controllers = [MtbFileRestController::class])
@ExtendWith(value = [MockitoExtension::class, SpringExtension::class])
@@ -74,192 +74,81 @@ import org.springframework.test.web.servlet.post
)
class MtbFileRestControllerTest {
- lateinit var mockMvc: MockMvc
- lateinit var requestProcessor: RequestProcessor
- lateinit var consentEvaluator: ConsentEvaluator
-
- @BeforeEach
- fun setup(
- @Autowired mockMvc: MockMvc,
- @Autowired requestProcessor: RequestProcessor,
- @Autowired consentEvaluator: ConsentEvaluator,
- ) {
- this.mockMvc = mockMvc
- this.requestProcessor = requestProcessor
- this.consentEvaluator = consentEvaluator
-
- doAnswer { ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true) }
- .whenever(consentEvaluator)
- .check(any())
- }
-
- @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(url) {
- with(user("onkostarserver").roles("MTBFILE"))
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isAccepted() } }
-
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
- }
-
- @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(url) {
- with(user("onkostarserver").roles("ADMIN"))
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isAccepted() } }
+ lateinit var mockMvc: MockMvc
+ lateinit var requestProcessor: RequestProcessor
+ lateinit var consentEvaluator: ConsentEvaluator
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
- }
+ @BeforeEach
+ fun setup(
+ @Autowired mockMvc: MockMvc,
+ @Autowired requestProcessor: RequestProcessor,
+ @Autowired consentEvaluator: ConsentEvaluator,
+ ) {
+ this.mockMvc = mockMvc
+ this.requestProcessor = requestProcessor
+ this.consentEvaluator = consentEvaluator
- @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(url) {
- with(user("testuser").roles("USER"))
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isAccepted() } }
-
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
- }
+ doAnswer { ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true) }
+ .whenever(consentEvaluator)
+ .check(any())
+ }
- @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 testShouldDenyPermissionToSendMtbFileForAnonymous(url: String) {
- mockMvc
- .post(url) {
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isUnauthorized() } }
+ @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) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
- verify(requestProcessor, never()).processMtbFile(any<Mtb>())
- }
+ mockMvc
+ .post(url) {
+ with(user("onkostarserver").roles("MTBFILE"))
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .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 testShouldGrantPermissionToDeletePatientData(url: String) {
- mockMvc
- .delete(url) { with(user("onkostarserver").roles("MTBFILE")) }
- .andExpect { status { isAccepted() } }
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ }
- verify(requestProcessor, times(1))
- .processDeletion(anyValueClass(), eq(TtpConsentStatus.UNKNOWN_CHECK_FILE))
- }
+ @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) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
- @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() } }
+ mockMvc
+ .post(url) {
+ with(user("onkostarserver").roles("ADMIN"))
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .andExpect { status { isAccepted() } }
- verify(requestProcessor, never()).processDeletion(anyValueClass(), any())
- }
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ }
- @Nested
- @MockitoBean(types = [UserRoleRepository::class, ClientRegistrationRepository::class])
- @TestPropertySource(
- properties =
- [
- "app.pseudonymize.generator=BUILDIN",
- "app.security.admin-user=admin",
- "app.security.admin-password={noop}very-secret",
- "app.security.enable-tokens=true",
- "app.security.enable-oidc=true",
- ]
- )
- inner class WithOidcEnabled {
@ParameterizedTest
@ValueSource(
strings =
@@ -274,16 +163,18 @@ class MtbFileRestControllerTest {
"/api/mtb/etl/patient-record",
]
)
- fun testShouldGrantPermissionToSendMtbFileToAdminUser(url: String) {
- mockMvc
- .post(url) {
- with(user("onkostarserver").roles("ADMIN"))
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isAccepted() } }
+ fun testShouldGrantPermissionToSendMtbFileToUser(url: String) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+
+ mockMvc
+ .post(url) {
+ with(user("testuser").roles("USER"))
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .andExpect { status { isAccepted() } }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
}
@ParameterizedTest
@@ -300,37 +191,156 @@ class MtbFileRestControllerTest {
"/api/mtb/etl/patient-record",
]
)
- fun testShouldGrantPermissionToSendMtbFileToUser(url: String) {
- mockMvc
- .post(url) {
- with(user("onkostarserver").roles("USER"))
- contentType = MediaType.APPLICATION_JSON
- content = ObjectMapper().writeValueAsString(mtbFile)
- }
- .andExpect { status { isAccepted() } }
+ fun testShouldDenyPermissionToSendMtbFileForAnonymous(url: String) {
+ mockMvc
+ .post(url) {
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .andExpect { status { isUnauthorized() } }
+
+ verify(requestProcessor, never()).processMtbFile(any<Mtb>())
+ }
+
+ @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(url) { with(user("onkostarserver").roles("MTBFILE")) }
+ .andExpect { status { isAccepted() } }
+
+ verify(requestProcessor, times(1))
+ .processDeletion(anyValueClass(), eq(TtpConsentStatus.UNKNOWN_CHECK_FILE))
+ }
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ @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())
+ }
+
+ @Nested
+ @MockitoBean(types = [UserRoleRepository::class, ClientRegistrationRepository::class])
+ @TestPropertySource(
+ properties =
+ [
+ "app.pseudonymize.generator=BUILDIN",
+ "app.security.admin-user=admin",
+ "app.security.admin-password={noop}very-secret",
+ "app.security.enable-tokens=true",
+ "app.security.enable-oidc=true",
+ ]
+ )
+ inner class WithOidcEnabled {
+ @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) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+
+ mockMvc
+ .post(url) {
+ with(user("onkostarserver").roles("ADMIN"))
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .andExpect { status { isAccepted() } }
+
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ }
+
+ @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) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.thenReturn(true)
+
+ mockMvc
+ .post(url) {
+ with(user("onkostarserver").roles("USER"))
+ contentType = MediaType.APPLICATION_JSON
+ content = ObjectMapper().writeValueAsString(mtbFile)
+ }
+ .andExpect { status { isAccepted() } }
+
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ }
}
- }
- companion object {
+ companion object {
- val mtbFile =
- Mtb.builder()
- .patient(Patient.builder().id("PID").build())
- .episodesOfCare(
- listOf(
- MtbEpisodeOfCare.builder()
- .id("1")
- .patient(Reference.builder().id("PID").build())
- .period(
- PeriodDate.builder()
- .start(Date.from(Instant.parse("2023-08-08T02:00:00.00Z")))
- .build()
- )
- .build()
+ val mtbFile =
+ Mtb.builder()
+ .patient(Patient.builder().id("PID").build())
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder()
+ .id("1")
+ .patient(Reference.builder().id("PID").build())
+ .period(
+ PeriodDate.builder()
+ .start(Date.from(Instant.parse("2023-08-08T02:00:00.00Z")))
+ .build()
+ )
+ .build()
+ )
)
- )
- .build()
- }
+ .build()
+ }
}
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 f4ab194..523a0a8 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
@@ -33,36 +33,37 @@ import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping(path = ["mtbfile", "mtb", "api/mtbfile", "api/mtb"])
class MtbFileRestController(
- private val requestProcessor: RequestProcessor,
- private val consentEvaluator: ConsentEvaluator,
+ private val requestProcessor: RequestProcessor
) {
- private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
+ private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
- @GetMapping
- fun info(): ResponseEntity<String> {
- return ResponseEntity.ok("Test")
- }
+ @GetMapping
+ fun info(): ResponseEntity<String> {
+ return ResponseEntity.ok("Test")
+ }
- @PostMapping(
- path = ["", "etl/patient-record"],
- consumes =
- [
- MediaType.APPLICATION_JSON_VALUE,
- CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE,
- ],
- )
- fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
- logger.debug("Accepted MTB File (DNPM V2) for processing")
- requestProcessor.processMtbFile(mtbFile)
- return ResponseEntity.accepted().build()
- }
+ @PostMapping(
+ path = ["", "etl/patient-record"],
+ consumes =
+ [
+ MediaType.APPLICATION_JSON_VALUE,
+ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE,
+ ],
+ )
+ fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
+ logger.debug("Accepted MTB File (DNPM V2) for processing")
+ if (requestProcessor.processMtbFile(mtbFile)) {
+ return ResponseEntity.accepted().build()
+ }
+ return ResponseEntity.badRequest().build()
+ }
- @DeleteMapping(
- path = ["{patientId}", "etl/patient-record/{patientId}", "etl/patient/{patientId}"]
- )
- fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
- logger.debug("Accepted patient ID to process deletion")
- requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE)
- return ResponseEntity.accepted().build()
- }
+ @DeleteMapping(
+ path = ["{patientId}", "etl/patient-record/{patientId}", "etl/patient/{patientId}"]
+ )
+ fun deleteData(@PathVariable patientId: String): ResponseEntity<Unit> {
+ logger.debug("Accepted patient ID to process deletion")
+ requestProcessor.processDeletion(PatientId(patientId), TtpConsentStatus.UNKNOWN_CHECK_FILE)
+ return ResponseEntity.accepted().build()
+ }
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
index fe1fd3b..7325265 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt
@@ -56,225 +56,251 @@ class RequestProcessor(
private val consentProcessor: ConsentProcessor?,
) {
- private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
+ private var logger: Logger = LoggerFactory.getLogger("RequestProcessor")
- fun processMtbFile(mtbFile: Mtb) {
- processMtbFile(mtbFile, randomRequestId())
- }
+ fun processMtbFile(mtbFile: Mtb): Boolean {
+ return processMtbFile(mtbFile, randomRequestId())
+ }
+
+ fun processMtbFile(mtbFile: Mtb, requestId: RequestId): Boolean {
+ val isConsentOk =
+ consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
+ consentProcessor == null
- fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
- val isConsentOk =
- consentProcessor != null && consentProcessor.consentGatedCheckAndTryEmbedding(mtbFile) ||
- consentProcessor == null
+ if (!isConsentOk) {
+ logger.warn("consent check failed but will be sent to DNPM:DIP!")
+ }
- if (!isConsentOk) {
- logger.warn("consent check failed but will be sent to DNPM:DIP!")
+ try {
+ mtbFile addGenomDeTan pseudonymizeService
+ mtbFile pseudonymizeWith pseudonymizeService
+ mtbFile anonymizeContentWith pseudonymizeService
+ val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
+ saveAndSend(request)
+ } catch (e: Exception) {
+ logger.error("Error while processing MtbFile", e)
+ requestService.save(
+ Request(
+ null,
+ requestId,
+ PatientPseudonym("INVALID"),
+ emptyPatientId(),
+ fingerprint(""),
+ RequestType.MTB_FILE,
+ SubmissionType.UNKNOWN,
+ RequestStatus.ERROR,
+ Tan.empty(),
+ report = Report("Fehlerhafte Eingangsdaten. Keine Verarbeitung oder Weiterleitung."),
+ )
+ )
+ return false
+ }
+ return true
}
- mtbFile addGenomDeTan pseudonymizeService
- mtbFile pseudonymizeWith pseudonymizeService
- mtbFile anonymizeContentWith pseudonymizeService
- val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
- saveAndSend(request)
- }
-
- private fun <T> saveAndSend(request: MtbFileRequest<T>) {
- var submissionType: SubmissionType =
- when (request) {
- is DnpmV2MtbFileRequest -> {
- when (request.content.metadata?.type) {
- MvhSubmissionType.TEST -> SubmissionType.TEST
- MvhSubmissionType.INITIAL -> SubmissionType.INITIAL
- MvhSubmissionType.ADDITION -> SubmissionType.ADDITION
- MvhSubmissionType.CORRECTION -> SubmissionType.CORRECTION
- MvhSubmissionType.FOLLOWUP -> SubmissionType.FOLLOWUP
- else -> SubmissionType.UNKNOWN
+ private fun <T> saveAndSend(request: MtbFileRequest<T>) {
+ var submissionType: SubmissionType =
+ when (request) {
+ is DnpmV2MtbFileRequest -> {
+ when (request.content.metadata?.type) {
+ MvhSubmissionType.TEST -> SubmissionType.TEST
+ MvhSubmissionType.INITIAL -> SubmissionType.INITIAL
+ MvhSubmissionType.ADDITION -> SubmissionType.ADDITION
+ MvhSubmissionType.CORRECTION -> SubmissionType.CORRECTION
+ MvhSubmissionType.FOLLOWUP -> SubmissionType.FOLLOWUP
+ else -> SubmissionType.UNKNOWN
+ }
+ }
}
- }
- }
- if (
- appConfigProperties.postInitialSubmissionBlock &&
+ if (
+ appConfigProperties.postInitialSubmissionBlock &&
hasSuccessfullInitialSubmission(request.patientPseudonym()) &&
hasUnacceptedInitialSubmission(request.patientPseudonym())
- ) {
- requestService.save(
- Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.BLOCKED_INITIAL,
- Tan(request.content.metadata?.transferTan.orEmpty())
- )
- )
- // Exit - no further processing
- return
- }
+ ) {
+ requestService.save(
+ Request(
+ request.requestId,
+ request.patientPseudonym(),
+ emptyPatientId(),
+ fingerprint(request),
+ RequestType.MTB_FILE,
+ submissionType,
+ RequestStatus.BLOCKED_INITIAL,
+ Tan(request.content.metadata?.transferTan.orEmpty())
+ )
+ )
+ // Exit - no further processing
+ return
+ }
- if (
- appConfigProperties.postInitialSubmissionBlock &&
+ if (
+ appConfigProperties.postInitialSubmissionBlock &&
hasSuccessfullInitialSubmission(request.patientPseudonym()) &&
!hasUnacceptedInitialSubmission(request.patientPseudonym())
- ) {
- // Use "addition" after "intial" with "Meldebestaetigung"
- request.content.metadata?.let {
- logger.warn("Override submission type using 'addition' after first initial submission!")
- it.type = MvhSubmissionType.ADDITION
- submissionType = SubmissionType.ADDITION
- }
- }
+ ) {
+ // Use "addition" after "intial" with "Meldebestaetigung"
+ request.content.metadata?.let {
+ logger.warn("Override submission type using 'addition' after first initial submission!")
+ it.type = MvhSubmissionType.ADDITION
+ submissionType = SubmissionType.ADDITION
+ }
+ }
- requestService.save(
- Request(
- request.requestId,
- request.patientPseudonym(),
- emptyPatientId(),
- fingerprint(request),
- RequestType.MTB_FILE,
- submissionType,
- RequestStatus.UNKNOWN,
- Tan(request.content.metadata?.transferTan.orEmpty()),
+ requestService.save(
+ Request(
+ request.requestId,
+ request.patientPseudonym(),
+ emptyPatientId(),
+ fingerprint(request),
+ RequestType.MTB_FILE,
+ submissionType,
+ RequestStatus.UNKNOWN,
+ Tan(request.content.metadata?.transferTan.orEmpty()),
+ )
)
- )
- if (appConfigProperties.duplicationDetection && isDuplication(request)) {
- applicationEventPublisher.publishEvent(
- ResponseEvent(request.requestId, Instant.now(), RequestStatus.DUPLICATION)
- )
- return
- }
+ if (appConfigProperties.duplicationDetection && isDuplication(request)) {
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(request.requestId, Instant.now(), RequestStatus.DUPLICATION)
+ )
+ return
+ }
- val responseStatus = sender.send(request)
-
- applicationEventPublisher.publishEvent(
- ResponseEvent(
- request.requestId,
- Instant.now(),
- responseStatus.status,
- when (responseStatus.status) {
- RequestStatus.ERROR,
- RequestStatus.WARNING -> Optional.of(responseStatus.body)
- else -> Optional.empty()
- },
- )
- )
- }
+ val responseStatus = sender.send(request)
- private fun hasSuccessfullInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
- return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
- it.submissionType == SubmissionType.INITIAL &&
- (it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING)
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(
+ request.requestId,
+ Instant.now(),
+ responseStatus.status,
+ when (responseStatus.status) {
+ RequestStatus.ERROR,
+ RequestStatus.WARNING -> Optional.of(responseStatus.body)
+
+ else -> Optional.empty()
+ },
+ )
+ )
}
- }
- private fun hasUnacceptedInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
- return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
- it.submissionType == SubmissionType.INITIAL &&
- !(it.submissionAccepted || it.status == RequestStatus.BLOCKED_INITIAL)
+ private fun hasSuccessfullInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
+ return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
+ it.submissionType == SubmissionType.INITIAL &&
+ (it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING)
+ }
}
- }
- private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
- val patientPseudonym =
- when (pseudonymizedMtbFileRequest) {
- is DnpmV2MtbFileRequest ->
- PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
+ private fun hasUnacceptedInitialSubmission(patientPseudonym: PatientPseudonym): Boolean {
+ return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any {
+ it.submissionType == SubmissionType.INITIAL &&
+ !(it.submissionAccepted || it.status == RequestStatus.BLOCKED_INITIAL)
}
+ }
- val lastMtbFileRequestForPatient =
- requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
- val isLastRequestDeletion =
- requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
-
- return null != lastMtbFileRequestForPatient &&
- !isLastRequestDeletion &&
- lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
- }
-
- fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
- processDeletion(patientId, randomRequestId(), isConsented)
- }
-
- fun processDeletion(patientId: PatientId, requestId: RequestId, isConsented: TtpConsentStatus) {
- try {
- val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
-
- val requestStatus: RequestStatus =
- when (isConsented) {
- TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
- TtpConsentStatus.BROAD_CONSENT_MISSING,
- TtpConsentStatus.BROAD_CONSENT_REJECTED -> RequestStatus.NO_CONSENT
- TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR
- TtpConsentStatus.BROAD_CONSENT_GIVEN,
- TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN
- TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
- TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
- TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED -> {
- throw RuntimeException(
- "processDelete should never deal with '" +
- isConsented.name +
- "' consent status. This is a bug and need to be fixed!"
- )
+ private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
+ val patientPseudonym =
+ when (pseudonymizedMtbFileRequest) {
+ is DnpmV2MtbFileRequest ->
+ PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
}
- }
-
- requestService.save(
- Request(
- requestId,
- patientPseudonym,
- emptyPatientId(),
- fingerprint(patientPseudonym.value),
- RequestType.DELETE,
- SubmissionType.UNKNOWN,
- requestStatus,
- Tan.empty()
- )
- )
-
- val responseStatus = sender.send(DeleteRequest(requestId, patientPseudonym))
-
- applicationEventPublisher.publishEvent(
- ResponseEvent(
- requestId,
- Instant.now(),
- responseStatus.status,
- when (responseStatus.status) {
- RequestStatus.WARNING,
- RequestStatus.ERROR -> Optional.of(responseStatus.body)
- else -> Optional.empty()
- },
- )
- )
- } catch (_: Exception) {
- requestService.save(
- Request(
- uuid = requestId,
- patientPseudonym = emptyPatientPseudonym(),
- pid = patientId,
- fingerprint = Fingerprint.empty(),
- status = RequestStatus.ERROR,
- type = RequestType.DELETE,
- submissionType = SubmissionType.UNKNOWN,
- report = Report("Fehler bei der Pseudonymisierung"),
- tan = Tan.empty(),
- )
- )
+
+ val lastMtbFileRequestForPatient =
+ requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
+ val isLastRequestDeletion =
+ requestService.isLastRequestWithKnownStatusDeletion(patientPseudonym)
+
+ return null != lastMtbFileRequestForPatient &&
+ !isLastRequestDeletion &&
+ lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
}
- }
- private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
- return when (request) {
- is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
+ fun processDeletion(patientId: PatientId, isConsented: TtpConsentStatus) {
+ processDeletion(patientId, randomRequestId(), isConsented)
}
- }
- private fun fingerprint(s: String): Fingerprint {
- return Fingerprint(Base32().encodeAsString(DigestUtils.sha256(s))
- .replace("=", "")
- .lowercase())
- }
+ fun processDeletion(patientId: PatientId, requestId: RequestId, isConsented: TtpConsentStatus) {
+ try {
+ val patientPseudonym = pseudonymizeService.patientPseudonym(patientId)
+
+ val requestStatus: RequestStatus =
+ when (isConsented) {
+ TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
+ TtpConsentStatus.BROAD_CONSENT_MISSING,
+ TtpConsentStatus.BROAD_CONSENT_REJECTED -> RequestStatus.NO_CONSENT
+
+ TtpConsentStatus.FAILED_TO_ASK -> RequestStatus.ERROR
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ TtpConsentStatus.UNKNOWN_CHECK_FILE -> RequestStatus.UNKNOWN
+
+ TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
+ TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
+ TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED -> {
+ throw RuntimeException(
+ "processDelete should never deal with '" +
+ isConsented.name +
+ "' consent status. This is a bug and need to be fixed!"
+ )
+ }
+ }
+
+ requestService.save(
+ Request(
+ requestId,
+ patientPseudonym,
+ emptyPatientId(),
+ fingerprint(patientPseudonym.value),
+ RequestType.DELETE,
+ SubmissionType.UNKNOWN,
+ requestStatus,
+ Tan.empty()
+ )
+ )
+
+ val responseStatus = sender.send(DeleteRequest(requestId, patientPseudonym))
+
+ applicationEventPublisher.publishEvent(
+ ResponseEvent(
+ requestId,
+ Instant.now(),
+ responseStatus.status,
+ when (responseStatus.status) {
+ RequestStatus.WARNING,
+ RequestStatus.ERROR -> Optional.of(responseStatus.body)
+
+ else -> Optional.empty()
+ },
+ )
+ )
+ } catch (_: Exception) {
+ requestService.save(
+ Request(
+ uuid = requestId,
+ patientPseudonym = emptyPatientPseudonym(),
+ pid = patientId,
+ fingerprint = Fingerprint.empty(),
+ status = RequestStatus.ERROR,
+ type = RequestType.DELETE,
+ submissionType = SubmissionType.UNKNOWN,
+ report = Report("Fehler bei der Pseudonymisierung"),
+ tan = Tan.empty(),
+ )
+ )
+ }
+ }
+
+ private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
+ return when (request) {
+ is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
+ }
+ }
+
+ private fun fingerprint(s: String): Fingerprint {
+ return Fingerprint(
+ Base32().encodeAsString(DigestUtils.sha256(s))
+ .replace("=", "")
+ .lowercase()
+ )
+ }
}
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<Mtb>()) }.thenReturn(true)
- verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
- }
+ 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<Mtb>())
+ }
- }
+ @ParameterizedTest
+ @ArgumentsSource(Dnpm21MtbFile::class)
+ fun shouldProcessPostRequest(mtb: Mtb) {
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.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<Mtb>())
- }
+ whenever { requestProcessor.processMtbFile(any<Mtb>()) }.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<Mtb>())
+
+ 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<Mtb>())
+ this.requestProcessor.processMtbFile(mtbFile)
- whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true)
+ val requestCaptor = argumentCaptor<Request>()
+ 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<Request>()
- 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<Mtb>())
+
+ 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<ResponseEvent>()
+ 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<Mtb>())
+ @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<DnpmV2MtbFileRequest>())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+
+ 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<ResponseEvent>()
+ 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<DnpmV2MtbFileRequest>())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" }
+ .whenever(pseudonymizeService)
+ .genomDeTan(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+
+ 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<ResponseEvent>()
+ 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<ResponseEvent>()
- 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<DnpmV2MtbFileRequest>())
+ 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<DnpmV2MtbFileRequest>())
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+ 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<Mtb>())
+
+ 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<ResponseEvent>()
- 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<DnpmV2MtbFileRequest>())
-
- doAnswer { it.arguments[0] as String }
- .whenever(pseudonymizeService)
- .patientPseudonym(anyValueClass())
-
- doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" }
- .whenever(pseudonymizeService)
- .genomDeTan(anyValueClass())
-
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
-
- 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<ResponseEvent>()
- verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
- assertThat(eventCaptor.firstValue).isNotNull
- assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR)
- }
+ val requestCaptor = argumentCaptor<DnpmV2MtbFileRequest>()
+ 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<ResponseEvent>()
+ 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<DeleteRequest>())
+
+ this.requestProcessor.processDeletion(
+ TEST_PATIENT_ID,
+ isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE,
)
- doAnswer { lastRequests }
- .whenever(requestService)
- .allRequestsByPatientPseudonym(anyValueClass())
+ val requestCaptor = argumentCaptor<Request>()
+ 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<DnpmV2MtbFileRequest>())
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DeleteRequest>())
- 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<ResponseEvent>()
+ 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<Mtb>())
+ @Test
+ fun testShouldSendRequestWithoutConsent() {
+ doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ doAnswer { it.arguments.first() }
+ .whenever(transformationService)
+ .transform(any<Mtb>())
+
+ 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<ResponseEvent>()
+ 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<DeleteRequest>())
- 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<DnpmV2MtbFileRequest>()
- 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<ResponseEvent>()
- 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<DeleteRequest>())
-
- this.requestProcessor.processDeletion(
- TEST_PATIENT_ID,
- isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE,
- )
-
- val requestCaptor = argumentCaptor<Request>()
- 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<DeleteRequest>())
-
- this.requestProcessor.processDeletion(
- TEST_PATIENT_ID,
- isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE,
- )
-
- val eventCaptor = argumentCaptor<ResponseEvent>()
- verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
- assertThat(eventCaptor.firstValue).isNotNull
- assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
- }
+ val eventCaptor = argumentCaptor<ResponseEvent>()
+ 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<DnpmV2MtbFileRequest>())
-
- doAnswer { it.arguments.first() }
- .whenever(transformationService)
- .transform(any<Mtb>())
-
- 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<Request>()
+ 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<Mtb>())
+
+ doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
+ .whenever(sender)
+ .send(any<DnpmV2MtbFileRequest>())
+
+ 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<ResponseEvent>()
+ 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<DnpmV2MtbFileRequest>())
+
+ doAnswer { it.arguments[0] as String }
+ .whenever(pseudonymizeService)
+ .patientPseudonym(anyValueClass())
+
+ doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" }
+ .whenever(pseudonymizeService)
+ .genomDeTan(anyValueClass())
+
+ doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
+
+ 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<ResponseEvent>()
- 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<DeleteRequest>())
-
- this.requestProcessor.processDeletion(
- TEST_PATIENT_ID,
- isConsented = TtpConsentStatus.UNKNOWN_CHECK_FILE,
- )
-
- val eventCaptor = argumentCaptor<ResponseEvent>()
- 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<Request>()
- 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<Mtb>())
-
- doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) }
- .whenever(sender)
- .send(any<DnpmV2MtbFileRequest>())
-
- 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<ResponseEvent>()
- verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture())
- assertThat(eventCaptor.firstValue).isNotNull
- assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS)
- }
+ val requestCaptor = argumentCaptor<Request>()
+ 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<DnpmV2MtbFileRequest>())
-
- doAnswer { it.arguments[0] as String }
- .whenever(pseudonymizeService)
- .patientPseudonym(anyValueClass())
-
- doAnswer { "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2" }
- .whenever(pseudonymizeService)
- .genomDeTan(anyValueClass())
-
- doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>())
-
- 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<Request>()
- 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<Mtb>())
- 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<Request>()
+ 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<DnpmV2MtbFileRequest>())
+ }
}
@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<Mtb>())
-
- 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<Request>()
- 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<DnpmV2MtbFileRequest>())
+ fun shouldCatchExceptionsWhenProcessingMtbFileAndSaveError() {
+ val invalidMtbFile = Mtb.builder().build()
+
+ val success = this.requestProcessor.processMtbFile(invalidMtbFile)
+
+ assertThat(success).isFalse()
+
+ verify(sender, times(0)).send(any<DnpmV2MtbFileRequest>())
+
+ val requestCaptor = argumentCaptor<Request>()
+ 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")
+ }
}