1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-04-19 17:26:51 +00:00

feat: support multiple request content types (#109)

This commit is contained in:
Paul-Christian Volkmer 2025-04-06 22:17:46 +02:00 committed by GitHub
parent 8e3de6a220
commit c6b37fda69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 643 additions and 331 deletions

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.monitoring.RequestRepository import dev.dnpm.etl.processor.monitoring.RequestRepository
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.output.BwhcV1MtbFileRequest
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.MtbFileSender
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
@ -91,7 +92,7 @@ class EtlProcessorApplicationTests : AbstractTestcontainerTest() {
fun mtbFileIsTransformed() { fun mtbFileIsTransformed() {
doAnswer { doAnswer {
MtbFileSender.Response(RequestStatus.SUCCESS) MtbFileSender.Response(RequestStatus.SUCCESS)
}.whenever(mtbFileSender).send(any<MtbFileSender.MtbFileRequest>()) }.whenever(mtbFileSender).send(any<BwhcV1MtbFileRequest>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(
@ -134,9 +135,9 @@ class EtlProcessorApplicationTests : AbstractTestcontainerTest() {
} }
} }
val captor = argumentCaptor<MtbFileSender.MtbFileRequest>() val captor = argumentCaptor<BwhcV1MtbFileRequest>()
verify(mtbFileSender).send(captor.capture()) verify(mtbFileSender).send(captor.capture())
assertThat(captor.firstValue.mtbFile.diagnoses).hasSize(1).allMatch { diagnosis -> assertThat(captor.firstValue.content.diagnoses).hasSize(1).allMatch { diagnosis ->
diagnosis.icd10.version == "2014" diagnosis.icd10.version == "2014"
} }
} }

View File

@ -91,7 +91,7 @@ class MtbFileRestControllerTest {
status { isAccepted() } status { isAccepted() }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -104,7 +104,7 @@ class MtbFileRestControllerTest {
status { isAccepted() } status { isAccepted() }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -117,7 +117,7 @@ class MtbFileRestControllerTest {
status { isUnauthorized() } status { isUnauthorized() }
} }
verify(requestProcessor, never()).processMtbFile(any()) verify(requestProcessor, never()).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -130,7 +130,7 @@ class MtbFileRestControllerTest {
status { isForbidden() } status { isForbidden() }
} }
verify(requestProcessor, never()).processMtbFile(any()) verify(requestProcessor, never()).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -177,7 +177,7 @@ class MtbFileRestControllerTest {
status { isAccepted() } status { isAccepted() }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -190,7 +190,7 @@ class MtbFileRestControllerTest {
status { isAccepted() } status { isAccepted() }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
} }

View File

@ -26,7 +26,6 @@ import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import dev.pcvolkmer.mv64e.mtb.Mtb import dev.pcvolkmer.mv64e.mtb.Mtb
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@ -47,10 +46,10 @@ class MtbFileRestController(
@PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] ) @PostMapping( consumes = [ MediaType.APPLICATION_JSON_VALUE ] )
fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity<Unit> { fun mtbFile(@RequestBody mtbFile: MtbFile): ResponseEntity<Unit> {
if (mtbFile.consent.status == Consent.Status.ACTIVE) { if (mtbFile.consent.status == Consent.Status.ACTIVE) {
logger.debug("Accepted MTB File for processing") logger.debug("Accepted MTB File (bwHC V1) for processing")
requestProcessor.processMtbFile(mtbFile) requestProcessor.processMtbFile(mtbFile)
} else { } else {
logger.debug("Accepted MTB File and process deletion") logger.debug("Accepted MTB File (bwHC V1) and process deletion")
val patientId = PatientId(mtbFile.patient.id) val patientId = PatientId(mtbFile.patient.id)
requestProcessor.processDeletion(patientId) requestProcessor.processDeletion(patientId)
} }
@ -59,7 +58,9 @@ class MtbFileRestController(
@PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] ) @PostMapping( consumes = [ CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE] )
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> { fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build() logger.debug("Accepted MTB File (DNPM V2) for processing")
requestProcessor.processMtbFile(mtbFile)
return ResponseEntity.accepted().build()
} }
@DeleteMapping(path = ["{patientId}"]) @DeleteMapping(path = ["{patientId}"])

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -22,10 +22,12 @@ package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.Consent import de.ukw.ccc.bwhc.dto.Consent
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.config.KafkaProperties import dev.dnpm.etl.processor.config.KafkaProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import org.apache.kafka.clients.producer.ProducerRecord
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.core.KafkaTemplate
import org.springframework.retry.support.RetryTemplate import org.springframework.retry.support.RetryTemplate
@ -38,14 +40,20 @@ class KafkaMtbFileSender(
private val logger = LoggerFactory.getLogger(KafkaMtbFileSender::class.java) private val logger = LoggerFactory.getLogger(KafkaMtbFileSender::class.java)
override fun send(request: MtbFileSender.MtbFileRequest): MtbFileSender.Response { override fun <T> send(request: MtbFileRequest<T>): MtbFileSender.Response {
return try { return try {
return retryTemplate.execute<MtbFileSender.Response, Exception> { return retryTemplate.execute<MtbFileSender.Response, Exception> {
val result = kafkaTemplate.send( val record =
kafkaProperties.outputTopic, ProducerRecord(kafkaProperties.outputTopic, key(request), objectMapper.writeValueAsString(request))
key(request), when (request) {
objectMapper.writeValueAsString(Data(request.requestId, request.mtbFile)) is BwhcV1MtbFileRequest -> record.headers()
) .add("contentType", MediaType.APPLICATION_JSON_VALUE.toByteArray())
is DnpmV2MtbFileRequest -> record.headers()
.add("contentType", CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray())
}
val result = kafkaTemplate.send(record)
if (result.get() != null) { if (result.get() != null) {
logger.debug("Sent file via KafkaMtbFileSender") logger.debug("Sent file via KafkaMtbFileSender")
MtbFileSender.Response(RequestStatus.UNKNOWN) MtbFileSender.Response(RequestStatus.UNKNOWN)
@ -59,7 +67,7 @@ class KafkaMtbFileSender(
} }
} }
override fun send(request: MtbFileSender.DeleteRequest): MtbFileSender.Response { override fun send(request: DeleteRequest): MtbFileSender.Response {
val dummyMtbFile = MtbFile.builder() val dummyMtbFile = MtbFile.builder()
.withConsent( .withConsent(
Consent.builder() Consent.builder()
@ -71,12 +79,15 @@ class KafkaMtbFileSender(
return try { return try {
return retryTemplate.execute<MtbFileSender.Response, Exception> { return retryTemplate.execute<MtbFileSender.Response, Exception> {
val result = kafkaTemplate.send( val record =
ProducerRecord(
kafkaProperties.outputTopic, kafkaProperties.outputTopic,
key(request), key(request),
objectMapper.writeValueAsString(Data(request.requestId, dummyMtbFile)) // Always use old BwhcV1FileRequest with Consent REJECT
objectMapper.writeValueAsString(BwhcV1MtbFileRequest(request.requestId, dummyMtbFile))
) )
val result = kafkaTemplate.send(record)
if (result.get() != null) { if (result.get() != null) {
logger.debug("Sent deletion request via KafkaMtbFileSender") logger.debug("Sent deletion request via KafkaMtbFileSender")
MtbFileSender.Response(RequestStatus.UNKNOWN) MtbFileSender.Response(RequestStatus.UNKNOWN)
@ -94,13 +105,12 @@ class KafkaMtbFileSender(
return "${this.kafkaProperties.servers} (${this.kafkaProperties.outputTopic}/${this.kafkaProperties.outputResponseTopic})" return "${this.kafkaProperties.servers} (${this.kafkaProperties.outputTopic}/${this.kafkaProperties.outputResponseTopic})"
} }
private fun key(request: MtbFileSender.MtbFileRequest): String { private fun key(request: MtbRequest): String {
return "{\"pid\": \"${request.mtbFile.patient.id}\"}" return when (request) {
is BwhcV1MtbFileRequest -> "{\"pid\": \"${request.content.patient.id}\"}"
is DnpmV2MtbFileRequest -> "{\"pid\": \"${request.content.patient.id}\"}"
is DeleteRequest -> "{\"pid\": \"${request.patientId.value}\"}"
else -> throw IllegalArgumentException("Unsupported request type: ${request::class.simpleName}")
} }
private fun key(request: MtbFileSender.DeleteRequest): String {
return "{\"pid\": \"${request.patientId.value}\"}"
} }
data class Data(val requestId: RequestId, val content: MtbFile)
} }

View File

@ -19,25 +19,17 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import org.springframework.http.HttpStatusCode import org.springframework.http.HttpStatusCode
interface MtbFileSender { interface MtbFileSender {
fun send(request: MtbFileRequest): Response fun <T> send(request: MtbFileRequest<T>): Response
fun send(request: DeleteRequest): Response fun send(request: DeleteRequest): Response
fun endpoint(): String fun endpoint(): String
data class Response(val status: RequestStatus, val body: String = "") data class Response(val status: RequestStatus, val body: String = "")
data class MtbFileRequest(val requestId: RequestId, val mtbFile: MtbFile)
data class DeleteRequest(val requestId: RequestId, val patientId: PatientPseudonym)
} }
fun Int.asRequestStatus(): RequestStatus { fun Int.asRequestStatus(): RequestStatus {

View File

@ -0,0 +1,59 @@
/*
* This file is part of ETL-Processor
*
* Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package dev.dnpm.etl.processor.output
import de.ukw.ccc.bwhc.dto.MtbFile
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId
import dev.pcvolkmer.mv64e.mtb.Mtb
interface MtbRequest {
val requestId: RequestId
}
sealed interface MtbFileRequest<out T> : MtbRequest {
override val requestId: RequestId
val content: T
fun patientPseudonym(): PatientPseudonym
}
data class BwhcV1MtbFileRequest(
override val requestId: RequestId,
override val content: MtbFile
) : MtbFileRequest<MtbFile> {
override fun patientPseudonym(): PatientPseudonym {
return PatientPseudonym(content.patient.id)
}
}
data class DnpmV2MtbFileRequest(
override val requestId: RequestId,
override val content: Mtb
) : MtbFileRequest<Mtb> {
override fun patientPseudonym(): PatientPseudonym {
return PatientPseudonym(content.patient.id)
}
}
data class DeleteRequest(
override val requestId: RequestId,
val patientId: PatientPseudonym
) : MtbRequest

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -19,10 +19,11 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.asRequestStatus import dev.dnpm.etl.processor.monitoring.asRequestStatus
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.HttpEntity import org.springframework.http.HttpEntity
@ -46,11 +47,11 @@ abstract class RestMtbFileSender(
abstract fun deleteUrl(patientId: PatientPseudonym): String abstract fun deleteUrl(patientId: PatientPseudonym): String
override fun send(request: MtbFileSender.MtbFileRequest): MtbFileSender.Response { override fun <T> send(request: MtbFileRequest<T>): MtbFileSender.Response {
try { try {
return retryTemplate.execute<MtbFileSender.Response, Exception> { return retryTemplate.execute<MtbFileSender.Response, Exception> {
val headers = getHttpHeaders() val headers = getHttpHeaders(request)
val entityReq = HttpEntity(request.mtbFile, headers) val entityReq = HttpEntity(request.content, headers)
val response = restTemplate.postForEntity( val response = restTemplate.postForEntity(
sendUrl(), sendUrl(),
entityReq, entityReq,
@ -76,10 +77,10 @@ abstract class RestMtbFileSender(
return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung") return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung")
} }
override fun send(request: MtbFileSender.DeleteRequest): MtbFileSender.Response { override fun send(request: DeleteRequest): MtbFileSender.Response {
try { try {
return retryTemplate.execute<MtbFileSender.Response, Exception> { return retryTemplate.execute<MtbFileSender.Response, Exception> {
val headers = getHttpHeaders() val headers = getHttpHeaders(request)
val entityReq = HttpEntity(null, headers) val entityReq = HttpEntity(null, headers)
restTemplate.delete( restTemplate.delete(
deleteUrl(request.patientId), deleteUrl(request.patientId),
@ -102,11 +103,15 @@ abstract class RestMtbFileSender(
return this.restTargetProperties.uri.orEmpty() return this.restTargetProperties.uri.orEmpty()
} }
private fun getHttpHeaders(): HttpHeaders { private fun getHttpHeaders(request: MtbRequest): HttpHeaders {
val username = restTargetProperties.username val username = restTargetProperties.username
val password = restTargetProperties.password val password = restTargetProperties.password
val headers = HttpHeaders() val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON headers.contentType = when (request) {
is BwhcV1MtbFileRequest -> MediaType.APPLICATION_JSON
is DnpmV2MtbFileRequest -> CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON
else -> MediaType.APPLICATION_JSON
}
if (username.isNullOrBlank() || password.isNullOrBlank()) { if (username.isNullOrBlank() || password.isNullOrBlank()) {
return headers return headers

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -27,10 +27,11 @@ import dev.dnpm.etl.processor.monitoring.Report
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.RequestType import dev.dnpm.etl.processor.monitoring.RequestType
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.*
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith import dev.dnpm.etl.processor.pseudonym.anonymizeContentWith
import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith
import dev.pcvolkmer.mv64e.mtb.Mtb
import org.apache.commons.codec.binary.Base32 import org.apache.commons.codec.binary.Base32
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
@ -55,29 +56,40 @@ class RequestProcessor(
fun processMtbFile(mtbFile: MtbFile, requestId: RequestId) { fun processMtbFile(mtbFile: MtbFile, requestId: RequestId) {
val pid = PatientId(mtbFile.patient.id) val pid = PatientId(mtbFile.patient.id)
mtbFile pseudonymizeWith pseudonymizeService mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService mtbFile anonymizeContentWith pseudonymizeService
val request = BwhcV1MtbFileRequest(requestId, transformationService.transform(mtbFile))
saveAndSend(request, pid)
}
val request = MtbFileSender.MtbFileRequest(requestId, transformationService.transform(mtbFile)) fun processMtbFile(mtbFile: Mtb) {
processMtbFile(mtbFile, randomRequestId())
}
val patientPseudonym = PatientPseudonym(request.mtbFile.patient.id) fun processMtbFile(mtbFile: Mtb, requestId: RequestId) {
val pid = PatientId(mtbFile.patient.id)
mtbFile pseudonymizeWith pseudonymizeService
mtbFile anonymizeContentWith pseudonymizeService
val request = DnpmV2MtbFileRequest(requestId, transformationService.transform(mtbFile))
saveAndSend(request, pid)
}
private fun <T> saveAndSend(request: MtbFileRequest<T>, pid: PatientId) {
requestService.save( requestService.save(
Request( Request(
requestId, request.requestId,
patientPseudonym, request.patientPseudonym(),
pid, pid,
fingerprint(request.mtbFile), fingerprint(request),
RequestType.MTB_FILE, RequestType.MTB_FILE,
RequestStatus.UNKNOWN RequestStatus.UNKNOWN
) )
) )
if (appConfigProperties.duplicationDetection && isDuplication(mtbFile)) { if (appConfigProperties.duplicationDetection && isDuplication(request)) {
applicationEventPublisher.publishEvent( applicationEventPublisher.publishEvent(
ResponseEvent( ResponseEvent(
requestId, request.requestId,
Instant.now(), Instant.now(),
RequestStatus.DUPLICATION RequestStatus.DUPLICATION
) )
@ -89,7 +101,7 @@ class RequestProcessor(
applicationEventPublisher.publishEvent( applicationEventPublisher.publishEvent(
ResponseEvent( ResponseEvent(
requestId, request.requestId,
Instant.now(), Instant.now(),
responseStatus.status, responseStatus.status,
when (responseStatus.status) { when (responseStatus.status) {
@ -100,8 +112,11 @@ class RequestProcessor(
) )
} }
private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean { private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean {
val patientPseudonym = PatientPseudonym(pseudonymizedMtbFile.patient.id) val patientPseudonym = when (pseudonymizedMtbFileRequest) {
is BwhcV1MtbFileRequest -> PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
is DnpmV2MtbFileRequest -> PatientPseudonym(pseudonymizedMtbFileRequest.content.patient.id)
}
val lastMtbFileRequestForPatient = val lastMtbFileRequestForPatient =
requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym) requestService.lastMtbFileRequestForPatientPseudonym(patientPseudonym)
@ -109,7 +124,7 @@ class RequestProcessor(
return null != lastMtbFileRequestForPatient return null != lastMtbFileRequestForPatient
&& !isLastRequestDeletion && !isLastRequestDeletion
&& lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFile) && lastMtbFileRequestForPatient.fingerprint == fingerprint(pseudonymizedMtbFileRequest)
} }
fun processDeletion(patientId: PatientId) { fun processDeletion(patientId: PatientId) {
@ -131,7 +146,7 @@ class RequestProcessor(
) )
) )
val responseStatus = sender.send(MtbFileSender.DeleteRequest(requestId, patientPseudonym)) val responseStatus = sender.send(DeleteRequest(requestId, patientPseudonym))
applicationEventPublisher.publishEvent( applicationEventPublisher.publishEvent(
ResponseEvent( ResponseEvent(
@ -160,8 +175,11 @@ class RequestProcessor(
} }
} }
private fun fingerprint(mtbFile: MtbFile): Fingerprint { private fun <T> fingerprint(request: MtbFileRequest<T>): Fingerprint {
return fingerprint(objectMapper.writeValueAsString(mtbFile)) return when (request) {
is BwhcV1MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
is DnpmV2MtbFileRequest -> fingerprint(objectMapper.writeValueAsString(request.content))
}
} }
private fun fingerprint(s: String): Fingerprint { private fun fingerprint(s: String): Fingerprint {

View File

@ -23,10 +23,21 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException import com.jayway.jsonpath.PathNotFoundException
import de.ukw.ccc.bwhc.dto.MtbFile import de.ukw.ccc.bwhc.dto.MtbFile
import dev.pcvolkmer.mv64e.mtb.Mtb
class TransformationService(private val objectMapper: ObjectMapper, private val transformations: List<Transformation>) { class TransformationService(private val objectMapper: ObjectMapper, private val transformations: List<Transformation>) {
fun transform(mtbFile: MtbFile): MtbFile { fun transform(mtbFile: MtbFile): MtbFile {
var json = objectMapper.writeValueAsString(mtbFile) val json = transform(objectMapper.writeValueAsString(mtbFile))
return objectMapper.readValue(json, MtbFile::class.java)
}
fun transform(mtbFile: Mtb): Mtb {
val json = transform(objectMapper.writeValueAsString(mtbFile))
return objectMapper.readValue(json, Mtb::class.java)
}
private fun transform(content: String): String {
var json = content
transformations.forEach { transformation -> transformations.forEach { transformation ->
val jsonPath = JsonPath.parse(json) val jsonPath = JsonPath.parse(json)
@ -48,7 +59,7 @@ class TransformationService(private val objectMapper: ObjectMapper, private val
json = jsonPath.jsonString() json = jsonPath.jsonString()
} }
return objectMapper.readValue(json, MtbFile::class.java) return json
} }
fun getTransformations(): List<Transformation> { fun getTransformations(): List<Transformation> {

View File

@ -74,7 +74,7 @@ class KafkaInputListenerTest {
) )
) )
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -121,7 +121,7 @@ class KafkaInputListenerTest {
) )
) )
verify(requestProcessor, times(1)).processMtbFile(any(), anyValueClass()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>(), anyValueClass())
} }
@Test @Test

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import dev.pcvolkmer.mv64e.mtb.Mtb
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -72,7 +73,7 @@ class MtbFileRestControllerTest {
} }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -128,7 +129,7 @@ class MtbFileRestControllerTest {
} }
} }
verify(requestProcessor, times(1)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<MtbFile>())
} }
@Test @Test
@ -182,11 +183,11 @@ class MtbFileRestControllerTest {
contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON
}.andExpect { }.andExpect {
status { status {
isNotImplemented() isAccepted()
} }
} }
verify(requestProcessor, times(0)).processMtbFile(any()) verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -21,20 +21,25 @@ package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import de.ukw.ccc.bwhc.dto.Patient
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.KafkaProperties import dev.dnpm.etl.processor.config.KafkaProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.pcvolkmer.mv64e.mtb.*
import org.apache.kafka.clients.producer.ProducerRecord
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.* import org.mockito.kotlin.*
import org.springframework.http.MediaType
import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.support.SendResult import org.springframework.kafka.support.SendResult
import org.springframework.retry.policy.SimpleRetryPolicy import org.springframework.retry.policy.SimpleRetryPolicy
@ -45,6 +50,9 @@ import java.util.concurrent.ExecutionException
@ExtendWith(MockitoExtension::class) @ExtendWith(MockitoExtension::class)
class KafkaMtbFileSenderTest { class KafkaMtbFileSenderTest {
@Nested
inner class BwhcV1Record {
private lateinit var kafkaTemplate: KafkaTemplate<String, String> private lateinit var kafkaTemplate: KafkaTemplate<String, String>
private lateinit var kafkaMtbFileSender: KafkaMtbFileSender private lateinit var kafkaMtbFileSender: KafkaMtbFileSender
@ -65,67 +73,69 @@ class KafkaMtbFileSenderTest {
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("requestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldSendMtbFileRequestAndReturnExpectedState(testData: TestData) { fun shouldSendMtbFileRequestAndReturnExpectedState(testData: TestData) {
doAnswer { doAnswer {
if (null != testData.exception) { if (null != testData.exception) {
throw testData.exception throw testData.exception
} }
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
val response = kafkaMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile(Consent.Status.ACTIVE))) val response = kafkaMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, bwhcV1MtbFile(Consent.Status.ACTIVE)))
assertThat(response.status).isEqualTo(testData.requestStatus) assertThat(response.status).isEqualTo(testData.requestStatus)
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("requestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldSendDeleteRequestAndReturnExpectedState(testData: TestData) { fun shouldSendDeleteRequestAndReturnExpectedState(testData: TestData) {
doAnswer { doAnswer {
if (null != testData.exception) { if (null != testData.exception) {
throw testData.exception throw testData.exception
} }
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
val response = kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) val response = kafkaMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(testData.requestStatus) assertThat(response.status).isEqualTo(testData.requestStatus)
} }
@Test @Test
fun shouldSendMtbFileRequestWithCorrectKeyAndBody() { fun shouldSendMtbFileRequestWithCorrectKeyAndHeaderAndBody() {
doAnswer { doAnswer {
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile(Consent.Status.ACTIVE))) kafkaMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, bwhcV1MtbFile(Consent.Status.ACTIVE)))
val captor = argumentCaptor<String>() val captor = argumentCaptor<ProducerRecord<String, String>>()
verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture()) verify(kafkaTemplate, times(1)).send(captor.capture())
assertThat(captor.firstValue).isNotNull assertThat(captor.firstValue.key()).isNotNull
assertThat(captor.firstValue).isEqualTo("{\"pid\": \"PID\"}") assertThat(captor.firstValue.key()).isEqualTo("{\"pid\": \"PID\"}")
assertThat(captor.secondValue).isNotNull assertThat(captor.firstValue.headers().headers("contentType")).isNotNull
assertThat(captor.secondValue).isEqualTo(objectMapper.writeValueAsString(kafkaRecordData(TEST_REQUEST_ID, Consent.Status.ACTIVE))) assertThat(captor.firstValue.headers().headers("contentType")?.firstOrNull()?.value()).isEqualTo(MediaType.APPLICATION_JSON_VALUE.toByteArray())
assertThat(captor.firstValue.value()).isNotNull
assertThat(captor.firstValue.value()).isEqualTo(objectMapper.writeValueAsString(bwhcV1kafkaRecordData(TEST_REQUEST_ID, Consent.Status.ACTIVE)))
} }
@Test @Test
fun shouldSendDeleteRequestWithCorrectKeyAndBody() { fun shouldSendDeleteRequestWithCorrectKeyAndBody() {
doAnswer { doAnswer {
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) kafkaMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
val captor = argumentCaptor<String>() val captor = argumentCaptor<ProducerRecord<String, String>>()
verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture()) verify(kafkaTemplate, times(1)).send(captor.capture())
assertThat(captor.firstValue).isNotNull assertThat(captor.firstValue.key()).isNotNull
assertThat(captor.firstValue).isEqualTo("{\"pid\": \"PID\"}") assertThat(captor.firstValue.key()).isEqualTo("{\"pid\": \"PID\"}")
assertThat(captor.secondValue).isNotNull assertThat(captor.firstValue.value()).isNotNull
assertThat(captor.secondValue).isEqualTo(objectMapper.writeValueAsString(kafkaRecordData(TEST_REQUEST_ID, Consent.Status.REJECTED))) assertThat(captor.firstValue.value()).isEqualTo(objectMapper.writeValueAsString(bwhcV1kafkaRecordData(TEST_REQUEST_ID, Consent.Status.REJECTED)))
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("requestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldRetryOnMtbFileKafkaSendError(testData: TestData) { fun shouldRetryOnMtbFileKafkaSendError(testData: TestData) {
val kafkaProperties = KafkaProperties("testtopic") val kafkaProperties = KafkaProperties("testtopic")
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build()
@ -136,9 +146,9 @@ class KafkaMtbFileSenderTest {
throw testData.exception throw testData.exception
} }
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile(Consent.Status.ACTIVE))) kafkaMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, bwhcV1MtbFile(Consent.Status.ACTIVE)))
val expectedCount = when (testData.exception) { val expectedCount = when (testData.exception) {
// OK - No Retry // OK - No Retry
@ -147,11 +157,11 @@ class KafkaMtbFileSenderTest {
else -> times(3) else -> times(3)
} }
verify(kafkaTemplate, expectedCount).send(anyString(), anyString(), anyString()) verify(kafkaTemplate, expectedCount).send(any<ProducerRecord<String, String>>())
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("requestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldRetryOnDeleteKafkaSendError(testData: TestData) { fun shouldRetryOnDeleteKafkaSendError(testData: TestData) {
val kafkaProperties = KafkaProperties("testtopic") val kafkaProperties = KafkaProperties("testtopic")
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build()
@ -162,9 +172,9 @@ class KafkaMtbFileSenderTest {
throw testData.exception throw testData.exception
} }
completedFuture(SendResult<String, String>(null, null)) completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(anyString(), anyString(), anyString()) }.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) kafkaMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
val expectedCount = when (testData.exception) { val expectedCount = when (testData.exception) {
// OK - No Retry // OK - No Retry
@ -173,14 +183,98 @@ class KafkaMtbFileSenderTest {
else -> times(3) else -> times(3)
} }
verify(kafkaTemplate, expectedCount).send(anyString(), anyString(), anyString()) verify(kafkaTemplate, expectedCount).send(any<ProducerRecord<String, String>>())
}
}
@Nested
inner class DnpmV2Record {
private lateinit var kafkaTemplate: KafkaTemplate<String, String>
private lateinit var kafkaMtbFileSender: KafkaMtbFileSender
private lateinit var objectMapper: ObjectMapper
@BeforeEach
fun setup(
@Mock kafkaTemplate: KafkaTemplate<String, String>
) {
val kafkaProperties = KafkaProperties("testtopic")
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build()
this.objectMapper = ObjectMapper()
this.kafkaTemplate = kafkaTemplate
this.kafkaMtbFileSender = KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, objectMapper)
}
@ParameterizedTest
@MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldSendMtbFileRequestAndReturnExpectedState(testData: TestData) {
doAnswer {
if (null != testData.exception) {
throw testData.exception
}
completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
val response = kafkaMtbFileSender.send(DnpmV2MtbFileRequest(TEST_REQUEST_ID, dnpmV2MtbFile()))
assertThat(response.status).isEqualTo(testData.requestStatus)
}
@Test
fun shouldSendMtbFileRequestWithCorrectKeyAndHeaderAndBody() {
doAnswer {
completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(DnpmV2MtbFileRequest(TEST_REQUEST_ID, dnpmV2MtbFile()))
val captor = argumentCaptor<ProducerRecord<String, String>>()
verify(kafkaTemplate, times(1)).send(captor.capture())
assertThat(captor.firstValue.key()).isNotNull
assertThat(captor.firstValue.key()).isEqualTo("{\"pid\": \"PID\"}")
assertThat(captor.firstValue.headers().headers("contentType")).isNotNull
assertThat(captor.firstValue.headers().headers("contentType")?.firstOrNull()?.value()).isEqualTo(CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray())
assertThat(captor.firstValue.value()).isNotNull
assertThat(captor.firstValue.value()).isEqualTo(objectMapper.writeValueAsString(dnmpV2kafkaRecordData(TEST_REQUEST_ID)))
}
@ParameterizedTest
@MethodSource("dev.dnpm.etl.processor.output.KafkaMtbFileSenderTest#requestWithResponseSource")
fun shouldRetryOnMtbFileKafkaSendError(testData: TestData) {
val kafkaProperties = KafkaProperties("testtopic")
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build()
this.kafkaMtbFileSender = KafkaMtbFileSender(this.kafkaTemplate, kafkaProperties, retryTemplate, this.objectMapper)
doAnswer {
if (null != testData.exception) {
throw testData.exception
}
completedFuture(SendResult<String, String>(null, null))
}.whenever(kafkaTemplate).send(any<ProducerRecord<String, String>>())
kafkaMtbFileSender.send(DnpmV2MtbFileRequest(TEST_REQUEST_ID, dnpmV2MtbFile()))
val expectedCount = when (testData.exception) {
// OK - No Retry
null -> times(1)
// Request failed - Retry max 3 times
else -> times(3)
}
verify(kafkaTemplate, expectedCount).send(any<ProducerRecord<String, String>>())
}
} }
companion object { companion object {
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
fun mtbFile(consentStatus: Consent.Status): MtbFile { fun bwhcV1MtbFile(consentStatus: Consent.Status): MtbFile {
return if (consentStatus == Consent.Status.ACTIVE) { return if (consentStatus == Consent.Status.ACTIVE) {
MtbFile.builder() MtbFile.builder()
.withPatient( .withPatient(
@ -215,8 +309,31 @@ class KafkaMtbFileSenderTest {
}.build() }.build()
} }
fun kafkaRecordData(requestId: RequestId, consentStatus: Consent.Status): KafkaMtbFileSender.Data { fun dnpmV2MtbFile(): Mtb = Mtb.builder()
return KafkaMtbFileSender.Data(requestId, mtbFile(consentStatus)) .withPatient(
dev.pcvolkmer.mv64e.mtb.Patient.builder()
.withId("PID")
.withBirthDate("2000-08-08")
.withGender(CodingGender.builder().withCode(CodingGender.Code.MALE).build())
.build()
)
.withEpisodesOfCare(
listOf(
MTBEpisodeOfCare.builder()
.withId("1")
.withPatient(Reference("PID"))
.withPeriod(PeriodDate.builder().withStart("2023-08-08").build())
.build()
)
)
.build()
fun bwhcV1kafkaRecordData(requestId: RequestId, consentStatus: Consent.Status): MtbRequest {
return BwhcV1MtbFileRequest(requestId, bwhcV1MtbFile(consentStatus))
}
fun dnmpV2kafkaRecordData(requestId: RequestId): MtbRequest {
return DnpmV2MtbFileRequest(requestId, dnpmV2MtbFile())
} }
data class TestData(val requestStatus: RequestStatus, val exception: Throwable? = null) data class TestData(val requestStatus: RequestStatus, val exception: Throwable? = null)

View File

@ -30,16 +30,16 @@ import dev.dnpm.etl.processor.monitoring.RequestStatus
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.retry.policy.SimpleRetryPolicy import org.springframework.retry.policy.SimpleRetryPolicy
import org.springframework.retry.support.RetryTemplateBuilder import org.springframework.retry.support.RetryTemplateBuilder
import org.springframework.test.web.client.ExpectedCount import org.springframework.test.web.client.ExpectedCount
import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers.method import org.springframework.test.web.client.match.MockRestRequestMatchers.*
import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
@ -73,7 +73,7 @@ class RestBwhcMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ -84,11 +84,12 @@ class RestBwhcMtbFileSenderTest {
this.mockRestServiceServer this.mockRestServiceServer
.expect(method(HttpMethod.POST)) .expect(method(HttpMethod.POST))
.andExpect(requestTo("http://localhost:9000/mtbfile/MTBFile")) .andExpect(requestTo("http://localhost:9000/mtbfile/MTBFile"))
.andExpect(header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
.andRespond { .andRespond {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile)) val response = restMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, mtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ -118,7 +119,7 @@ class RestBwhcMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile)) val response = restMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, mtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ -148,7 +149,7 @@ class RestBwhcMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }

View File

@ -22,6 +22,8 @@ package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import de.ukw.ccc.bwhc.dto.Patient
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
@ -29,25 +31,30 @@ import dev.dnpm.etl.processor.config.AppConfiguration
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.output.RestBwhcMtbFileSenderTest.Companion import dev.pcvolkmer.mv64e.mtb.*
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.retry.backoff.NoBackOffPolicy import org.springframework.retry.backoff.NoBackOffPolicy
import org.springframework.retry.policy.SimpleRetryPolicy import org.springframework.retry.policy.SimpleRetryPolicy
import org.springframework.retry.support.RetryTemplateBuilder import org.springframework.retry.support.RetryTemplateBuilder
import org.springframework.test.web.client.ExpectedCount import org.springframework.test.web.client.ExpectedCount
import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers.method import org.springframework.test.web.client.match.MockRestRequestMatchers.*
import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
class RestDipMtbFileSenderTest { class RestDipMtbFileSenderTest {
@Nested
inner class BwhcV1ContentRequest {
private lateinit var mockRestServiceServer: MockRestServiceServer private lateinit var mockRestServiceServer: MockRestServiceServer
private lateinit var restMtbFileSender: RestMtbFileSender private lateinit var restMtbFileSender: RestMtbFileSender
@ -62,41 +69,28 @@ class RestDipMtbFileSenderTest {
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) this.restMtbFileSender =
RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("deleteRequestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#mtbFileRequestWithResponseSource")
fun shouldReturnExpectedResponseForDelete(requestWithResponse: RequestWithResponse) {
this.mockRestServiceServer
.expect(method(HttpMethod.DELETE))
.andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient/${TEST_PATIENT_PSEUDONYM.value}"))
.andRespond {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
}
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body)
}
@ParameterizedTest
@MethodSource("mtbFileRequestWithResponseSource")
fun shouldReturnExpectedResponseForMtbFilePost(requestWithResponse: RequestWithResponse) { fun shouldReturnExpectedResponseForMtbFilePost(requestWithResponse: RequestWithResponse) {
this.mockRestServiceServer this.mockRestServiceServer
.expect(method(HttpMethod.POST)) .expect(method(HttpMethod.POST))
.andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient-record")) .andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient-record"))
.andExpect(header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE))
.andRespond { .andRespond {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile)) val response = restMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, bwhcV1mtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("mtbFileRequestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#mtbFileRequestWithResponseSource")
fun shouldRetryOnMtbFileHttpRequestError(requestWithResponse: RequestWithResponse) { fun shouldRetryOnMtbFileHttpRequestError(requestWithResponse: RequestWithResponse) {
val restTemplate = RestTemplate() val restTemplate = RestTemplate()
val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false) val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false)
@ -123,13 +117,89 @@ class RestDipMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest(TEST_REQUEST_ID, mtbFile)) val response = restMtbFileSender.send(BwhcV1MtbFileRequest(TEST_REQUEST_ID, bwhcV1mtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body)
}
}
@Nested
inner class DnpmV2ContentRequest {
private lateinit var mockRestServiceServer: MockRestServiceServer
private lateinit var restMtbFileSender: RestMtbFileSender
private var reportService = ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build()))
@BeforeEach
fun setup() {
val restTemplate = RestTemplate()
val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false)
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build()
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
}
@ParameterizedTest
@MethodSource("dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#mtbFileRequestWithResponseSource")
fun shouldReturnExpectedResponseForDnpmV2MtbFilePost(requestWithResponse: RequestWithResponse) {
this.mockRestServiceServer
.expect(method(HttpMethod.POST))
.andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient-record"))
.andExpect(header(HttpHeaders.CONTENT_TYPE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE))
.andRespond {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
}
val response = restMtbFileSender.send(DnpmV2MtbFileRequest(TEST_REQUEST_ID, dnpmV2MtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body)
}
}
@Nested
inner class DeleteRequest {
private lateinit var mockRestServiceServer: MockRestServiceServer
private lateinit var restMtbFileSender: RestMtbFileSender
private var reportService = ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build()))
@BeforeEach
fun setup() {
val restTemplate = RestTemplate()
val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false)
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build()
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender =
RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
}
@ParameterizedTest
@MethodSource("dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#deleteRequestWithResponseSource")
fun shouldReturnExpectedResponseForDelete(requestWithResponse: RequestWithResponse) {
this.mockRestServiceServer
.expect(method(HttpMethod.DELETE))
.andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient/${TEST_PATIENT_PSEUDONYM.value}"))
.andRespond {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
}
val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("deleteRequestWithResponseSource") @MethodSource("dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#deleteRequestWithResponseSource")
fun shouldRetryOnDeleteHttpRequestError(requestWithResponse: RequestWithResponse) { fun shouldRetryOnDeleteHttpRequestError(requestWithResponse: RequestWithResponse) {
val restTemplate = RestTemplate() val restTemplate = RestTemplate()
val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false) val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null, false)
@ -156,11 +226,13 @@ class RestDipMtbFileSenderTest {
withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it) withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM))
assertThat(response.status).isEqualTo(requestWithResponse.response.status) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body) assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
}
companion object { companion object {
data class RequestWithResponse( data class RequestWithResponse(
val httpStatus: HttpStatus, val httpStatus: HttpStatus,
@ -171,7 +243,7 @@ class RestDipMtbFileSenderTest {
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
val mtbFile: MtbFile = MtbFile.builder() val bwhcV1mtbFile: MtbFile = MtbFile.builder()
.withPatient( .withPatient(
Patient.builder() Patient.builder()
.withId("PID") .withId("PID")
@ -195,6 +267,25 @@ class RestDipMtbFileSenderTest {
) )
.build() .build()
val dnpmV2MtbFile: Mtb = Mtb.builder()
.withPatient(
dev.pcvolkmer.mv64e.mtb.Patient.builder()
.withId("PID")
.withBirthDate("2000-08-08")
.withGender(CodingGender.builder().withCode(CodingGender.Code.MALE).build())
.build()
)
.withEpisodesOfCare(
listOf(
MTBEpisodeOfCare.builder()
.withId("1")
.withPatient(Reference("PID"))
.withPeriod(PeriodDate.builder().withStart("2023-08-08").build())
.build()
)
)
.build()
private const val ERROR_RESPONSE_BODY = "Sonstiger Fehler bei der Übertragung" private const val ERROR_RESPONSE_BODY = "Sonstiger Fehler bei der Übertragung"
/** /**

View File

@ -21,14 +21,19 @@ package dev.dnpm.etl.processor.services
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.* import dev.dnpm.etl.processor.Fingerprint
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.monitoring.Request import dev.dnpm.etl.processor.monitoring.Request
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.RequestType import dev.dnpm.etl.processor.monitoring.RequestType
import dev.dnpm.etl.processor.output.BwhcV1MtbFileRequest
import dev.dnpm.etl.processor.output.DeleteRequest
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.MtbFileSender
import dev.dnpm.etl.processor.output.RestMtbFileSender import dev.dnpm.etl.processor.output.RestMtbFileSender
import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
import dev.dnpm.etl.processor.randomRequestId
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -109,7 +114,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
}.whenever(transformationService).transform(any()) }.whenever(transformationService).transform(any<MtbFile>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(
@ -168,7 +173,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
}.whenever(transformationService).transform(any()) }.whenever(transformationService).transform(any<MtbFile>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(
@ -223,7 +228,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
}.whenever(sender).send(any<MtbFileSender.MtbFileRequest>()) }.whenever(sender).send(any<BwhcV1MtbFileRequest>())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
@ -231,7 +236,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
}.whenever(transformationService).transform(any()) }.whenever(transformationService).transform(any<MtbFile>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(
@ -286,7 +291,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)
}.whenever(sender).send(any<MtbFileSender.MtbFileRequest>()) }.whenever(sender).send(any<BwhcV1MtbFileRequest>())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
@ -294,7 +299,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
}.whenever(transformationService).transform(any()) }.whenever(transformationService).transform(any<MtbFile>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(
@ -336,7 +341,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.UNKNOWN) MtbFileSender.Response(status = RequestStatus.UNKNOWN)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID) this.requestProcessor.processDeletion(TEST_PATIENT_ID)
@ -354,7 +359,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID) this.requestProcessor.processDeletion(TEST_PATIENT_ID)
@ -372,7 +377,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)
}.whenever(sender).send(any<MtbFileSender.DeleteRequest>()) }.whenever(sender).send(any<DeleteRequest>())
this.requestProcessor.processDeletion(TEST_PATIENT_ID) this.requestProcessor.processDeletion(TEST_PATIENT_ID)
@ -404,11 +409,11 @@ class RequestProcessorTest {
doAnswer { doAnswer {
it.arguments[0] it.arguments[0]
}.whenever(transformationService).transform(any()) }.whenever(transformationService).transform(any<MtbFile>())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
}.whenever(sender).send(any<MtbFileSender.MtbFileRequest>()) }.whenever(sender).send(any<BwhcV1MtbFileRequest>())
val mtbFile = MtbFile.builder() val mtbFile = MtbFile.builder()
.withPatient( .withPatient(