diff options
| author | Paul-Christian Volkmer | 2026-01-06 16:34:12 +0100 |
|---|---|---|
| committer | GitHub | 2026-01-06 15:34:12 +0000 |
| commit | 7be91444a867774362eb5b57bdd246fb50189e7d (patch) | |
| tree | 6a325575bf19e4016ead259a92803b110071eb4f | |
| parent | 2a106a49d91699d0699af1134c41a43b942b85e8 (diff) | |
feat: block further initial submissions (#232)
24 files changed, 556 insertions, 98 deletions
@@ -45,6 +45,17 @@ Zu diesem Zweck muss in gPas eine **Multi-Pseudonym-Domäne** konfiguriert werde **WICHTIG:** Deaktivierte Pseudonymisierung ist nur für Tests nutzbar. Vorgangsummern sind zufällig und werden anschließend verworfen. +#### Blockieren weiterer initialer Submissions + +Diese Anwendung blockiert weitere initiale Submissions nach der ersten erfolgreichen Übertragung in DNPM:DIP. +Sobald für einen Patienten eine Übertragung ohne Issues oder mit maximal Warnungen erfolgte und damit von +DNPM:DIP akzeptiert wurde, werden weitere Meldungen solange verworfen, bis ein Administrator den Patienten +wieder freigegeben hat. + +**ACHTUNG**: Diese Funktionalität ist in Version 0.12.x noch nicht standardmäßig aktiviert und muss erst aktiviert werden. + +`APP_POST_INITIAL_SUBMISSION_BLOCK` -> `true` | `false` (falls fehlt, wird `false` angenommen) + #### Test Betriebsbereitschaft Um die voll Betriebsbereitschaft herzustellen, muss eine erfolgreiche Übertragung mit dem Submission-Typ *Test* erfolgt sein. Über die Umgebungsvariable wird dieser Übertragungsmodus diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/monitoring/RequestRepositoryTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/monitoring/RequestRepositoryTest.kt index 428a99d..1f561da 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/monitoring/RequestRepositoryTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/monitoring/RequestRepositoryTest.kt @@ -61,6 +61,7 @@ class RequestRepositoryTest : AbstractTestcontainerTest() { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-07-07T00:00:00Z"), ) diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt index f6f6a08..d27aa4c 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/output/RestDipMtbFileSenderTest.kt @@ -31,6 +31,8 @@ import dev.dnpm.etl.processor.consent.ConsentEvaluator 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.hamcrest.CoreMatchers.not import org.hamcrest.Matchers.containsString @@ -51,8 +53,6 @@ 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 java.time.Instant -import java.util.* @SpringBootTest @MockitoBean(types = [ReportService::class]) @@ -70,43 +70,39 @@ import java.util.* ) class RestDipMtbFileSenderTest { - @Nested - inner class DnpmV2ContentRequest { + @Nested + inner class DnpmV2ContentRequest { - private lateinit var mockRestServiceServer: MockRestServiceServer + private lateinit var mockRestServiceServer: MockRestServiceServer - private lateinit var restMtbFileSender: RestMtbFileSender + private lateinit var restMtbFileSender: RestMtbFileSender - private var reportService = - ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build())) + private var reportService = + ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build())) - @BeforeEach - fun setup( - @Autowired restTemplate: RestTemplate - ) { - val restTemplate = restTemplate - val restTargetProperties = RestTargetProperties("http://localhost:9000/api", null, null) - val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() + @BeforeEach + fun setup(@Autowired restTemplate: RestTemplate) { + 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.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) - this.restMtbFileSender = - RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) - } + this.restMtbFileSender = + RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) + } - @Test - fun shouldNotSendJsonNullValues() { - this.mockRestServiceServer - .expect(method(HttpMethod.POST)) - .andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient-record")) - .andExpect( - content().string(not(containsString("null"))) - ) - .andRespond { - withStatus(HttpStatus.OK) - .contentType(MediaType.APPLICATION_JSON) - .body( - """ + @Test + fun shouldNotSendJsonNullValues() { + this.mockRestServiceServer + .expect(method(HttpMethod.POST)) + .andExpect(requestTo("http://localhost:9000/api/mtb/etl/patient-record")) + .andExpect(content().string(not(containsString("null")))) + .andRespond { + withStatus(HttpStatus.OK) + .contentType(MediaType.APPLICATION_JSON) + .body( + """ { "patient": "PID", "issues": [ @@ -114,33 +110,34 @@ class RestDipMtbFileSenderTest { ] } """ - ) - .createResponse(it) - } + ) + .createResponse(it) + } - val response = restMtbFileSender.send(DnpmV2MtbFileRequest(RequestId("TEST1234"), dnpmV2MtbFile())) - assertThat(response.status).isEqualTo(RequestStatus.SUCCESS) - } + val response = + restMtbFileSender.send(DnpmV2MtbFileRequest(RequestId("TEST1234"), dnpmV2MtbFile())) + assertThat(response.status).isEqualTo(RequestStatus.SUCCESS) } + } - companion object { - 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()) } - } - ) + companion object { + 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()) } + } + ) + } } + } } diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/services/RequestServiceIntegrationTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/services/RequestServiceIntegrationTest.kt index d9489f2..0b30e94 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/services/RequestServiceIntegrationTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/services/RequestServiceIntegrationTest.kt @@ -24,6 +24,7 @@ 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 dev.dnpm.etl.processor.monitoring.SubmissionType import dev.dnpm.etl.processor.output.MtbFileSender import java.time.Instant import org.assertj.core.api.Assertions.assertThat @@ -75,6 +76,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-07-07T02:00:00Z"), ), @@ -85,6 +87,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() { PatientId("P2"), Fingerprint("0123456789abcdef2"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-08-08T00:00:00Z"), ), @@ -95,6 +98,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() { PatientId("P2"), Fingerprint("0123456789abcdee1"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ), diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/HomeControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/HomeControllerTest.kt index 33fc9d2..e329b30 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/HomeControllerTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/HomeControllerTest.kt @@ -26,6 +26,7 @@ import dev.dnpm.etl.processor.monitoring.Report 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.monitoring.SubmissionType import dev.dnpm.etl.processor.services.RequestService import java.io.IOException import java.time.Instant @@ -113,6 +114,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("ashdkasdh"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, ), Request( @@ -122,6 +124,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("asdasdasd"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.ERROR, ), ) @@ -148,6 +151,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("ashdkasdh"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.now(), Report("Test"), @@ -174,6 +178,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("ashdkasdh"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, ), Request( @@ -183,6 +188,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("asdasdasd"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.ERROR, ), ) @@ -208,6 +214,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("ashdkasdh"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, ), Request( @@ -217,6 +224,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("asdasdasd"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.ERROR, ), ) @@ -286,6 +294,7 @@ class HomeControllerTest { PatientId("PATIENT1"), Fingerprint("ashdkasdh"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.NO_CONSENT, ) ) diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/StatisticsRestControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/StatisticsRestControllerTest.kt index 16a9464..aabd634 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/StatisticsRestControllerTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/web/StatisticsRestControllerTest.kt @@ -28,6 +28,7 @@ import dev.dnpm.etl.processor.monitoring.CountedState 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.monitoring.SubmissionType import dev.dnpm.etl.processor.randomRequestId import dev.dnpm.etl.processor.services.RequestService import org.hamcrest.Matchers.equalTo @@ -185,6 +186,7 @@ class StatisticsRestControllerTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant .now() @@ -200,6 +202,7 @@ class StatisticsRestControllerTest { PatientId("P2"), Fingerprint("0123456789abcdef2"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant .now() @@ -215,6 +218,7 @@ class StatisticsRestControllerTest { PatientId("P2"), Fingerprint("0123456789abcdee1"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.ERROR, Instant .now() @@ -230,6 +234,7 @@ class StatisticsRestControllerTest { PatientId("P2"), Fingerprint("0123456789abcdef2"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.DUPLICATION, Instant .now() @@ -245,6 +250,7 @@ class StatisticsRestControllerTest { PatientId("P2"), Fingerprint("0123456789abcdef2"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.UNKNOWN, Instant .now() diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt index 7d795c8..96e48ea 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt @@ -29,6 +29,7 @@ data class AppConfigProperties( var maxRetryAttempts: Int = 3, var duplicationDetection: Boolean = true, var genomDeTestSubmission: Boolean = false, + var postInitialSubmissionBlock: Boolean = false, ) { companion object { const val NAME = "app" diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt index f21c09b..40c290a 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt @@ -32,6 +32,8 @@ import dev.dnpm.etl.processor.security.TokenService import dev.dnpm.etl.processor.services.ConsentProcessor import dev.dnpm.etl.processor.services.Transformation import dev.dnpm.etl.processor.services.TransformationService +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toJavaDuration import org.apache.cxf.jaxws.JaxWsProxyFactoryBean import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.condition.AnyNestedCondition @@ -58,8 +60,6 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager import org.springframework.web.client.HttpClientErrorException import org.springframework.web.client.RestTemplate import reactor.core.publisher.Sinks -import kotlin.time.Duration.Companion.seconds -import kotlin.time.toJavaDuration @Configuration @EnableConfigurationProperties( @@ -77,12 +77,14 @@ class AppConfiguration { private val logger = LoggerFactory.getLogger(AppConfiguration::class.java) - fun stringHttpMessageConverter(): StringHttpMessageConverter { - return StringHttpMessageConverter() - } + fun stringHttpMessageConverter(): StringHttpMessageConverter { + return StringHttpMessageConverter() + } - @Bean - fun mappingJacksonHttpMessageConverter(objectMapper: ObjectMapper): MappingJackson2HttpMessageConverter { + @Bean + fun mappingJacksonHttpMessageConverter( + objectMapper: ObjectMapper + ): MappingJackson2HttpMessageConverter { val converter = MappingJackson2HttpMessageConverter() converter.setObjectMapper(objectMapper) return converter @@ -91,7 +93,10 @@ class AppConfiguration { @Bean fun restTemplate(objectMapper: ObjectMapper): RestTemplate { return RestTemplateBuilder() - .messageConverters(stringHttpMessageConverter(), mappingJacksonHttpMessageConverter(objectMapper)) + .messageConverters( + stringHttpMessageConverter(), + mappingJacksonHttpMessageConverter(objectMapper), + ) .build() } @@ -273,16 +278,21 @@ class AppConfiguration { return GicsConsentService(gIcsConfigProperties, retryTemplate, restTemplate, appFhirConfig) } - @Conditional(GicsGetBroadConsentEnabledCondition::class) - @Bean - fun gicsGetBroadConsentService( - gIcsConfigProperties: GIcsConfigProperties, - retryTemplate: RetryTemplate, - restTemplate: RestTemplate, - appFhirConfig: AppFhirConfig, - ): IConsentService { - return GicsGetBroadConsentService(gIcsConfigProperties, retryTemplate, restTemplate, appFhirConfig) - } + @Conditional(GicsGetBroadConsentEnabledCondition::class) + @Bean + fun gicsGetBroadConsentService( + gIcsConfigProperties: GIcsConfigProperties, + retryTemplate: RetryTemplate, + restTemplate: RestTemplate, + appFhirConfig: AppFhirConfig, + ): IConsentService { + return GicsGetBroadConsentService( + gIcsConfigProperties, + retryTemplate, + restTemplate, + appFhirConfig, + ) + } @Conditional(GicsEnabledCondition::class) @Bean @@ -336,9 +346,9 @@ class GicsEnabledCondition : class GicsGetBroadConsentEnabledCondition : AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) { - @ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics_get_bc") - @ConditionalOnProperty(name = ["app.consent.gics.uri"]) - class OnGicsGetBroadConsentServiceSelected { - // Just for Condition - } + @ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics_get_bc") + @ConditionalOnProperty(name = ["app.consent.gics.uri"]) + class OnGicsGetBroadConsentServiceSelected { + // Just for Condition + } } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt index 6b7cab8..e0f24cf 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppSecurityConfiguration.kt @@ -94,6 +94,7 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN", "USER")) authorize("/report/**", hasAnyRole("ADMIN", "USER")) + authorize("/submission/**", hasAnyRole("ADMIN", "USER")) authorize("*.css", permitAll) authorize("*.ico", permitAll) authorize("*.jpeg", permitAll) @@ -161,6 +162,7 @@ class AppSecurityConfiguration(private val securityConfigProperties: SecurityCon authorize("/mtbfile/**", hasAnyRole("MTBFILE", "ADMIN")) authorize("/mtb/**", hasAnyRole("MTBFILE", "ADMIN")) authorize("/report/**", hasRole("ADMIN")) + authorize("/submission/**", hasAnyRole("ADMIN")) authorize(anyRequest, permitAll) } httpBasic { realmName = "ETL-Processor" } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt index 71731f1..b1cc1ed 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/Request.kt @@ -41,9 +41,11 @@ data class Request( val pid: PatientId, @Column("fingerprint") val fingerprint: Fingerprint, val type: RequestType, + @Column("submission_type") val submissionType: SubmissionType, var status: RequestStatus, var processedAt: Instant = Instant.now(), @Embedded.Nullable var report: Report? = null, + @Column("submission_accepted") var submissionAccepted: Boolean = false, ) { constructor( uuid: RequestId, @@ -51,8 +53,19 @@ data class Request( pid: PatientId, fingerprint: Fingerprint, type: RequestType, + submissionType: SubmissionType, status: RequestStatus, - ) : this(null, uuid, patientPseudonym, pid, fingerprint, type, status, Instant.now()) + ) : this( + null, + uuid, + patientPseudonym, + pid, + fingerprint, + type, + submissionType, + status, + Instant.now(), + ) constructor( uuid: RequestId, @@ -60,9 +73,20 @@ data class Request( pid: PatientId, fingerprint: Fingerprint, type: RequestType, + submissionType: SubmissionType, status: RequestStatus, processedAt: Instant, - ) : this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt) + ) : this( + null, + uuid, + patientPseudonym, + pid, + fingerprint, + type, + submissionType, + status, + processedAt, + ) fun isPendingUnknown(): Boolean { return this.status == RequestStatus.UNKNOWN && diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt index 5487a05..a0cd4ad 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/RequestStatus.kt @@ -28,4 +28,5 @@ enum class RequestStatus( UNKNOWN("unknown"), DUPLICATION("duplication"), NO_CONSENT("no-consent"), + BLOCKED_INITIAL("blocked-initial"), } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/monitoring/SubmissionType.kt b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/SubmissionType.kt new file mode 100644 index 0000000..351281e --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/monitoring/SubmissionType.kt @@ -0,0 +1,12 @@ +package dev.dnpm.etl.processor.monitoring + +enum class SubmissionType( + val value: String, +) { + UNKNOWN("unknown"), + INITIAL("initial"), + ADDITION("addition"), + CORRECTION("correction"), + FOLLOWUP("followup"), + TEST("test"), +} diff --git a/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt b/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt index 9c22ec0..a2a88b6 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/output/RestMtbFileSender.kt @@ -53,7 +53,8 @@ abstract class RestMtbFileSender( return retryTemplate.execute<MtbFileSender.Response, Exception> { val headers = getHttpHeaders(request) val entityReq = HttpEntity(request.content, headers) - val response = restTemplate.exchange(sendUrl(), HttpMethod.POST, entityReq, String::class.java) + val response = + restTemplate.exchange(sendUrl(), HttpMethod.POST, entityReq, String::class.java) if (!response.statusCode.is2xxSuccessful) { logger.warn("Error sending to remote system: {}", response.body) return@execute MtbFileSender.Response( diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt index 22a25ee..2b80167 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestProcessor.kt @@ -27,6 +27,7 @@ import dev.dnpm.etl.processor.monitoring.Report 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.monitoring.SubmissionType import dev.dnpm.etl.processor.output.DeleteRequest import dev.dnpm.etl.processor.output.DnpmV2MtbFileRequest import dev.dnpm.etl.processor.output.MtbFileRequest @@ -38,6 +39,7 @@ import dev.dnpm.etl.processor.pseudonym.pseudonymizeWith import dev.pcvolkmer.mv64e.mtb.ConsentProvision import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose import dev.pcvolkmer.mv64e.mtb.Mtb +import dev.pcvolkmer.mv64e.mtb.MvhSubmissionType import java.time.Instant import java.util.* import org.apache.commons.codec.binary.Base32 @@ -94,6 +96,53 @@ class RequestProcessor( } private fun <T> saveAndSend(request: MtbFileRequest<T>) { + var submissionType: SubmissionType = + when (request) { + is DnpmV2MtbFileRequest -> { + when (request.content.metadata?.type) { + MvhSubmissionType.TEST -> SubmissionType.TEST + MvhSubmissionType.INITIAL -> SubmissionType.INITIAL + MvhSubmissionType.ADDITION -> SubmissionType.ADDITION + MvhSubmissionType.CORRECTION -> SubmissionType.CORRECTION + MvhSubmissionType.FOLLOWUP -> SubmissionType.FOLLOWUP + else -> SubmissionType.UNKNOWN + } + } + } + + if ( + appConfigProperties.postInitialSubmissionBlock && + hasSuccessfullInitialSubmission(request.patientPseudonym()) && + hasUnacceptedInitialSubmission(request.patientPseudonym()) + ) { + requestService.save( + Request( + request.requestId, + request.patientPseudonym(), + emptyPatientId(), + fingerprint(request), + RequestType.MTB_FILE, + submissionType, + RequestStatus.BLOCKED_INITIAL, + ) + ) + // Exit - no further processing + return + } + + if ( + appConfigProperties.postInitialSubmissionBlock && + hasSuccessfullInitialSubmission(request.patientPseudonym()) && + !hasUnacceptedInitialSubmission(request.patientPseudonym()) + ) { + // Use "addition" after "intial" with "Meldebestaetigung" + request.content.metadata?.let { + logger.warn("Override submission type using 'addition' after first initial submission!") + it.type = MvhSubmissionType.ADDITION + submissionType = SubmissionType.ADDITION + } + } + requestService.save( Request( request.requestId, @@ -101,6 +150,7 @@ class RequestProcessor( emptyPatientId(), fingerprint(request), RequestType.MTB_FILE, + submissionType, RequestStatus.UNKNOWN, ) ) @@ -128,6 +178,20 @@ class RequestProcessor( ) } + private fun hasSuccessfullInitialSubmission(patientPseudonym: PatientPseudonym): Boolean { + return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any { + it.submissionType == SubmissionType.INITIAL && + (it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING) + } + } + + private fun hasUnacceptedInitialSubmission(patientPseudonym: PatientPseudonym): Boolean { + return this.requestService.allRequestsByPatientPseudonym(patientPseudonym).any { + it.submissionType == SubmissionType.INITIAL && + !(it.submissionAccepted || it.status == RequestStatus.BLOCKED_INITIAL) + } + } + private fun <T> isDuplication(pseudonymizedMtbFileRequest: MtbFileRequest<T>): Boolean { val patientPseudonym = when (pseudonymizedMtbFileRequest) { @@ -179,6 +243,7 @@ class RequestProcessor( emptyPatientId(), fingerprint(patientPseudonym.value), RequestType.DELETE, + SubmissionType.UNKNOWN, requestStatus, ) ) @@ -206,6 +271,7 @@ class RequestProcessor( fingerprint = Fingerprint.empty(), status = RequestStatus.ERROR, type = RequestType.DELETE, + submissionType = SubmissionType.UNKNOWN, report = Report("Fehler bei der Pseudonymisierung"), ) ) @@ -219,11 +285,8 @@ class RequestProcessor( } private fun fingerprint(s: String): Fingerprint { - return Fingerprint( - Base32() - .encodeAsString(DigestUtils.sha256(s)) - .replace("=", "") - .lowercase() - ) + return Fingerprint(Base32().encodeAsString(DigestUtils.sha256(s)) + .replace("=", "") + .lowercase()) } } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt index 082cd20..ed0f264 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt @@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.web import dev.dnpm.etl.processor.NotFoundException import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.RequestId +import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.services.RequestService import org.springframework.data.domain.Pageable @@ -29,8 +30,10 @@ import org.springframework.data.domain.Sort import org.springframework.data.web.PageableDefault import org.springframework.stereotype.Controller import org.springframework.ui.Model +import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestMapping @Controller @@ -38,6 +41,7 @@ import org.springframework.web.bind.annotation.RequestMapping class HomeController( private val requestService: RequestService, private val reportService: ReportService, + private val appConfigProperties: AppConfigProperties, ) { @GetMapping fun index( @@ -47,7 +51,7 @@ class HomeController( ): String { val requests = requestService.findAll(pageable) model.addAttribute("requests", requests) - + model.addAttribute("postInitialSubmissionBlock", appConfigProperties.postInitialSubmissionBlock) return "index" } @@ -76,4 +80,32 @@ class HomeController( return "report" } + + @PutMapping(path = ["/submission/{id}/accepted"]) + fun acceptSubmission( + @PathVariable id: RequestId, + model: Model, + ): String { + val request = requestService.findByUuid(id).orElse(null) ?: throw NotFoundException() + request.submissionAccepted = true + val savedRequest = requestService.save(request) + + model.addAttribute("request", savedRequest) + + return "fragments :: accept-initial" + } + + @DeleteMapping(path = ["/submission/{id}/accepted"]) + fun unacceptSubmission( + @PathVariable id: RequestId, + model: Model, + ): String { + val request = requestService.findByUuid(id).orElse(null) ?: throw NotFoundException() + request.submissionAccepted = false + val savedRequest = requestService.save(request) + + model.addAttribute("request", savedRequest) + + return "fragments :: accept-initial" + } } diff --git a/src/main/resources/db/migration/mariadb/V0_5_0__SubmissionType.sql b/src/main/resources/db/migration/mariadb/V0_5_0__SubmissionType.sql new file mode 100644 index 0000000..79f89d8 --- /dev/null +++ b/src/main/resources/db/migration/mariadb/V0_5_0__SubmissionType.sql @@ -0,0 +1,2 @@ +ALTER TABLE request ADD COLUMN submission_type varchar(16) not null default 'UNKNOWN'; +ALTER TABLE request ADD COLUMN submission_accepted boolean not null default false;
\ No newline at end of file diff --git a/src/main/resources/db/migration/postgresql/V0_5_0__SubmissionType.sql b/src/main/resources/db/migration/postgresql/V0_5_0__SubmissionType.sql new file mode 100644 index 0000000..79f89d8 --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V0_5_0__SubmissionType.sql @@ -0,0 +1,2 @@ +ALTER TABLE request ADD COLUMN submission_type varchar(16) not null default 'UNKNOWN'; +ALTER TABLE request ADD COLUMN submission_accepted boolean not null default false;
\ No newline at end of file diff --git a/src/main/resources/static/style.css b/src/main/resources/static/style.css index 83d98c3..33e5d20 100644 --- a/src/main/resources/static/style.css +++ b/src/main/resources/static/style.css @@ -360,7 +360,7 @@ form.samplecode-input input:focus-visible { } .border { - padding: 1.5rem; + padding: 1rem; border: 1px solid var(--table-border); border-radius: .5rem; background: white; @@ -382,7 +382,7 @@ table { } table.config-table td:first-child { - width: 24rem; + width: 26rem; min-width: fit-content; } @@ -451,10 +451,13 @@ th { } td { - font-family: monospace; border-bottom: 1px solid var(--bg-gray-op); } +td, td > a { + font-family: monospace; +} + tr:last-of-type > td { border-bottom: none; } @@ -465,10 +468,9 @@ td > small { } td.patient-id { - width: 32rem; + min-width: 20rem; text-overflow: ellipsis; overflow: hidden; - display: block; } td.bg-blue, th.bg-blue, @@ -571,6 +573,11 @@ td.clipboard.clipped { color: white; } +.btn.btn-green { + background: var(--bg-green); + color: white; +} + .btn.btn-blue { background: var(--bg-blue); color: white; diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index f3d95c9..8ecd507 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -35,6 +35,20 @@ </ul> </nav> </div> + <th:block th:fragment="accept-initial" sec:authorize="hasRole('USER') or hasRole('ADMIN')"> + <button class="btn" hx-swap="outerHTML" th:hx-delete="@{/submission/{requestId}/accepted(requestId=${request.uuid})}" th:if="${ + request.submissionType.value == 'initial' + and (request.status.value == 'success' or request.status.value == 'warning') + and request.submissionAccepted == true}" title="Keine Meldebestätigung - blockieren"> + 🔒 + </button> + <button class="btn" hx-swap="outerHTML" th:hx-put="@{/submission/{requestId}/accepted(requestId=${request.uuid})}" th:if="${ + request.submissionType.value == 'initial' + and (request.status.value == 'success' or request.status.value == 'warning') + and request.submissionAccepted == false}" title="Meldebestätigung vorhanden - nicht weiter blockieren"> + 🔓 + </button> + </th:block> <footer th:fragment="footer"> <div class="container"> <div> diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 5021bc4..a732fa2 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -42,6 +42,7 @@ <tr> <th>Status</th> <th>Typ</th> + <th sec:authorize="hasRole('USER') or hasRole('ADMIN')" th:if="${postInitialSubmissionBlock}">Aktion</th> <th>ID</th> <th>Datum</th> <th>Patienten-ID</th> @@ -56,7 +57,14 @@ <td th:if="${request.status.value == 'unknown' and request.isPendingUnknown()}" class="bg-yellow"><small>⏰ [[ ${request.status} ]] ⏰</small></td> <td th:if="${request.status.value == 'duplication'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value == 'no-consent'}" class="bg-blue"><small>[[ ${request.status} ]]</small></td> - <td th:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td> + <td th:if="${request.status.value == 'blocked-initial'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td> + <td th:style="${request.type.value == 'delete'} ? 'color: red;'"> + <small> + [[ ${request.type} ]] + <th:block th:if="${request.submissionType.value != 'unknown'}">([[ ${request.submissionType} ]])</th:block> + </small> + </td> + <td th:insert="~{fragments :: accept-initial}" sec:authorize="hasRole('USER') or hasRole('ADMIN')" th:if="${postInitialSubmissionBlock}"></td> <td th:if="not ${request.report}">[[ ${request.uuid} ]]</td> <td th:if="${request.report}"> <a th:href="@{/report/{id}(id=${request.uuid})}" sec:authorize="hasRole('USER') or hasRole('ADMIN')">[[ ${request.uuid} ]]</a> @@ -78,6 +86,7 @@ </main> <footer th:replace="~{fragments.html :: footer}"></footer> <script th:src="@{/scripts.js}"></script> + <script th:src="@{/webjars/htmx.org/dist/htmx.min.js}"></script> <script> window.addEventListener('load', () => { let keyBindings = { 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 4bd3fc1..851c1a1 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestProcessorTest.kt @@ -28,6 +28,7 @@ import dev.dnpm.etl.processor.consent.TtpConsentStatus 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.monitoring.SubmissionType import dev.dnpm.etl.processor.output.DeleteRequest import dev.dnpm.etl.processor.output.DnpmV2MtbFileRequest import dev.dnpm.etl.processor.output.MtbFileSender @@ -39,6 +40,7 @@ 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 import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.Mock @@ -104,6 +106,7 @@ class RequestProcessorTest { PatientId("P1"), Fingerprint("6vkiti5bk6ikwifpajpt7cygmd3dvw54d6lwfhzlynb3pqtzferq"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ) @@ -159,6 +162,7 @@ class RequestProcessorTest { PatientId("P1"), Fingerprint("4gcjwtjjtcczybsljxepdfpkaeusvd7g3vogfqpmphyffyzfx7dq"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ) @@ -214,6 +218,7 @@ class RequestProcessorTest { PatientId("P1"), Fingerprint("different"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ) @@ -273,6 +278,7 @@ class RequestProcessorTest { PatientId("P1"), Fingerprint("different"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ) @@ -339,6 +345,102 @@ class RequestProcessorTest { } @Test + fun testShouldSendMtbFileAdditionIfInitialFileWasAccepted() { + + // One successful and accepted and one blocked initial + val lastRequests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.SUCCESS, + Instant.parse("2026-01-05T09:00:00Z"), + submissionAccepted = true, + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("blocked_initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.BLOCKED_INITIAL, + Instant.parse("2026-01-05T10:00:00Z"), + submissionAccepted = false, + ), + ) + + doAnswer { lastRequests } + .whenever(requestService) + .allRequestsByPatientPseudonym(anyValueClass()) + + doAnswer { false } + .whenever(requestService) + .isLastRequestWithKnownStatusDeletion(anyValueClass()) + + doAnswer { MtbFileSender.Response(status = RequestStatus.SUCCESS) } + .whenever(sender) + .send(any<DnpmV2MtbFileRequest>()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + ObjectMapper(), + applicationEventPublisher, + AppConfigProperties(postInitialSubmissionBlock = true), + consentProcessor, + ) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .metadata(MvhMetadata()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() + + this.requestProcessor.processMtbFile(mtbFile) + + val requestCaptor = argumentCaptor<DnpmV2MtbFileRequest>() + verify(sender, times(1)).send(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.content.metadata.type).isEqualTo(MvhSubmissionType.ADDITION) + + val eventCaptor = argumentCaptor<ResponseEvent>() + verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture()) + assertThat(eventCaptor.firstValue).isNotNull + assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) + } + + @Test fun testShouldSendDeleteRequestAndSaveUnknownRequestStatusAtFirst() { doAnswer { "PSEUDONYM" }.whenever(pseudonymizeService).patientPseudonym(anyValueClass()) @@ -452,6 +554,136 @@ class RequestProcessorTest { assertThat(eventCaptor.firstValue.status).isEqualTo(RequestStatus.SUCCESS) } + @Nested + inner class WithInitialSubmissionBlock { + + private lateinit var pseudonymizeService: PseudonymizeService + private lateinit var transformationService: TransformationService + private lateinit var sender: MtbFileSender + private lateinit var requestService: RequestService + private lateinit var applicationEventPublisher: ApplicationEventPublisher + private lateinit var appConfigProperties: AppConfigProperties + private lateinit var consentProcessor: ConsentProcessor + private lateinit var requestProcessor: RequestProcessor + + @BeforeEach + fun setup( + @Mock pseudonymizeService: PseudonymizeService, + @Mock transformationService: TransformationService, + @Mock sender: RestMtbFileSender, + @Mock requestService: RequestService, + @Mock applicationEventPublisher: ApplicationEventPublisher, + @Mock consentProcessor: ConsentProcessor, + ) { + this.pseudonymizeService = pseudonymizeService + this.transformationService = transformationService + this.sender = sender + this.requestService = requestService + this.applicationEventPublisher = applicationEventPublisher + this.appConfigProperties = AppConfigProperties() + this.consentProcessor = consentProcessor + + val objectMapper = ObjectMapper() + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + objectMapper, + applicationEventPublisher, + appConfigProperties, + consentProcessor, + ) + } + + @Test + fun testShouldNotSendMtbFileIfInitialFileWasSent() { + + // One failed attempt and one successful but not accepted + val lastRequests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.ERROR, + Instant.parse("2026-01-05T09:00:00Z"), + submissionAccepted = false, + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("blocked_initial"), + RequestType.MTB_FILE, + SubmissionType.INITIAL, + RequestStatus.SUCCESS, + Instant.parse("2026-01-05T10:00:00Z"), + submissionAccepted = false, + ), + ) + + doAnswer { lastRequests } + .whenever(requestService) + .allRequestsByPatientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] as String } + .whenever(pseudonymizeService) + .patientPseudonym(anyValueClass()) + + doAnswer { it.arguments[0] }.whenever(transformationService).transform(any<Mtb>()) + + whenever(consentProcessor.consentGatedCheckAndTryEmbedding(any())).thenReturn(true) + + requestProcessor = + RequestProcessor( + pseudonymizeService, + transformationService, + sender, + requestService, + ObjectMapper(), + applicationEventPublisher, + AppConfigProperties(postInitialSubmissionBlock = true), + consentProcessor, + ) + + val mtbFile = + Mtb.builder() + .patient(Patient.builder().id("123").build()) + .episodesOfCare( + listOf( + MtbEpisodeOfCare.builder() + .id("1") + .patient(Reference.builder().id("123").build()) + .period( + PeriodDate.builder() + .start(Date.from(Instant.parse("2021-01-01T00:00:00.00Z"))) + .build() + ) + .build() + ) + ) + .build() + + this.requestProcessor.processMtbFile(mtbFile) + + val requestCaptor = argumentCaptor<Request>() + verify(requestService, times(1)).save(requestCaptor.capture()) + assertThat(requestCaptor.firstValue).isNotNull + assertThat(requestCaptor.firstValue.status).isEqualTo(RequestStatus.BLOCKED_INITIAL) + + verify(applicationEventPublisher, times(0)).publishEvent(any()) + verify(sender, times(0)).send(any<DnpmV2MtbFileRequest>()) + } + } + companion object { val TEST_PATIENT_ID = PatientId("TEST_12345678901") } 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 bc0286c..fdb7578 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt @@ -24,6 +24,7 @@ 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 dev.dnpm.etl.processor.monitoring.SubmissionType import java.time.Instant import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach @@ -51,6 +52,7 @@ class RequestServiceTest { PatientId("PX"), Fingerprint("dummy"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-08-08T02:00:00Z"), ) @@ -72,6 +74,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-07-07T00:00:00Z"), ), @@ -82,6 +85,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdefd"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-07-07T02:00:00Z"), ), @@ -92,6 +96,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.UNKNOWN, Instant.parse("2023-08-11T00:00:00Z"), ), @@ -113,6 +118,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-07-07T00:00:00Z"), ), @@ -123,6 +129,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-07-07T02:00:00Z"), ), @@ -133,6 +140,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.UNKNOWN, Instant.parse("2023-08-11T00:00:00Z"), ), @@ -154,6 +162,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-07-07T02:00:00Z"), ), @@ -164,6 +173,7 @@ class RequestServiceTest { PatientId("P2"), Fingerprint("0123456789abcdef2"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.WARNING, Instant.parse("2023-08-08T00:00:00Z"), ), @@ -200,6 +210,7 @@ class RequestServiceTest { PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.DELETE, + SubmissionType.TEST, RequestStatus.SUCCESS, Instant.parse("2023-07-07T02:00:00Z"), ) 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 16a5791..804b91c 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ResponseProcessorTest.kt @@ -23,6 +23,7 @@ import dev.dnpm.etl.processor.* 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.monitoring.SubmissionType import java.time.Instant import java.util.* import org.assertj.core.api.Assertions.assertThat @@ -52,6 +53,7 @@ class ResponseProcessorTest { PatientId("1"), Fingerprint("dummyfingerprint"), RequestType.MTB_FILE, + SubmissionType.TEST, RequestStatus.UNKNOWN, ) 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 a4af214..3af5097 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/TransformationServiceTest.kt @@ -24,11 +24,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 { @@ -149,7 +149,12 @@ class TransformationServiceTest { ) .build() val consent = ConsentProcessorTest.getDummyGenomDeConsent() - val jsonNode = ObjectMapper().readValue(FhirContext.forR4().newJsonParser().encodeToString(consent), ObjectNode::class.java) + val jsonNode = + ObjectMapper() + .readValue( + FhirContext.forR4().newJsonParser().encodeToString(consent), + ObjectNode::class.java, + ) mvhMetadata.researchConsents = mutableListOf() mvhMetadata.researchConsents.add(MvhMetadata.ResearchConsent.from(jsonNode)) |
