diff options
Diffstat (limited to 'src/test/kotlin/dev/dnpm')
23 files changed, 809 insertions, 555 deletions
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/FunctionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/FunctionsTest.kt index 716529b..95ef0d4 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/FunctionsTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/FunctionsTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2026 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt new file mode 100644 index 0000000..b940537 --- /dev/null +++ b/src/test/kotlin/dev/dnpm/etl/processor/config/Jackson3ConfigTest.kt @@ -0,0 +1,92 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package dev.dnpm.etl.processor.config + +import com.fasterxml.jackson.databind.node.ObjectNode +import dev.pcvolkmer.mv64e.mtb.Mtb +import dev.pcvolkmer.mv64e.mtb.MvhMetadata +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.util.* + +class Jackson3ConfigTest { + + lateinit var jacksonConfig: JacksonConfig + + @BeforeEach + fun setup() { + this.jacksonConfig = JacksonConfig() + } + + @ParameterizedTest + @ValueSource(strings = [ + "mv64e-mtb-fake-patient.json", + "fake_broadConsent_mii_response_deny.json", + "fake_broadConsent_mii_response_permit.json", + ]) + fun shouldSerializeJsonWithoutNulledOutFields(filename: String) { + val inputJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream(filename))?.readAllBytes()?.decodeToString() + + val jsonNode = this.jacksonConfig.jsonMapper().readTree(inputJson) + val actual = this.jacksonConfig.jsonMapper().writeValueAsString(jsonNode) + + assertThat(actual).doesNotContain("null") + } + + @ParameterizedTest + @ValueSource(strings = [ + "fake_broadConsent_mii_response_deny.json", + "fake_broadConsent_mii_response_permit.json", + ]) + fun shouldSerializeConsentWithoutWithoutDatesAsTimestamps(filename: String) { + val inputJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream(filename))?.readAllBytes()?.decodeToString() + + val json = this.jacksonConfig.jsonMapper().readTree(inputJson) + val actual = this.jacksonConfig.jsonMapper().writeValueAsString(json) + + assertThat(actual).contains(""""lastUpdated":"2025-08-15T11:13:59.143+02:00"""") + } + + @Test + fun shouldSerializeJsonWithBroadConsent() { + val inputMtbFileJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("mv64e-mtb-fake-patient.json"))?.readAllBytes()?.decodeToString() + + val inputConsentJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("fake_broadConsent_mii_response_permit.json"))?.readAllBytes()?.decodeToString() + + val mtb = this.jacksonConfig.jsonMapper().readValue<Mtb>(inputMtbFileJson, Mtb::class.java) + // Still use Jackson2 ObjectMapper since MTB DTO requires Jackson2 ObjectNode + val consentJsonNode = Jackson2Config().objectMapper().readTree(inputConsentJson) + mtb.metadata = MvhMetadata.builder().researchConsents(listOf(MvhMetadata.ResearchConsent.from(consentJsonNode as ObjectNode))).build() + + val actual = this.jacksonConfig.jsonMapper().writeValueAsString(mtb) + + assertThat(actual).doesNotContain("null") + assertThat(actual).contains(""""lastUpdated":"2025-08-15T11:13:59.143+02:00"""") + assertThat(actual).contains("""{"entry":[{"fullUrl":"http://localhost:8080/ttp-fhir/fhir/gics/Consent/7d3456c2-79b1-11f0-ab27-6ed0ed82d0fd"""") + } +} diff --git a/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt index 9042d8c..e734c6f 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/config/JacksonConfigTest.kt @@ -1,5 +1,28 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package dev.dnpm.etl.processor.config +import com.fasterxml.jackson.databind.node.ObjectNode +import dev.pcvolkmer.mv64e.mtb.Mtb +import dev.pcvolkmer.mv64e.mtb.MvhMetadata import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -9,11 +32,11 @@ import java.util.* class JacksonConfigTest { - lateinit var jacksonConfig: JacksonConfig + lateinit var jacksonConfig: Jackson2Config @BeforeEach fun setup() { - this.jacksonConfig = JacksonConfig() + this.jacksonConfig = Jackson2Config() } @ParameterizedTest @@ -26,8 +49,8 @@ class JacksonConfigTest { val inputJson = Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream(filename))?.readAllBytes()?.decodeToString() - val json = this.jacksonConfig.objectMapper().readTree(inputJson) - val actual = this.jacksonConfig.objectMapper().writeValueAsString(json) + val jsonNode = this.jacksonConfig.objectMapper().readTree(inputJson) + val actual = this.jacksonConfig.objectMapper().writeValueAsString(jsonNode) assertThat(actual).doesNotContain("null") } @@ -47,4 +70,23 @@ class JacksonConfigTest { assertThat(actual).contains(""""lastUpdated":"2025-08-15T11:13:59.143+02:00"""") } + @Test + fun shouldSerializeJsonWithBroadConsent() { + val inputMtbFileJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("mv64e-mtb-fake-patient.json"))?.readAllBytes()?.decodeToString() + + val inputConsentJson = + Objects.requireNonNull(this.javaClass.classLoader.getResourceAsStream("fake_broadConsent_mii_response_permit.json"))?.readAllBytes()?.decodeToString() + + val mtb = this.jacksonConfig.objectMapper().readValue<Mtb>(inputMtbFileJson, Mtb::class.java) + val consentJsonNode = this.jacksonConfig.objectMapper().readTree(inputConsentJson) + mtb.metadata = MvhMetadata.builder().researchConsents(listOf(MvhMetadata.ResearchConsent.from(consentJsonNode as ObjectNode))).build() + + val actual = this.jacksonConfig.objectMapper().writeValueAsString(mtb) + + assertThat(actual).doesNotContain("null") + assertThat(actual).contains(""""lastUpdated":"2025-08-15T11:13:59.143+02:00"""") + assertThat(actual).contains("""{"entry":[{"fullUrl":"http://localhost:8080/ttp-fhir/fhir/gics/Consent/7d3456c2-79b1-11f0-ab27-6ed0ed82d0fd"""") + } + } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt index 1140425..30e3823 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/consent/ConsentProcessorTest.kt @@ -1,11 +1,29 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package dev.dnpm.etl.processor.consent import ca.uhn.fhir.context.FhirContext -import com.fasterxml.jackson.databind.ObjectMapper import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.GIcsConfigProperties import dev.dnpm.etl.processor.services.ConsentProcessor -import java.util.* import org.assertj.core.api.Assertions.assertThat import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.Consent @@ -15,13 +33,15 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension +import tools.jackson.databind.json.JsonMapper +import java.util.* @ExtendWith(MockitoExtension::class) class ConsentProcessorTest { lateinit var consentProcessor: ConsentProcessor - val objectMapper = ObjectMapper() + val jsonMapper = JsonMapper() val fhirContext = FhirContext.forR4() @BeforeEach @@ -33,7 +53,7 @@ class ConsentProcessorTest { ConsentProcessor( appConfigProperties, gIcsConfigProperties, - objectMapper, + jsonMapper, fhirContext, consentService, ) diff --git a/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt index 85a8b3e..284c552 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt b/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt index b7bf436..192bf90 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2024-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt index 0d3f275..eeb6288 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -43,13 +44,14 @@ import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.firstValue import org.mockito.kotlin.times import org.mockito.kotlin.verify +import tools.jackson.databind.json.JsonMapper import java.util.* @ExtendWith(MockitoExtension::class) class KafkaInputListenerTest { private lateinit var requestProcessor: RequestProcessor - private lateinit var objectMapper: ObjectMapper + private lateinit var jsonMapper: JsonMapper private lateinit var kafkaInputListener: KafkaInputListener @@ -59,9 +61,9 @@ class KafkaInputListenerTest { @Mock consentEvaluator: ConsentEvaluator, ) { this.requestProcessor = requestProcessor - this.objectMapper = ObjectMapper() + this.jsonMapper = JsonMapper() - this.kafkaInputListener = KafkaInputListener(requestProcessor, consentEvaluator, objectMapper) + this.kafkaInputListener = KafkaInputListener(requestProcessor, consentEvaluator, jsonMapper) } @Test @@ -88,7 +90,7 @@ class KafkaInputListenerTest { .build() kafkaInputListener.onMessage( - ConsumerRecord("testtopic", 0, 0, "", this.objectMapper.writeValueAsString(mtbFile)) + ConsumerRecord("testtopic", 0, 0, "", this.jsonMapper.writeValueAsString(mtbFile)) ) verify(requestProcessor, times(1)).processMtbFile(any<Mtb>()) @@ -113,7 +115,7 @@ class KafkaInputListenerTest { .build() kafkaInputListener.onMessage( - ConsumerRecord("testtopic", 0, 0, "", this.objectMapper.writeValueAsString(mtbFile)) + ConsumerRecord("testtopic", 0, 0, "", this.jsonMapper.writeValueAsString(mtbFile)) ) verify(requestProcessor, times(1)).processMtbFile(any<Mtb>()) } @@ -153,7 +155,7 @@ class KafkaInputListenerTest { -1, -1, "", - this.objectMapper.writeValueAsString(mtbFile), + this.jsonMapper.writeValueAsString(mtbFile), headers, Optional.empty(), ) @@ -197,7 +199,7 @@ class KafkaInputListenerTest { -1, -1, "", - this.objectMapper.writeValueAsString(mtbFile), + this.jsonMapper.writeValueAsString(mtbFile), headers, Optional.empty(), ) @@ -248,7 +250,7 @@ class KafkaInputListenerTest { -1, -1, "", - this.objectMapper.writeValueAsString(mtbFile), + this.jsonMapper.writeValueAsString(mtbFile), headers, Optional.empty(), ) @@ -303,7 +305,7 @@ class KafkaInputListenerTest { -1, -1, "", - this.objectMapper.writeValueAsString(mtbFile), + this.jsonMapper.writeValueAsString(mtbFile), headers, Optional.empty(), ) diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt index c8e5804..9ee4b2b 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt index a380d2a..62ece6c 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt @@ -1,3 +1,23 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package dev.dnpm.etl.processor.monitoring import dev.dnpm.etl.processor.config.GIcsConfigProperties diff --git a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ReportServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ReportServiceTest.kt index 74d1138..83c2354 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ReportServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ReportServiceTest.kt @@ -1,3 +1,23 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package dev.dnpm.etl.processor.monitoring import dev.dnpm.etl.processor.config.JacksonConfig @@ -12,8 +32,8 @@ class ReportServiceTest { @BeforeEach fun setUp() { - val jacksonConfig = JacksonConfig() - service = ReportService(jacksonConfig.objectMapper()) + val jackson3Config = JacksonConfig() + service = ReportService(jackson3Config.jsonMapper()) } @Test diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt index ac14366..ee840a8 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/output/KafkaMtbFileSenderTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -19,17 +20,12 @@ package dev.dnpm.etl.processor.output -import com.fasterxml.jackson.databind.ObjectMapper import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.config.KafkaProperties import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.pcvolkmer.mv64e.mtb.* -import java.time.Instant -import java.util.* -import java.util.concurrent.CompletableFuture.completedFuture -import java.util.concurrent.ExecutionException import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.clients.producer.RecordMetadata import org.apache.kafka.common.TopicPartition @@ -47,6 +43,11 @@ import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.support.SendResult import org.springframework.retry.policy.SimpleRetryPolicy import org.springframework.retry.support.RetryTemplateBuilder +import tools.jackson.databind.json.JsonMapper +import java.time.Instant +import java.util.* +import java.util.concurrent.CompletableFuture.completedFuture +import java.util.concurrent.ExecutionException @ExtendWith(MockitoExtension::class) class KafkaMtbFileSenderTest { @@ -58,18 +59,18 @@ class KafkaMtbFileSenderTest { private lateinit var kafkaMtbFileSender: KafkaMtbFileSender - private lateinit var objectMapper: ObjectMapper + private lateinit var jsonMapper: JsonMapper @BeforeEach fun setup(@Mock kafkaTemplate: KafkaTemplate<String, String>) { val kafkaProperties = KafkaProperties("testtopic") val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() - this.objectMapper = ObjectMapper() + this.jsonMapper = JsonMapper.builder().build() this.kafkaTemplate = kafkaTemplate this.kafkaMtbFileSender = - KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, objectMapper) + KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, jsonMapper) } @ParameterizedTest @@ -94,7 +95,7 @@ class KafkaMtbFileSenderTest { val kafkaProperties = KafkaProperties("testtopic") val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() this.kafkaMtbFileSender = - KafkaMtbFileSender(this.kafkaTemplate, kafkaProperties, retryTemplate, this.objectMapper) + KafkaMtbFileSender(this.kafkaTemplate, kafkaProperties, retryTemplate, this.jsonMapper) doAnswer { if (null != testData.exception) { @@ -126,18 +127,18 @@ class KafkaMtbFileSenderTest { private lateinit var kafkaMtbFileSender: KafkaMtbFileSender - private lateinit var objectMapper: ObjectMapper + private lateinit var jsonMapper: JsonMapper @BeforeEach fun setup(@Mock kafkaTemplate: KafkaTemplate<String, String>) { val kafkaProperties = KafkaProperties("testtopic") val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() - this.objectMapper = ObjectMapper() + this.jsonMapper = JsonMapper() this.kafkaTemplate = kafkaTemplate this.kafkaMtbFileSender = - KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, objectMapper) + KafkaMtbFileSender(kafkaTemplate, kafkaProperties, retryTemplate, jsonMapper) } @ParameterizedTest @@ -179,7 +180,7 @@ class KafkaMtbFileSenderTest { .isEqualTo("POST".toByteArray()) assertThat(captor.firstValue.value()).isNotNull assertThat(captor.firstValue.value()) - .isEqualTo(objectMapper.writeValueAsString(dnmpV2kafkaRecordData(TEST_REQUEST_ID))) + .isEqualTo(jsonMapper.writeValueAsString(dnmpV2kafkaRecordData(TEST_REQUEST_ID))) } @Test @@ -212,7 +213,7 @@ class KafkaMtbFileSenderTest { val kafkaProperties = KafkaProperties("testtopic") val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() this.kafkaMtbFileSender = - KafkaMtbFileSender(this.kafkaTemplate, kafkaProperties, retryTemplate, this.objectMapper) + KafkaMtbFileSender(this.kafkaTemplate, kafkaProperties, retryTemplate, this.jsonMapper) doAnswer { if (null != testData.exception) { diff --git a/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt index 2ab0218..d95ced0 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -19,8 +20,6 @@ package dev.dnpm.etl.processor.output -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.KotlinModule import dev.dnpm.etl.processor.CustomMediaType import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.RequestId @@ -30,8 +29,6 @@ import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.pcvolkmer.mv64e.mtb.* -import java.time.Instant -import java.util.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested @@ -48,252 +45,255 @@ import org.springframework.test.web.client.MockRestServiceServer import org.springframework.test.web.client.match.MockRestRequestMatchers.* import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus import org.springframework.web.client.RestTemplate +import tools.jackson.databind.json.JsonMapper +import java.time.Instant +import java.util.* class RestDipMtbFileSenderTest { - @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) - val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() - - this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) - - this.restMtbFileSender = - RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) - } + @Nested + inner class DnpmV2ContentRequest { + + private lateinit var mockRestServiceServer: MockRestServiceServer + + private lateinit var restMtbFileSender: RestMtbFileSender + + private var reportService = + ReportService(JsonMapper()) + + @BeforeEach + fun setup() { + val restTemplate = RestTemplate() + val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null) + 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) + } - @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) + 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) - val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() + @Nested + inner class DeleteRequest { + + private lateinit var mockRestServiceServer: MockRestServiceServer + + private lateinit var restMtbFileSender: RestMtbFileSender + + private var reportService = + ReportService(JsonMapper()) + + @BeforeEach + fun setup() { + val restTemplate = RestTemplate() + val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null) + 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) + } - this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) + val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) + assertThat(response.status).isEqualTo(requestWithResponse.response.status) + assertThat(response.body).isEqualTo(requestWithResponse.response.body) + } + + @ParameterizedTest + @MethodSource( + "dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#deleteRequestWithResponseSource" + ) + fun shouldRetryOnDeleteHttpRequestError(requestWithResponse: RequestWithResponse) { + val restTemplate = RestTemplate() + val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null) + val retryTemplate = AppConfiguration().retryTemplate(AppConfigProperties()) + retryTemplate.setBackOffPolicy(NoBackOffPolicy()) + + this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) + this.restMtbFileSender = + RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) + + val expectedCount = + when (requestWithResponse.httpStatus) { + // OK - No Retry + HttpStatus.OK, + HttpStatus.CREATED, + HttpStatus.UNPROCESSABLE_ENTITY, + HttpStatus.BAD_REQUEST -> ExpectedCount.max(1) + // Request failed - Retry max 3 times + else -> ExpectedCount.max(3) + } - this.restMtbFileSender = - RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) - } + this.mockRestServiceServer + .expect(expectedCount, 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) + } - @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.body).isEqualTo(requestWithResponse.response.body) + val response = restMtbFileSender.send(DeleteRequest(TEST_REQUEST_ID, TEST_PATIENT_PSEUDONYM)) + assertThat(response.status).isEqualTo(requestWithResponse.response.status) + assertThat(response.body).isEqualTo(requestWithResponse.response.body) + } } - @ParameterizedTest - @MethodSource( - "dev.dnpm.etl.processor.output.RestDipMtbFileSenderTest#deleteRequestWithResponseSource" - ) - fun shouldRetryOnDeleteHttpRequestError(requestWithResponse: RequestWithResponse) { - val restTemplate = RestTemplate() - val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null) - val retryTemplate = AppConfiguration().retryTemplate(AppConfigProperties()) - retryTemplate.setBackOffPolicy(NoBackOffPolicy()) - - this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) - this.restMtbFileSender = - RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) - - val expectedCount = - when (requestWithResponse.httpStatus) { - // OK - No Retry - HttpStatus.OK, - HttpStatus.CREATED, - HttpStatus.UNPROCESSABLE_ENTITY, - HttpStatus.BAD_REQUEST -> ExpectedCount.max(1) - // Request failed - Retry max 3 times - else -> ExpectedCount.max(3) - } - - this.mockRestServiceServer - .expect(expectedCount, 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.body).isEqualTo(requestWithResponse.response.body) - } - } - - companion object { - data class RequestWithResponse( - val httpStatus: HttpStatus, - val body: String, - val response: MtbFileSender.Response, - ) - - val TEST_REQUEST_ID = RequestId("TestId") - val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") - - fun dnpmV2MtbFile(): Mtb { - return Mtb().apply { - this.patient = - dev.pcvolkmer.mv64e.mtb.Patient().apply { - this.id = "PID" - this.birthDate = Date.from(Instant.now()) - this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + companion object { + data class RequestWithResponse( + val httpStatus: HttpStatus, + val body: String, + val response: MtbFileSender.Response, + ) + + val TEST_REQUEST_ID = RequestId("TestId") + val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") + + fun dnpmV2MtbFile(): Mtb { + return Mtb().apply { + this.patient = + Patient().apply { + this.id = "PID" + this.birthDate = Date.from(Instant.now()) + this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + } + this.episodesOfCare = + listOf( + MtbEpisodeOfCare().apply { + this.id = "1" + this.patient = Reference().apply { this.id = "PID" } + this.period = PeriodDate().apply { this.start = Date.from(Instant.now()) } + } + ) } - this.episodesOfCare = - listOf( - MtbEpisodeOfCare().apply { - this.id = "1" - this.patient = Reference().apply { this.id = "PID" } - this.period = PeriodDate().apply { this.start = Date.from(Instant.now()) } - } + } + + 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, + responseBodyWithMaxSeverity(ReportService.Severity.INFO), + MtbFileSender.Response( + RequestStatus.SUCCESS, + responseBodyWithMaxSeverity(ReportService.Severity.INFO), + ), + ), + RequestWithResponse( + HttpStatus.CREATED, + responseBodyWithMaxSeverity(ReportService.Severity.WARNING), + MtbFileSender.Response( + RequestStatus.WARNING, + responseBodyWithMaxSeverity(ReportService.Severity.WARNING), + ), + ), + RequestWithResponse( + HttpStatus.BAD_REQUEST, + responseBodyWithMaxSeverity(ReportService.Severity.ERROR), + MtbFileSender.Response( + RequestStatus.ERROR, + responseBodyWithMaxSeverity(ReportService.Severity.ERROR), + ), + ), + RequestWithResponse( + HttpStatus.UNPROCESSABLE_ENTITY, + responseBodyWithMaxSeverity(ReportService.Severity.ERROR), + MtbFileSender.Response( + RequestStatus.ERROR, + responseBodyWithMaxSeverity(ReportService.Severity.ERROR), + ), + ), + // Some more errors not mentioned in documentation + RequestWithResponse( + HttpStatus.NOT_FOUND, + ERROR_RESPONSE_BODY, + MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY), + ), + RequestWithResponse( + HttpStatus.INTERNAL_SERVER_ERROR, + ERROR_RESPONSE_BODY, + MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY), + ), ) - } - } - - 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, - responseBodyWithMaxSeverity(ReportService.Severity.INFO), - MtbFileSender.Response( - RequestStatus.SUCCESS, - responseBodyWithMaxSeverity(ReportService.Severity.INFO), - ), - ), - RequestWithResponse( - HttpStatus.CREATED, - responseBodyWithMaxSeverity(ReportService.Severity.WARNING), - MtbFileSender.Response( - RequestStatus.WARNING, - responseBodyWithMaxSeverity(ReportService.Severity.WARNING), - ), - ), - RequestWithResponse( - HttpStatus.BAD_REQUEST, - responseBodyWithMaxSeverity(ReportService.Severity.ERROR), - MtbFileSender.Response( - RequestStatus.ERROR, - responseBodyWithMaxSeverity(ReportService.Severity.ERROR), - ), - ), - RequestWithResponse( - HttpStatus.UNPROCESSABLE_ENTITY, - responseBodyWithMaxSeverity(ReportService.Severity.ERROR), - MtbFileSender.Response( - RequestStatus.ERROR, - responseBodyWithMaxSeverity(ReportService.Severity.ERROR), - ), - ), - // Some more errors not mentioned in documentation - RequestWithResponse( - HttpStatus.NOT_FOUND, - ERROR_RESPONSE_BODY, - MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY), - ), - RequestWithResponse( - HttpStatus.INTERNAL_SERVER_ERROR, - ERROR_RESPONSE_BODY, - 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), - ), - ) - } + } + + /** + * 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), + ), + ) + } - fun responseBodyWithMaxSeverity(severity: ReportService.Severity): String { - return when (severity) { - ReportService.Severity.INFO -> - """ + fun responseBodyWithMaxSeverity(severity: ReportService.Severity): String { + return when (severity) { + ReportService.Severity.INFO -> + """ { "patient": "PID", "issues": [ @@ -302,8 +302,8 @@ class RestDipMtbFileSenderTest { } """ - ReportService.Severity.WARNING -> - """ + ReportService.Severity.WARNING -> + """ { "patient": "PID", "issues": [ @@ -313,8 +313,8 @@ class RestDipMtbFileSenderTest { } """ - ReportService.Severity.ERROR -> - """ + ReportService.Severity.ERROR -> + """ { "patient": "PID", "issues": [ @@ -325,8 +325,8 @@ class RestDipMtbFileSenderTest { } """ - ReportService.Severity.FATAL -> - """ + ReportService.Severity.FATAL -> + """ { "patient": "PID", "issues": [ @@ -337,7 +337,7 @@ class RestDipMtbFileSenderTest { ] } """ - } + } + } } - } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt index 84b081a..abc17c6 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2025-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -20,7 +21,6 @@ package dev.dnpm.etl.processor.pseudonym import ca.uhn.fhir.context.FhirContext -import com.fasterxml.jackson.databind.ObjectMapper import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.GIcsConfigProperties import dev.dnpm.etl.processor.config.JacksonConfig @@ -28,8 +28,6 @@ import dev.dnpm.etl.processor.consent.MtbFileConsentService import dev.dnpm.etl.processor.services.ConsentProcessor import dev.dnpm.etl.processor.services.ConsentProcessorTest import dev.pcvolkmer.mv64e.mtb.* -import java.time.Instant -import java.util.* import org.assertj.core.api.Assertions.assertThat import org.hl7.fhir.r4.model.Bundle import org.junit.jupiter.api.Nested @@ -42,253 +40,256 @@ import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.doAnswer import org.mockito.kotlin.whenever import org.springframework.core.io.ClassPathResource +import tools.jackson.databind.json.JsonMapper +import java.time.Instant +import java.util.* @ExtendWith(MockitoExtension::class) class ExtensionsTest { - fun getObjectMapper(): ObjectMapper { - return JacksonConfig().objectMapper() - } + fun getJsonMapper(): JsonMapper { + return JacksonConfig().jsonMapper() + } - @Nested - inner class UsingDnpmV2Datamodel { + @Nested + inner class UsingDnpmV2Datamodel { - val FAKE_MTB_FILE_PATH = "mv64e-mtb-fake-patient.json" - val CLEAN_PATIENT_ID = "644bae7a-56f6-4ee8-b02f-c532e65af5b1" + val FAKE_MTB_FILE_PATH = "mv64e-mtb-fake-patient.json" + val CLEAN_PATIENT_ID = "644bae7a-56f6-4ee8-b02f-c532e65af5b1" - private fun fakeMtbFile(): Mtb { - val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream - return getObjectMapper().readValue(mtbFile, Mtb::class.java) - } + private fun fakeMtbFile(): Mtb { + val mtbFile = ClassPathResource(FAKE_MTB_FILE_PATH).inputStream + return getJsonMapper().readValue(mtbFile, Mtb::class.java) + } - private fun Mtb.serialized(): String { - return getObjectMapper().writeValueAsString(this) - } + private fun Mtb.serialized(): String { + return getJsonMapper().writeValueAsString(this) + } - @Test - fun shouldNotContainCleanPatientId(@Mock pseudonymizeService: PseudonymizeService) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" - } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + @Test + fun shouldNotContainCleanPatientId(@Mock pseudonymizeService: PseudonymizeService) { + doAnswer { + it.arguments[0] + "PSEUDO-ID" + } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) - val mtbFile = fakeMtbFile() - mtbFile.ensureMetaDataIsInitialized() - addConsentData(mtbFile) + val mtbFile = fakeMtbFile() + mtbFile.ensureMetaDataIsInitialized() + addConsentData(mtbFile) - mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.pseudonymizeWith(pseudonymizeService) - assertThat(mtbFile.patient.id).isEqualTo("PSEUDO-ID") - assertThat(mtbFile.serialized()).doesNotContain(CLEAN_PATIENT_ID) - } + assertThat(mtbFile.patient.id).isEqualTo("PSEUDO-ID") + assertThat(mtbFile.serialized()).doesNotContain(CLEAN_PATIENT_ID) + } - private fun addConsentData(mtbFile: Mtb) { - val gIcsConfigProperties = GIcsConfigProperties("", "", "") - val appConfigProperties = AppConfigProperties(emptyList()) - - val bundle = Bundle() - val dummyConsent = ConsentProcessorTest.getDummyGenomDeConsent() - dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID" - bundle.addEntry().resource = dummyConsent - - ConsentProcessor( - appConfigProperties, - gIcsConfigProperties, - JacksonConfig().objectMapper(), - FhirContext.forR4(), - MtbFileConsentService(), - ) - .embedBroadConsentResources(mtbFile, bundle) - } + private fun addConsentData(mtbFile: Mtb) { + val gIcsConfigProperties = GIcsConfigProperties("", "", "") + val appConfigProperties = AppConfigProperties(emptyList()) + + val bundle = Bundle() + val dummyConsent = ConsentProcessorTest.getDummyGenomDeConsent() + dummyConsent.patient.reference = "Patient/$CLEAN_PATIENT_ID" + bundle.addEntry().resource = dummyConsent + + ConsentProcessor( + appConfigProperties, + gIcsConfigProperties, + JacksonConfig().jsonMapper(), + FhirContext.forR4(), + MtbFileConsentService(), + ) + .embedBroadConsentResources(mtbFile, bundle) + } - @Test - fun shouldNotThrowExceptionOnNullValues(@Mock pseudonymizeService: PseudonymizeService) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" - } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - - val mtbFile = - Mtb().apply { - this.patient = - Patient().apply { - this.id = "PID" - this.birthDate = Date.from(Instant.now()) - this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + @Test + fun shouldNotThrowExceptionOnNullValues(@Mock pseudonymizeService: PseudonymizeService) { + doAnswer { + it.arguments[0] + "PSEUDO-ID" + } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() + + val mtbFile = + Mtb().apply { + this.patient = + Patient().apply { + this.id = "PID" + this.birthDate = Date.from(Instant.now()) + this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + } + this.episodesOfCare = + listOf( + MtbEpisodeOfCare().apply { + this.id = "1" + this.patient = Reference().apply { this.id = "PID" } + this.period = PeriodDate().apply { this.start = Date.from(Instant.now()) } + } + ) } - this.episodesOfCare = - listOf( - MtbEpisodeOfCare().apply { - this.id = "1" - this.patient = Reference().apply { this.id = "PID" } - this.period = PeriodDate().apply { this.start = Date.from(Instant.now()) } - } - ) - } - mtbFile.pseudonymizeWith(pseudonymizeService) - mtbFile.anonymizeContentWith(pseudonymizeService) + mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.anonymizeContentWith(pseudonymizeService) - assertThat(mtbFile.episodesOfCare).hasSize(1) - assertThat(mtbFile.episodesOfCare.map { it.id }).isNotNull - } + assertThat(mtbFile.episodesOfCare).hasSize(1) + assertThat(mtbFile.episodesOfCare.map { it.id }).isNotNull + } - @Test - fun shouldNotContainAnyUuidAfterRehashingOfIds(@Mock pseudonymizeService: PseudonymizeService) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" - } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) + @Test + fun shouldNotContainAnyUuidAfterRehashingOfIds(@Mock pseudonymizeService: PseudonymizeService) { + doAnswer { + it.arguments[0] + "PSEUDO-ID" + } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) - doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() + doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - val mtbFile = fakeMtbFile() + val mtbFile = fakeMtbFile() - /** replace hex values with random long, so our test does not match false positives */ - mtbFile.ngsReports.forEach { report -> - report.results.simpleVariants.forEach { simpleVariant -> - simpleVariant.externalIds.forEach { extIdValue -> - extIdValue.value = Math.random().toLong().toString() - } - } - } - mtbFile.ngsReports.forEach { report -> - report.results.rnaFusions.forEach { simpleVariant -> - simpleVariant.externalIds.forEach { extIdValue -> - extIdValue.value = Math.random().toLong().toString() - } - simpleVariant.fusionPartner3Prime?.transcriptId?.value = Math.random().toLong().toString() - simpleVariant.fusionPartner5Prime?.transcriptId?.value = Math.random().toLong().toString() - simpleVariant.externalIds?.forEach { it?.value = Math.random().toLong().toString() } + /** replace hex values with random long, so our test does not match false positives */ + mtbFile.ngsReports.forEach { report -> + report.results.simpleVariants.forEach { simpleVariant -> + simpleVariant.externalIds.forEach { extIdValue -> + extIdValue.value = Math.random().toLong().toString() + } + } + } + mtbFile.ngsReports.forEach { report -> + report.results.rnaFusions.forEach { simpleVariant -> + simpleVariant.externalIds.forEach { extIdValue -> + extIdValue.value = Math.random().toLong().toString() + } + simpleVariant.fusionPartner3Prime?.transcriptId?.value = Math.random().toLong().toString() + simpleVariant.fusionPartner5Prime?.transcriptId?.value = Math.random().toLong().toString() + simpleVariant.externalIds?.forEach { it?.value = Math.random().toLong().toString() } + } + } + + mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.anonymizeContentWith(pseudonymizeService) + + val pattern = + "\"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\"".toRegex().toPattern() + val input = mtbFile.serialized() + val matcher = pattern.matcher(input) + + assertThrows<IllegalStateException> { + matcher.find() + val posSt = "check at pos: " + matcher.start().toString() + ", " + matcher.end() + println(posSt + " with " + matcher.group()) + } + .also { assertThat(it.message).isEqualTo("No match found") } } - } - - mtbFile.pseudonymizeWith(pseudonymizeService) - mtbFile.anonymizeContentWith(pseudonymizeService) - - val pattern = - "\"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\"".toRegex().toPattern() - val input = mtbFile.serialized() - val matcher = pattern.matcher(input) - - assertThrows<IllegalStateException> { - matcher.find() - val posSt = "check at pos: " + matcher.start().toString() + ", " + matcher.end() - println(posSt + " with " + matcher.group()) - } - .also { assertThat(it.message).isEqualTo("No match found") } } - } - @Test - fun shouldUseSameAnonymIdForDiagnosisAndDiagnosisReferences( - @Mock pseudonymizeService: PseudonymizeService - ) { + @Test + fun shouldUseSameAnonymIdForDiagnosisAndDiagnosisReferences( + @Mock pseudonymizeService: PseudonymizeService + ) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" - } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - - val mtbFile = - Mtb().apply { - this.patient = - Patient().apply { - this.id = "PID" - this.birthDate = Date.from(Instant.now()) - this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } - } - this.diagnoses = listOf(MtbDiagnosis().apply { this.id = "Diagnosis-1" }) - this.episodesOfCare = - listOf( - MtbEpisodeOfCare().apply { - this.id = "Episode-1" - this.diagnoses = listOf(Reference().apply { this.id = "Diagnosis-1" }) - } - ) - this.guidelineTherapies = - listOf( - MtbSystemicTherapy().apply { - this.id = "Systemic-Therapy-1" - this.reason = Reference().apply { this.id = "Diagnosis-1" } - } - ) - this.guidelineProcedures = - listOf( - OncoProcedure().apply { - this.id = "Onco-Procedure-1" - this.reason = Reference().apply { this.id = "Diagnosis-1" } - } - ) - this.specimens = - listOf( - TumorSpecimen().apply { - this.id = "Specimen-1" - this.diagnosis = Reference().apply { this.id = "Diagnosis-1" } - } - ) + doAnswer { + it.arguments[0] + "PSEUDO-ID" } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() + + val mtbFile = + Mtb().apply { + this.patient = + Patient().apply { + this.id = "PID" + this.birthDate = Date.from(Instant.now()) + this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + } + this.diagnoses = listOf(MtbDiagnosis().apply { this.id = "Diagnosis-1" }) + this.episodesOfCare = + listOf( + MtbEpisodeOfCare().apply { + this.id = "Episode-1" + this.diagnoses = listOf(Reference().apply { this.id = "Diagnosis-1" }) + } + ) + this.guidelineTherapies = + listOf( + MtbSystemicTherapy().apply { + this.id = "Systemic-Therapy-1" + this.reason = Reference().apply { this.id = "Diagnosis-1" } + } + ) + this.guidelineProcedures = + listOf( + OncoProcedure().apply { + this.id = "Onco-Procedure-1" + this.reason = Reference().apply { this.id = "Diagnosis-1" } + } + ) + this.specimens = + listOf( + TumorSpecimen().apply { + this.id = "Specimen-1" + this.diagnosis = Reference().apply { this.id = "Diagnosis-1" } + } + ) + } + + mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.anonymizeContentWith(pseudonymizeService) + + assertThat(mtbFile.diagnoses.first().id) + .isEqualTo(mtbFile.episodesOfCare.first().diagnoses.first().id) + assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.guidelineTherapies.first().reason.id) + assertThat(mtbFile.diagnoses.first().id) + .isEqualTo(mtbFile.guidelineProcedures.first().reason.id) + assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.specimens.first().diagnosis.id) + } - mtbFile.pseudonymizeWith(pseudonymizeService) - mtbFile.anonymizeContentWith(pseudonymizeService) - - assertThat(mtbFile.diagnoses.first().id) - .isEqualTo(mtbFile.episodesOfCare.first().diagnoses.first().id) - assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.guidelineTherapies.first().reason.id) - assertThat(mtbFile.diagnoses.first().id) - .isEqualTo(mtbFile.guidelineProcedures.first().reason.id) - assertThat(mtbFile.diagnoses.first().id).isEqualTo(mtbFile.specimens.first().diagnosis.id) - } - - @Test - fun shouldNotThrowAnyExceptionOnMissingMsiId(@Mock pseudonymizeService: PseudonymizeService) { + @Test + fun shouldNotThrowAnyExceptionOnMissingMsiId(@Mock pseudonymizeService: PseudonymizeService) { - doAnswer { - it.arguments[0] - "PSEUDO-ID" - } - .whenever(pseudonymizeService) - .patientPseudonym(anyValueClass()) - - doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() - - val mtbFile = - Mtb().apply { - this.patient = - Patient().apply { - this.id = "PID" - this.birthDate = Date.from(Instant.now()) - this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } - } - this.msiFindings = - listOf( - null, - Msi.builder().id("1").build(), - Msi.builder().build(), - Msi.builder().specimen(null).build(), - Msi.builder().specimen(Reference.builder().build()).build(), - ) + doAnswer { + it.arguments[0] + "PSEUDO-ID" } - - mtbFile.pseudonymizeWith(pseudonymizeService) - mtbFile.anonymizeContentWith(pseudonymizeService) - - assertThat(mtbFile.msiFindings).isNotNull - assertThat(mtbFile.msiFindings[1]) - .satisfiesAnyOf( - { assertThat(it.id).isNull() }, - { assertThat(it.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") }, - ) - } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { "TESTDOMAIN" }.whenever(pseudonymizeService).prefix() + + val mtbFile = + Mtb().apply { + this.patient = + Patient().apply { + this.id = "PID" + this.birthDate = Date.from(Instant.now()) + this.gender = GenderCoding().apply { this.code = GenderCodingCode.MALE } + } + this.msiFindings = + listOf( + null, + Msi.builder().id("1").build(), + Msi.builder().build(), + Msi.builder().specimen(null).build(), + Msi.builder().specimen(Reference.builder().build()).build(), + ) + } + + mtbFile.pseudonymizeWith(pseudonymizeService) + mtbFile.anonymizeContentWith(pseudonymizeService) + + assertThat(mtbFile.msiFindings).isNotNull + assertThat(mtbFile.msiFindings[1]) + .satisfiesAnyOf( + { assertThat(it.id).isNull() }, + { assertThat(it.id).isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") }, + ) + } } diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt index 7da0247..7c6d6c9 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/PseudonymizeServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/security/TokenServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/security/TokenServiceTest.kt index e9a1650..18e0bb5 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/security/TokenServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/security/TokenServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2024-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/security/UserRoleServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/security/UserRoleServiceTest.kt index 7743069..2a5a481 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/security/UserRoleServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/security/UserRoleServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2024-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt index 331a816..a85d555 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt @@ -1,7 +1,26 @@ +/* + * This file is part of ETL-Processor + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + package dev.dnpm.etl.processor.services import ca.uhn.fhir.context.FhirContext -import com.fasterxml.jackson.databind.ObjectMapper import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.GIcsConfigProperties import dev.dnpm.etl.processor.config.JacksonConfig @@ -29,6 +48,7 @@ import org.mockito.kotlin.doAnswer import org.mockito.kotlin.eq import org.mockito.kotlin.whenever import org.springframework.core.io.ClassPathResource +import tools.jackson.databind.json.JsonMapper import java.io.IOException import java.io.InputStream import java.time.Instant @@ -40,7 +60,7 @@ class ConsentProcessorTest { private lateinit var appConfigProperties: AppConfigProperties private lateinit var gicsConsentService: GicsConsentService - private lateinit var objectMapper: ObjectMapper + private lateinit var jsonMapper: JsonMapper private lateinit var gIcsConfigProperties: GIcsConfigProperties private lateinit var fhirContext: FhirContext private lateinit var consentProcessor: ConsentProcessor @@ -52,7 +72,7 @@ class ConsentProcessorTest { this.gIcsConfigProperties = GIcsConfigProperties(uri = "https://gics.example.com", genomDeConsentDomainName = "GenomDE_MV") val jacksonConfig = JacksonConfig() - this.objectMapper = jacksonConfig.objectMapper() + this.jsonMapper = jacksonConfig.jsonMapper() this.fhirContext = JacksonConfig.fhirContext() this.gicsConsentService = gicsConsentService this.appConfigProperties = AppConfigProperties(emptyList()) @@ -60,7 +80,7 @@ class ConsentProcessorTest { ConsentProcessor( appConfigProperties, gIcsConfigProperties, - objectMapper, + jsonMapper, fhirContext, gicsConsentService, ) @@ -94,7 +114,7 @@ class ConsentProcessorTest { ConsentProcessor( appConfigProperties, gIcsConfigProperties, - objectMapper, + jsonMapper, fhirContext, MtbFileConsentService(), ) @@ -220,7 +240,7 @@ class ConsentProcessorTest { ConsentProcessor( appConfigProperties, gIcsConfigProperties, - objectMapper, + jsonMapper, fhirContext, gicsConsentService, ) diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt index 4308fed..e9c3107 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ReportServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -19,8 +20,6 @@ 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 dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.asRequestStatus @@ -30,6 +29,8 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource +import tools.jackson.databind.json.JsonMapper +import tools.jackson.module.kotlin.KotlinModule class ReportServiceTest { private lateinit var reportService: ReportService @@ -37,7 +38,7 @@ class ReportServiceTest { @BeforeEach fun setup() { this.reportService = - ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build())) + ReportService(JsonMapper.builder().addModule(KotlinModule.Builder().build()).build()) } @Test @@ -59,13 +60,34 @@ class ReportServiceTest { assertThat(actual).hasSize(4) assertThat(actual[0].severity).isEqualTo(ReportService.Severity.FATAL) - assertThat(actual[0].message).isEqualTo("Fatal Message") + assertThat(actual[0].getMessage()).isEqualTo("Fatal Message") assertThat(actual[1].severity).isEqualTo(ReportService.Severity.ERROR) - assertThat(actual[1].message).isEqualTo("Error Message") + assertThat(actual[1].getMessage()).isEqualTo("Error Message") assertThat(actual[2].severity).isEqualTo(ReportService.Severity.WARNING) - assertThat(actual[2].message).isEqualTo("Warning Message") + assertThat(actual[2].getMessage()).isEqualTo("Warning Message") assertThat(actual[3].severity).isEqualTo(ReportService.Severity.INFO) - assertThat(actual[3].message).isEqualTo("Info Message") + assertThat(actual[3].getMessage()).isEqualTo("Info Message") + + assertThat(actual.asRequestStatus()).isEqualTo(RequestStatus.ERROR) + } + + @Test + fun shouldParseDataQualityReportWithMissingPathError() { + val json = + """ + { + "patient": "4711", + "issues": [ + { "severity": "error", "details": "/specimens(0)/type/code: error.path.missing" } + ] + } + """.trimIndent() + + val actual = this.reportService.deserialize(json) + + assertThat(actual).hasSize(1) + assertThat(actual[0].severity).isEqualTo(ReportService.Severity.ERROR) + assertThat(actual[0].getMessage()).isEqualTo("/specimens(0)/type/code: error.path.missing") assertThat(actual.asRequestStatus()).isEqualTo(RequestStatus.ERROR) } @@ -88,7 +110,7 @@ class ReportServiceTest { assertThat(actual).hasSize(1) assertThat(actual[0].severity).isEqualTo(ReportService.Severity.ERROR) - assertThat(actual[0].message).isEqualTo("Not parsable data quality report '$invalidResponse'") + assertThat(actual[0].getMessage()).isEqualTo("Not parsable data quality report '$invalidResponse'") } companion object { diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt index b51aed2..5a89c03 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -34,6 +35,7 @@ import dev.dnpm.etl.processor.output.RestMtbFileSender import dev.dnpm.etl.processor.pseudonym.PseudonymizeService import dev.pcvolkmer.mv64e.mtb.* import org.assertj.core.api.Assertions.assertThat +import org.hl7.fhir.r4.model.Consent import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -46,6 +48,7 @@ import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.whenever import org.springframework.context.ApplicationEventPublisher +import tools.jackson.databind.json.JsonMapper import java.time.Instant import java.util.* @@ -60,6 +63,7 @@ class RequestProcessorTest { private lateinit var appConfigProperties: AppConfigProperties private lateinit var consentProcessor: ConsentProcessor private lateinit var requestProcessor: RequestProcessor + private lateinit var jsonMapper: JsonMapper @BeforeEach fun setup( @@ -77,8 +81,7 @@ class RequestProcessorTest { this.applicationEventPublisher = applicationEventPublisher this.appConfigProperties = AppConfigProperties() this.consentProcessor = consentProcessor - - val objectMapper = ObjectMapper() + this.jsonMapper = JsonMapper() requestProcessor = RequestProcessor( @@ -86,7 +89,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - objectMapper, + jsonMapper, applicationEventPublisher, appConfigProperties, consentProcessor, @@ -101,7 +104,7 @@ class RequestProcessorTest { randomRequestId(), PatientPseudonym("TEST_12345678901"), PatientId("P1"), - Fingerprint("6vkiti5bk6ikwifpajpt7cygmd3dvw54d6lwfhzlynb3pqtzferq"), + Fingerprint("syehahte4oyqd5m2rbd5imnth4rx6md32g2msb7sztnayxoc4kaq"), RequestType.MTB_FILE, SubmissionType.TEST, RequestStatus.SUCCESS, @@ -158,7 +161,7 @@ class RequestProcessorTest { randomRequestId(), PatientPseudonym("TEST_12345678901"), PatientId("P1"), - Fingerprint("4gcjwtjjtcczybsljxepdfpkaeusvd7g3vogfqpmphyffyzfx7dq"), + Fingerprint("me6ockoru4boi4ypghfia5myfqtuffwlbszwhtop2rtltb3ycjva"), RequestType.MTB_FILE, SubmissionType.TEST, RequestStatus.SUCCESS, @@ -413,7 +416,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, @@ -637,7 +640,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, @@ -681,6 +684,7 @@ class RequestProcessorTest { private lateinit var appConfigProperties: AppConfigProperties private lateinit var consentProcessor: ConsentProcessor private lateinit var requestProcessor: RequestProcessor + private lateinit var jsonMapper: JsonMapper @BeforeEach fun setup( @@ -698,8 +702,7 @@ class RequestProcessorTest { this.applicationEventPublisher = applicationEventPublisher this.appConfigProperties = AppConfigProperties() this.consentProcessor = consentProcessor - - val objectMapper = ObjectMapper() + this.jsonMapper = JsonMapper() requestProcessor = RequestProcessor( @@ -707,7 +710,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - objectMapper, + jsonMapper, applicationEventPublisher, appConfigProperties, consentProcessor, @@ -779,7 +782,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, @@ -883,7 +886,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, @@ -994,7 +997,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, @@ -1092,7 +1095,7 @@ class RequestProcessorTest { transformationService, sender, requestService, - ObjectMapper(), + jsonMapper, applicationEventPublisher, AppConfigProperties(postInitialSubmissionBlock = true), consentProcessor, diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt index 9b103ba..54b4dce 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt index 3b09cc7..441f3a9 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt index 3af5097..66e45f8 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -24,11 +25,11 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ObjectNode import dev.dnpm.etl.processor.config.JacksonConfig import dev.pcvolkmer.mv64e.mtb.* -import java.time.Instant -import java.util.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import java.time.Instant +import java.util.* class TransformationServiceTest { @@ -38,7 +39,7 @@ class TransformationServiceTest { fun setup() { this.service = TransformationService( - JacksonConfig().objectMapper(), + JacksonConfig().jsonMapper(), listOf( Transformation.of("diagnoses[*].code.version") from "2013" to "2014", ), 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 index 8d5024a..96c9a82 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/kafka/KafkaResponseProcessorTest.kt @@ -1,7 +1,8 @@ /* * This file is part of ETL-Processor * - * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * Copyright (c) 2023-2026 Paul-Christian Volkmer, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -19,8 +20,6 @@ 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 @@ -36,11 +35,13 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.springframework.context.ApplicationEventPublisher import org.springframework.http.HttpStatus +import tools.jackson.databind.json.JsonMapper +import tools.jackson.module.kotlin.KotlinModule @ExtendWith(MockitoExtension::class) class KafkaResponseProcessorTest { private lateinit var eventPublisher: ApplicationEventPublisher - private lateinit var objectMapper: ObjectMapper + private lateinit var jsonMapper: JsonMapper private lateinit var kafkaResponseProcessor: KafkaResponseProcessor @@ -57,7 +58,7 @@ class KafkaResponseProcessorTest { if (statusBody == null) { "" } else { - this.objectMapper.writeValueAsString( + this.jsonMapper.writeValueAsString( KafkaResponseProcessor.ResponseBody(requestId, statusCode, statusBody), ) }, @@ -68,9 +69,9 @@ class KafkaResponseProcessorTest { @Mock eventPublisher: ApplicationEventPublisher, ) { this.eventPublisher = eventPublisher - this.objectMapper = ObjectMapper().registerModule(KotlinModule.Builder().build()) + this.jsonMapper = JsonMapper.builder().addModule(KotlinModule.Builder().build()).build() - this.kafkaResponseProcessor = KafkaResponseProcessor(eventPublisher, objectMapper) + this.kafkaResponseProcessor = KafkaResponseProcessor(eventPublisher, jsonMapper) } @Test |
