summaryrefslogtreecommitdiff
path: root/src/test/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/kotlin')
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt173
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSenderTest.kt195
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt86
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt70
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt372
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt225
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt138
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt149
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/web/MtbFileRestControllerTest.kt150
9 files changed, 1558 insertions, 0 deletions
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt
new file mode 100644
index 0000000..3ec9757
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt
@@ -0,0 +1,173 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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 com.fasterxml.jackson.databind.ObjectMapper
+import de.ukw.ccc.bwhc.dto.*
+import dev.dnpm.etl.processor.config.KafkaTargetProperties
+import dev.dnpm.etl.processor.monitoring.RequestStatus
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.*
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.support.SendResult
+import java.util.concurrent.CompletableFuture.completedFuture
+import java.util.concurrent.ExecutionException
+
+@ExtendWith(MockitoExtension::class)
+class KafkaMtbFileSenderTest {
+
+ 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 kafkaTargetProperties = KafkaTargetProperties("testtopic")
+ this.objectMapper = ObjectMapper()
+ this.kafkaTemplate = kafkaTemplate
+
+ this.kafkaMtbFileSender = KafkaMtbFileSender(kafkaTemplate, kafkaTargetProperties, objectMapper)
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestWithResponseSource")
+ fun shouldSendMtbFileRequestAndReturnExpectedState(testData: TestData) {
+ doAnswer {
+ if (null != testData.exception) {
+ throw testData.exception
+ }
+ completedFuture(SendResult<String, String>(null, null))
+ }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
+
+ val response = kafkaMtbFileSender.send(MtbFileSender.MtbFileRequest("TestID", mtbFile(Consent.Status.ACTIVE)))
+ assertThat(response.status).isEqualTo(testData.requestStatus)
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestWithResponseSource")
+ fun shouldSendDeleteRequestAndReturnExpectedState(testData: TestData) {
+ doAnswer {
+ if (null != testData.exception) {
+ throw testData.exception
+ }
+ completedFuture(SendResult<String, String>(null, null))
+ }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
+
+ val response = kafkaMtbFileSender.send(MtbFileSender.DeleteRequest("TestID", "PID"))
+ assertThat(response.status).isEqualTo(testData.requestStatus)
+ }
+
+ @Test
+ fun shouldSendMtbFileRequestWithCorrectKeyAndBody() {
+ doAnswer {
+ completedFuture(SendResult<String, String>(null, null))
+ }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
+
+ kafkaMtbFileSender.send(MtbFileSender.MtbFileRequest("TestID", mtbFile(Consent.Status.ACTIVE)))
+
+ val captor = argumentCaptor<String>()
+ verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture())
+ assertThat(captor.firstValue).isNotNull
+ assertThat(captor.firstValue).isEqualTo("{\"pid\": \"PID\", \"eid\": \"1\"}")
+ assertThat(captor.secondValue).isNotNull
+ assertThat(captor.secondValue).isEqualTo(objectMapper.writeValueAsString(kafkaRecordData("TestID", Consent.Status.ACTIVE)))
+ }
+
+ @Test
+ fun shouldSendDeleteRequestWithCorrectKeyAndBody() {
+ doAnswer {
+ completedFuture(SendResult<String, String>(null, null))
+ }.whenever(kafkaTemplate).send(anyString(), anyString(), anyString())
+
+ kafkaMtbFileSender.send(MtbFileSender.DeleteRequest("TestID", "PID"))
+
+ val captor = argumentCaptor<String>()
+ verify(kafkaTemplate, times(1)).send(anyString(), captor.capture(), captor.capture())
+ assertThat(captor.firstValue).isNotNull
+ assertThat(captor.firstValue).isEqualTo("{\"pid\": \"PID\"}")
+ assertThat(captor.secondValue).isNotNull
+ assertThat(captor.secondValue).isEqualTo(objectMapper.writeValueAsString(kafkaRecordData("TestID", Consent.Status.REJECTED)))
+ }
+
+ companion object {
+ fun mtbFile(consentStatus: Consent.Status): MtbFile {
+ return if (consentStatus == Consent.Status.ACTIVE) {
+ MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("PID")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(consentStatus)
+ .withPatient("PID")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("PID")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .build()
+ )
+ } else {
+ MtbFile.builder()
+ .withConsent(
+ Consent.builder()
+ .withStatus(consentStatus)
+ .withPatient("PID")
+ .build()
+ )
+ }.build()
+ }
+
+ fun kafkaRecordData(requestId: String, consentStatus: Consent.Status): KafkaMtbFileSender.Data {
+ return KafkaMtbFileSender.Data(requestId, mtbFile(consentStatus))
+ }
+
+ data class TestData(val requestStatus: RequestStatus, val exception: Throwable? = null)
+
+ @JvmStatic
+ fun requestWithResponseSource(): Set<TestData> {
+ return setOf(
+ TestData(RequestStatus.UNKNOWN),
+ TestData(RequestStatus.ERROR, InterruptedException("Test interrupted")),
+ TestData(RequestStatus.ERROR, ExecutionException(RuntimeException("Test execution aborted")))
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSenderTest.kt
new file mode 100644
index 0000000..0cad285
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSenderTest.kt
@@ -0,0 +1,195 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.*
+import dev.dnpm.etl.processor.config.RestTargetProperties
+import dev.dnpm.etl.processor.monitoring.RequestStatus
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import org.springframework.http.HttpMethod
+import org.springframework.http.HttpStatus
+import org.springframework.test.web.client.MockRestServiceServer
+import org.springframework.test.web.client.match.MockRestRequestMatchers.method
+import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
+import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
+import org.springframework.web.client.RestTemplate
+
+class RestMtbFileSenderTest {
+
+ private lateinit var mockRestServiceServer: MockRestServiceServer
+
+ private lateinit var restMtbFileSender: RestMtbFileSender
+
+ @BeforeEach
+ fun setup() {
+ val restTemplate = RestTemplate()
+ val restTargetProperties = RestTargetProperties("http://localhost:9000/mtbfile")
+
+ this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
+
+ this.restMtbFileSender = RestMtbFileSender(restTemplate, restTargetProperties)
+ }
+
+ @ParameterizedTest
+ @MethodSource("deleteRequestWithResponseSource")
+ fun shouldReturnExpectedResponseForDelete(requestWithResponse: RequestWithResponse) {
+ this.mockRestServiceServer.expect {
+ method(HttpMethod.DELETE)
+ requestTo("/mtbfile")
+ }.andRespond {
+ withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
+ }
+
+ val response = restMtbFileSender.send(MtbFileSender.DeleteRequest("TestID", "PID"))
+ assertThat(response.status).isEqualTo(requestWithResponse.response.status)
+ assertThat(response.body).isEqualTo(requestWithResponse.response.body)
+ }
+
+ @ParameterizedTest
+ @MethodSource("mtbFileRequestWithResponseSource")
+ fun shouldReturnExpectedResponseForMtbFilePost(requestWithResponse: RequestWithResponse) {
+ this.mockRestServiceServer.expect {
+ method(HttpMethod.POST)
+ requestTo("/mtbfile")
+ }.andRespond {
+ withStatus(requestWithResponse.httpStatus).body(requestWithResponse.body).createResponse(it)
+ }
+
+ val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest("TestID", mtbFile))
+ assertThat(response.status).isEqualTo(requestWithResponse.response.status)
+ assertThat(response.body).isEqualTo(requestWithResponse.response.body)
+ }
+
+ companion object {
+ data class RequestWithResponse(
+ val httpStatus: HttpStatus,
+ val body: String,
+ val response: MtbFileSender.Response
+ )
+
+ private val warningBody = """
+ {
+ "patient_id": "PID",
+ "issues": [
+ { "severity": "warning", "message": "Something is not right" }
+ ]
+ }
+ """.trimIndent()
+
+ private val errorBody = """
+ {
+ "patient_id": "PID",
+ "issues": [
+ { "severity": "error", "message": "Something is very bad" }
+ ]
+ }
+ """.trimIndent()
+
+ val mtbFile: MtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("PID")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("PID")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("PID")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .build()
+ )
+ .build()
+
+ private const val ERROR_RESPONSE_BODY = "Sonstiger Fehler bei der Übertragung"
+
+ /**
+ * Synthetic http responses with related request status
+ * Also see: https://ibmi-intra.cs.uni-tuebingen.de/display/ZPM/bwHC+REST+API
+ */
+ @JvmStatic
+ fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> {
+ return setOf(
+ RequestWithResponse(HttpStatus.OK, "{}", MtbFileSender.Response(RequestStatus.SUCCESS, "{}")),
+ RequestWithResponse(
+ HttpStatus.CREATED,
+ warningBody,
+ MtbFileSender.Response(RequestStatus.WARNING, warningBody)
+ ),
+ RequestWithResponse(
+ HttpStatus.BAD_REQUEST,
+ "??",
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ ),
+ RequestWithResponse(
+ HttpStatus.UNPROCESSABLE_ENTITY,
+ errorBody,
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ ),
+ // Some more errors not mentioned in documentation
+ RequestWithResponse(
+ HttpStatus.NOT_FOUND,
+ "what????",
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ ),
+ RequestWithResponse(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ "what????",
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ )
+ )
+ }
+
+ /**
+ * Synthetic http responses with related request status
+ * Also see: https://ibmi-intra.cs.uni-tuebingen.de/display/ZPM/bwHC+REST+API
+ */
+ @JvmStatic
+ fun deleteRequestWithResponseSource(): Set<RequestWithResponse> {
+ return setOf(
+ RequestWithResponse(HttpStatus.OK, "", MtbFileSender.Response(RequestStatus.SUCCESS)),
+ // Some more errors not mentioned in documentation
+ RequestWithResponse(
+ HttpStatus.NOT_FOUND,
+ "what????",
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ ),
+ RequestWithResponse(
+ HttpStatus.INTERNAL_SERVER_ERROR,
+ "what????",
+ MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY)
+ )
+ )
+ }
+ }
+
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt
new file mode 100644
index 0000000..a30a328
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt
@@ -0,0 +1,86 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.pseudonym
+
+import de.ukw.ccc.bwhc.dto.*
+import dev.dnpm.etl.processor.config.PseudonymizeConfigProperties
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.whenever
+
+@ExtendWith(MockitoExtension::class)
+class PseudonymizeServiceTest {
+
+ private val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("123")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("123")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("123")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .build()
+ )
+ .build()
+
+ @Test
+ fun shouldNotUsePseudonymPrefixForGpas(@Mock generator: GpasPseudonymGenerator) {
+ doAnswer {
+ it.arguments[0]
+ }.whenever(generator).generate(anyString())
+
+ val pseudonymizeService = PseudonymizeService(generator, PseudonymizeConfigProperties())
+
+ mtbFile.pseudonymizeWith(pseudonymizeService)
+
+ assertThat(mtbFile.patient.id).isEqualTo("123")
+ }
+
+ @Test
+ fun shouldUsePseudonymPrefixForBuiltin(@Mock generator: AnonymizingGenerator) {
+ doAnswer {
+ it.arguments[0]
+ }.whenever(generator).generate(anyString())
+
+ val pseudonymizeService = PseudonymizeService(generator, PseudonymizeConfigProperties())
+
+ mtbFile.pseudonymizeWith(pseudonymizeService)
+
+ assertThat(mtbFile.patient.id).isEqualTo("UNKNOWN_123")
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt
new file mode 100644
index 0000000..70efe2b
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt
@@ -0,0 +1,70 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.services
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import dev.dnpm.etl.processor.monitoring.ReportService
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class ReportServiceTest {
+
+ private lateinit var reportService: ReportService
+
+ @BeforeEach
+ fun setup() {
+ this.reportService = ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build()))
+ }
+
+ @Test
+ fun shouldParseDataQualityReport() {
+ val json = """
+ {
+ "patient": "4711",
+ "issues": [
+ { "severity": "warning", "message": "Warning Message" },
+ { "severity": "error", "message": "Error Message" }
+ ]
+ }
+ """.trimIndent()
+
+ val actual = this.reportService.deserialize(json)
+
+ assertThat(actual).hasSize(2)
+ assertThat(actual[0].severity).isEqualTo(ReportService.Severity.WARNING)
+ assertThat(actual[0].message).isEqualTo("Warning Message")
+ assertThat(actual[1].severity).isEqualTo(ReportService.Severity.ERROR)
+ assertThat(actual[1].message).isEqualTo("Error Message")
+ }
+
+ @Test
+ fun shouldReturnSyntheticDataQualityReportOnParserError() {
+ val invalidResponse = "Invalid Response Data"
+
+ val actual = this.reportService.deserialize(invalidResponse)
+
+ assertThat(actual).hasSize(1)
+ assertThat(actual[0].severity).isEqualTo(ReportService.Severity.ERROR)
+ assertThat(actual[0].message).isEqualTo("Not parsable data quality report '$invalidResponse'")
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt
new file mode 100644
index 0000000..7856833
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt
@@ -0,0 +1,372 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.services
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import de.ukw.ccc.bwhc.dto.*
+import dev.dnpm.etl.processor.monitoring.Request
+import dev.dnpm.etl.processor.monitoring.RequestStatus
+import dev.dnpm.etl.processor.monitoring.RequestType
+import dev.dnpm.etl.processor.output.MtbFileSender
+import dev.dnpm.etl.processor.output.RestMtbFileSender
+import dev.dnpm.etl.processor.pseudonym.PseudonymizeService
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.*
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+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 sender: MtbFileSender
+ private lateinit var requestService: RequestService
+ private lateinit var applicationEventPublisher: ApplicationEventPublisher
+
+ private lateinit var requestProcessor: RequestProcessor
+
+ @BeforeEach
+ fun setup(
+ @Mock pseudonymizeService: PseudonymizeService,
+ @Mock sender: RestMtbFileSender,
+ @Mock requestService: RequestService,
+ @Mock applicationEventPublisher: ApplicationEventPublisher
+ ) {
+ this.pseudonymizeService = pseudonymizeService
+ this.sender = sender
+ this.requestService = requestService
+ this.applicationEventPublisher = applicationEventPublisher
+
+ val objectMapper = ObjectMapper()
+
+ requestProcessor = RequestProcessor(
+ pseudonymizeService,
+ sender,
+ requestService,
+ objectMapper,
+ applicationEventPublisher
+ )
+ }
+
+ @Test
+ fun testShouldSendMtbFileDuplicationAndSaveUnknownRequestStatusAtFirst() {
+ doAnswer {
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "xrysxpozhbs2lnrjgf3yq4fzj33kxr7xr5c2cbuskmelfdmckl3a",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-08-08T02:00:00Z")
+ )
+ }.`when`(requestService).lastMtbFileRequestForPatientPseudonym(anyString())
+
+ doAnswer {
+ false
+ }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
+
+ doAnswer {
+ it.arguments[0] as String
+ }.`when`(pseudonymizeService).patientPseudonym(any())
+
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("1")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("123")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("1")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .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.UNKNOWN)
+ }
+
+ @Test
+ fun testShouldDetectMtbFileDuplicationAndSendDuplicationEvent() {
+ doAnswer {
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "xrysxpozhbs2lnrjgf3yq4fzj33kxr7xr5c2cbuskmelfdmckl3a",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-08-08T02:00:00Z")
+ )
+ }.`when`(requestService).lastMtbFileRequestForPatientPseudonym(anyString())
+
+ doAnswer {
+ false
+ }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
+
+ doAnswer {
+ it.arguments[0] as String
+ }.`when`(pseudonymizeService).patientPseudonym(any())
+
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("1")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("123")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("1")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .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.DUPLICATION)
+ }
+
+ @Test
+ fun testShouldSendMtbFileAndSendSuccessEvent() {
+ doAnswer {
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "different",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-08-08T02:00:00Z")
+ )
+ }.`when`(requestService).lastMtbFileRequestForPatientPseudonym(anyString())
+
+ doAnswer {
+ false
+ }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
+
+ doAnswer {
+ MtbFileSender.Response(status = RequestStatus.SUCCESS)
+ }.`when`(sender).send(any<MtbFileSender.MtbFileRequest>())
+
+ doAnswer {
+ it.arguments[0] as String
+ }.`when`(pseudonymizeService).patientPseudonym(any())
+
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("1")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("123")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("1")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .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 testShouldSendMtbFileAndSendErrorEvent() {
+ doAnswer {
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "different",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-08-08T02:00:00Z")
+ )
+ }.`when`(requestService).lastMtbFileRequestForPatientPseudonym(anyString())
+
+ doAnswer {
+ false
+ }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
+
+ doAnswer {
+ MtbFileSender.Response(status = RequestStatus.ERROR)
+ }.`when`(sender).send(any<MtbFileSender.MtbFileRequest>())
+
+ doAnswer {
+ it.arguments[0] as String
+ }.`when`(pseudonymizeService).patientPseudonym(any())
+
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("1")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("123")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("1")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .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 testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() {
+ doAnswer {
+ "PSEUDONYM"
+ }.`when`(pseudonymizeService).patientPseudonym(anyString())
+
+ doAnswer {
+ MtbFileSender.Response(status = RequestStatus.UNKNOWN)
+ }.`when`(sender).send(any<MtbFileSender.DeleteRequest>())
+
+ this.requestProcessor.processDeletion("TEST_12345678901")
+
+ 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"
+ }.`when`(pseudonymizeService).patientPseudonym(anyString())
+
+ doAnswer {
+ MtbFileSender.Response(status = RequestStatus.SUCCESS)
+ }.`when`(sender).send(any<MtbFileSender.DeleteRequest>())
+
+ this.requestProcessor.processDeletion("TEST_12345678901")
+
+ 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"
+ }.`when`(pseudonymizeService).patientPseudonym(anyString())
+
+ doAnswer {
+ MtbFileSender.Response(status = RequestStatus.ERROR)
+ }.`when`(sender).send(any<MtbFileSender.DeleteRequest>())
+
+ this.requestProcessor.processDeletion("TEST_12345678901")
+
+ 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()).`when`(pseudonymizeService).patientPseudonym(anyString())
+
+ this.requestProcessor.processDeletion("TEST_12345678901")
+
+ val requestCaptor = argumentCaptor<Request>()
+ verify(requestService, times(1)).save(requestCaptor.capture())
+ assertThat(requestCaptor.firstValue).isNotNull
+ assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.ERROR)
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt
new file mode 100644
index 0000000..3cf8804
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt
@@ -0,0 +1,225 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.services
+
+import dev.dnpm.etl.processor.monitoring.Request
+import dev.dnpm.etl.processor.monitoring.RequestRepository
+import dev.dnpm.etl.processor.monitoring.RequestStatus
+import dev.dnpm.etl.processor.monitoring.RequestType
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mock
+import org.mockito.Mockito.*
+import org.mockito.junit.jupiter.MockitoExtension
+import java.time.Instant
+import java.util.*
+
+@ExtendWith(MockitoExtension::class)
+class RequestServiceTest {
+
+ private lateinit var requestRepository: RequestRepository
+
+ private lateinit var requestService: RequestService
+
+ private fun anyRequest() = any(Request::class.java) ?: Request(
+ id = 0L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_dummy",
+ pid = "PX",
+ fingerprint = "dummy",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-08-08T02:00:00Z")
+ )
+
+ @BeforeEach
+ fun setup(
+ @Mock requestRepository: RequestRepository
+ ) {
+ this.requestRepository = requestRepository
+ this.requestService = RequestService(requestRepository)
+ }
+
+ @Test
+ fun shouldIndicateLastRequestIsDeleteRequest() {
+ val requests = listOf(
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.WARNING,
+ processedAt = Instant.parse("2023-07-07T00:00:00Z")
+ ),
+ Request(
+ id = 2L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdefd",
+ type = RequestType.DELETE,
+ status = RequestStatus.WARNING,
+ processedAt = Instant.parse("2023-07-07T02:00:00Z")
+ ),
+ Request(
+ id = 3L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.UNKNOWN,
+ processedAt = Instant.parse("2023-08-11T00:00:00Z")
+ )
+ )
+
+ val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun shouldIndicateLastRequestIsNotDeleteRequest() {
+ val requests = listOf(
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.WARNING,
+ processedAt = Instant.parse("2023-07-07T00:00:00Z")
+ ),
+ Request(
+ id = 2L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.WARNING,
+ processedAt = Instant.parse("2023-07-07T02:00:00Z")
+ ),
+ Request(
+ id = 3L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.UNKNOWN,
+ processedAt = Instant.parse("2023-08-11T00:00:00Z")
+ )
+ )
+
+ val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun shouldReturnPatientsLastRequest() {
+ val requests = listOf(
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.DELETE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-07-07T02:00:00Z")
+ ),
+ Request(
+ id = 1L,
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678902",
+ pid = "P2",
+ fingerprint = "0123456789abcdef2",
+ type = RequestType.MTB_FILE,
+ status = RequestStatus.WARNING,
+ processedAt = Instant.parse("2023-08-08T00:00:00Z")
+ )
+ )
+
+ val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests)
+
+ assertThat(actual).isInstanceOf(Request::class.java)
+ assertThat(actual?.fingerprint).isEqualTo("0123456789abcdef2")
+ }
+
+ @Test
+ fun shouldReturnNullIfNoRequests() {
+ val requests = listOf<Request>()
+
+ val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests)
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun saveShouldSaveRequestUsingRepository() {
+ doAnswer {
+ val obj = it.arguments[0] as Request
+ obj.copy(id = 1L)
+ }.`when`(requestRepository).save(anyRequest())
+
+ val request = Request(
+ uuid = UUID.randomUUID().toString(),
+ patientId = "TEST_12345678901",
+ pid = "P1",
+ fingerprint = "0123456789abcdef1",
+ type = RequestType.DELETE,
+ status = RequestStatus.SUCCESS,
+ processedAt = Instant.parse("2023-07-07T02:00:00Z")
+ )
+
+ requestService.save(request)
+
+ verify(requestRepository, times(1)).save(anyRequest())
+ }
+
+ @Test
+ fun allRequestsByPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() {
+ requestService.allRequestsByPatientPseudonym("TEST_12345678901")
+
+ verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString())
+ }
+
+ @Test
+ fun lastMtbFileRequestForPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() {
+ requestService.lastMtbFileRequestForPatientPseudonym("TEST_12345678901")
+
+ verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString())
+ }
+
+ @Test
+ fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() {
+ requestService.isLastRequestWithKnownStatusDeletion("TEST_12345678901")
+
+ verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString())
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt
new file mode 100644
index 0000000..b9e4b7f
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt
@@ -0,0 +1,138 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.services
+
+import dev.dnpm.etl.processor.monitoring.Request
+import dev.dnpm.etl.processor.monitoring.RequestRepository
+import dev.dnpm.etl.processor.monitoring.RequestStatus
+import dev.dnpm.etl.processor.monitoring.RequestType
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.*
+import reactor.core.publisher.Sinks
+import java.time.Instant
+import java.util.*
+
+@ExtendWith(MockitoExtension::class)
+class ResponseProcessorTest {
+
+ private lateinit var requestRepository: RequestRepository
+ private lateinit var statisticsUpdateProducer: Sinks.Many<Any>
+
+ private lateinit var responseProcessor: ResponseProcessor
+
+ private val testRequest = Request(
+ 1L,
+ "TestID1234",
+ "PSEUDONYM-A",
+ "1",
+ "dummyfingerprint",
+ RequestType.MTB_FILE,
+ RequestStatus.UNKNOWN
+ )
+
+ @BeforeEach
+ fun setup(
+ @Mock requestRepository: RequestRepository,
+ @Mock statisticsUpdateProducer: Sinks.Many<Any>
+ ) {
+ this.requestRepository = requestRepository
+ this.statisticsUpdateProducer = statisticsUpdateProducer
+
+ this.responseProcessor = ResponseProcessor(requestRepository, statisticsUpdateProducer)
+ }
+
+ @Test
+ fun shouldNotSaveStatusForUnknownRequest() {
+ doAnswer {
+ Optional.empty<Request>()
+ }.whenever(requestRepository).findByUuidEquals(anyString())
+
+ val event = ResponseEvent(
+ "TestID1234",
+ Instant.parse("2023-09-09T00:00:00Z"),
+ RequestStatus.SUCCESS
+ )
+
+ this.responseProcessor.handleResponseEvent(event)
+
+ verify(requestRepository, never()).save(any())
+ }
+
+ @Test
+ fun shouldNotSaveStatusWithUnknownState() {
+ doAnswer {
+ Optional.of(testRequest)
+ }.whenever(requestRepository).findByUuidEquals(anyString())
+
+ val event = ResponseEvent(
+ "TestID1234",
+ Instant.parse("2023-09-09T00:00:00Z"),
+ RequestStatus.UNKNOWN
+ )
+
+ this.responseProcessor.handleResponseEvent(event)
+
+ verify(requestRepository, never()).save(any())
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestStatusSource")
+ fun shouldSaveStatusForKnownRequest(requestStatus: RequestStatus) {
+ doAnswer {
+ Optional.of(testRequest)
+ }.whenever(requestRepository).findByUuidEquals(anyString())
+
+ val event = ResponseEvent(
+ "TestID1234",
+ Instant.parse("2023-09-09T00:00:00Z"),
+ requestStatus
+ )
+
+ this.responseProcessor.handleResponseEvent(event)
+
+ val captor = argumentCaptor<Request>()
+ verify(requestRepository, times(1)).save(captor.capture())
+ assertThat(captor.firstValue).isNotNull
+ assertThat(captor.firstValue.status).isEqualTo(requestStatus)
+ }
+
+ companion object {
+
+ @JvmStatic
+ fun requestStatusSource(): Set<RequestStatus> {
+ return setOf(
+ RequestStatus.SUCCESS,
+ RequestStatus.WARNING,
+ RequestStatus.ERROR,
+ RequestStatus.DUPLICATION
+ )
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt
new file mode 100644
index 0000000..95bf41b
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt
@@ -0,0 +1,149 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.services.kafka
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import dev.dnpm.etl.processor.services.ResponseEvent
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.MethodSource
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.springframework.context.ApplicationEventPublisher
+import org.springframework.http.HttpStatus
+
+@ExtendWith(MockitoExtension::class)
+class KafkaResponseProcessorTest {
+
+ private lateinit var eventPublisher: ApplicationEventPublisher
+ private lateinit var objectMapper: ObjectMapper
+
+ private lateinit var kafkaResponseProcessor: KafkaResponseProcessor
+
+ private fun createKafkaRecord(
+ requestId: String,
+ statusCode: Int = 200,
+ statusBody: Map<String, Any>? = mapOf()
+ ): ConsumerRecord<String, String> {
+ return ConsumerRecord<String, String>(
+ "test-topic",
+ 0,
+ 0,
+ null,
+ if (statusBody == null) {
+ ""
+ } else {
+ this.objectMapper.writeValueAsString(KafkaResponseProcessor.ResponseBody(requestId, statusCode, statusBody))
+ }
+ )
+ }
+
+ @BeforeEach
+ fun setup(
+ @Mock eventPublisher: ApplicationEventPublisher
+ ) {
+ this.eventPublisher = eventPublisher
+ this.objectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build())
+
+ this.kafkaResponseProcessor = KafkaResponseProcessor(eventPublisher, objectMapper)
+ }
+
+ @Test
+ fun shouldNotProcessRecordsWithoutRequestIdInBody() {
+ val record = ConsumerRecord<String, String>(
+ "test-topic",
+ 0,
+ 0,
+ null,
+ """
+ {
+ "statusCode": 200,
+ "statusBody": {}
+ }
+ """.trimIndent()
+ )
+
+ this.kafkaResponseProcessor.onMessage(record)
+
+ verify(eventPublisher, never()).publishEvent(any<ResponseEvent>())
+ }
+
+ @Test
+ fun shouldProcessRecordsWithAliasNames() {
+ val record = ConsumerRecord<String, String>(
+ "test-topic",
+ 0,
+ 0,
+ null,
+ """
+ {
+ "request_id": "test0123456789",
+ "status_code": 200,
+ "status_body": {}
+ }
+ """.trimIndent()
+ )
+
+ this.kafkaResponseProcessor.onMessage(record)
+
+ verify(eventPublisher, times(1)).publishEvent(any<ResponseEvent>())
+ }
+
+ @Test
+ fun shouldNotProcessRecordsWithoutValidStatusBody() {
+ this.kafkaResponseProcessor.onMessage(createKafkaRecord(requestId = "TestID1234", statusBody = null))
+
+ verify(eventPublisher, never()).publishEvent(any<ResponseEvent>())
+ }
+
+ @ParameterizedTest
+ @MethodSource("statusCodeSource")
+ fun shouldProcessValidRecordsWithStatusCode(statusCode: Int) {
+ this.kafkaResponseProcessor.onMessage(createKafkaRecord("TestID1234", statusCode))
+ verify(eventPublisher, times(1)).publishEvent(any<ResponseEvent>())
+ }
+
+ companion object {
+
+ @JvmStatic
+ fun statusCodeSource(): Set<Int> {
+ return setOf(
+ HttpStatus.OK,
+ HttpStatus.CREATED,
+ HttpStatus.BAD_REQUEST,
+ HttpStatus.NOT_FOUND,
+ HttpStatus.UNPROCESSABLE_ENTITY,
+ HttpStatus.INTERNAL_SERVER_ERROR
+ )
+ .map { it.value() }
+ .toSet()
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/web/MtbFileRestControllerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/web/MtbFileRestControllerTest.kt
new file mode 100644
index 0000000..2fde35a
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/web/MtbFileRestControllerTest.kt
@@ -0,0 +1,150 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * Copyright (c) 2023 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.web
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import de.ukw.ccc.bwhc.dto.*
+import dev.dnpm.etl.processor.services.RequestProcessor
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.springframework.http.MediaType
+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
+
+@ExtendWith(MockitoExtension::class)
+class MtbFileRestControllerTest {
+
+ private lateinit var mockMvc: MockMvc
+
+ private lateinit var requestProcessor: RequestProcessor
+
+ private val objectMapper = ObjectMapper()
+
+ @BeforeEach
+ fun setup(
+ @Mock requestProcessor: RequestProcessor
+ ) {
+ this.requestProcessor = requestProcessor
+ val controller = MtbFileRestController(requestProcessor)
+ this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
+ }
+
+ @Test
+ fun shouldProcessMtbFilePostRequest() {
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("TEST_12345678")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.ACTIVE)
+ .withPatient("TEST_12345678")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("TEST_12345678")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .build()
+ )
+ .build()
+
+ mockMvc.post("/mtbfile") {
+ content = objectMapper.writeValueAsString(mtbFile)
+ contentType = MediaType.APPLICATION_JSON
+ }.andExpect {
+ status {
+ isAccepted()
+ }
+ }
+
+ verify(requestProcessor, times(1)).processMtbFile(any())
+ }
+
+ @Test
+ fun shouldProcessMtbFilePostRequestWithRejectedConsent() {
+ val mtbFile = MtbFile.builder()
+ .withPatient(
+ Patient.builder()
+ .withId("TEST_12345678")
+ .withBirthDate("2000-08-08")
+ .withGender(Patient.Gender.MALE)
+ .build()
+ )
+ .withConsent(
+ Consent.builder()
+ .withId("1")
+ .withStatus(Consent.Status.REJECTED)
+ .withPatient("TEST_12345678")
+ .build()
+ )
+ .withEpisode(
+ Episode.builder()
+ .withId("1")
+ .withPatient("TEST_12345678")
+ .withPeriod(PeriodStart("2023-08-08"))
+ .build()
+ )
+ .build()
+
+ mockMvc.post("/mtbfile") {
+ content = objectMapper.writeValueAsString(mtbFile)
+ contentType = MediaType.APPLICATION_JSON
+ }.andExpect {
+ status {
+ isAccepted()
+ }
+ }
+
+ val captor = argumentCaptor<String>()
+ verify(requestProcessor, times(1)).processDeletion(captor.capture())
+ assertThat(captor.firstValue).isEqualTo("TEST_12345678")
+ }
+
+ @Test
+ fun shouldProcessMtbFileDeleteRequest() {
+ mockMvc.delete("/mtbfile/TEST_12345678").andExpect {
+ status {
+ isAccepted()
+ }
+ }
+
+ val captor = argumentCaptor<String>()
+ verify(requestProcessor, times(1)).processDeletion(captor.capture())
+ assertThat(captor.firstValue).isEqualTo("TEST_12345678")
+ }
+
+} \ No newline at end of file