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

feat: use issue severity to create status (#90)

This commit is contained in:
Paul-Christian Volkmer 2025-04-03 17:06:03 +02:00 committed by GitHub
parent 98b971d7db
commit befeef3153
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 293 additions and 70 deletions

View File

@ -264,6 +264,27 @@ ein Consent-Widerspruch erfolgte.
Dieses Vorgehen empfiehlt sich, wenn Sie gespeicherte Records nachgelagert für andere Auswertungen verwenden möchten. Dieses Vorgehen empfiehlt sich, wenn Sie gespeicherte Records nachgelagert für andere Auswertungen verwenden möchten.
### Antworten und Statusauswertung
Anfragen and bwHC-Backend aus Versionen bis 0.9.x wurden wie folgt behandelt:
| HTTP-Response | Status |
|----------------|-----------|
| `HTTP 200` | `SUCCESS` |
| `HTTP 201` | `WARNING` |
| `HTTP 400-...` | `ERROR` |
Dies konnte dazu führen, dass zwar mit einem `HTTP 201` geantwortet wurde, aber dennoch in der Issue-Liste die
Severity `error` aufgetaucht ist.
Ab Version 0.10 wird die Issue-Liste der Antwort verwendet und die darion enthaltene höchste Severity-Stufe als Ergebnis verwendet.
| Höchste Severity | Status |
|------------------|-----------|
| `info` | `SUCCESS` |
| `warning` | `WARNING` |
| `error`, `fatal` | `ERROR` |
## Docker-Images ## Docker-Images
Diese Anwendung ist auch als Docker-Image verfügbar: https://github.com/pcvolkmer/etl-processor/pkgs/container/etl-processor Diese Anwendung ist auch als Docker-Image verfügbar: https://github.com/pcvolkmer/etl-processor/pkgs/container/etl-processor

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.config
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService
import dev.dnpm.etl.processor.output.MtbFileSender import dev.dnpm.etl.processor.output.MtbFileSender
import dev.dnpm.etl.processor.output.RestBwhcMtbFileSender import dev.dnpm.etl.processor.output.RestBwhcMtbFileSender
@ -53,15 +54,16 @@ class AppRestConfiguration {
fun restMtbFileSender( fun restMtbFileSender(
restTemplate: RestTemplate, restTemplate: RestTemplate,
restTargetProperties: RestTargetProperties, restTargetProperties: RestTargetProperties,
retryTemplate: RetryTemplate retryTemplate: RetryTemplate,
reportService: ReportService,
): MtbFileSender { ): MtbFileSender {
if (restTargetProperties.isBwhc) { if (restTargetProperties.isBwhc) {
logger.info("Selected 'RestBwhcMtbFileSender'") logger.info("Selected 'RestBwhcMtbFileSender'")
return RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate) return RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
} }
logger.info("Selected 'RestDipMtbFileSender'") logger.info("Selected 'RestDipMtbFileSender'")
return RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate) return RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
} }
@Bean @Bean

View File

@ -25,6 +25,8 @@ import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.core.JsonParseException import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.monitoring.ReportService.Issue
import dev.dnpm.etl.processor.monitoring.ReportService.Severity
class ReportService( class ReportService(
private val objectMapper: ObjectMapper private val objectMapper: ObjectMapper
@ -64,3 +66,12 @@ class ReportService(
INFO("info") INFO("info")
} }
} }
fun List<Issue>.asRequestStatus(): RequestStatus {
val severity = this.minOfOrNull { it.severity }
return when (severity) {
Severity.FATAL, Severity.ERROR -> RequestStatus.ERROR
Severity.WARNING -> RequestStatus.WARNING
else -> RequestStatus.SUCCESS
}
}

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService
import org.springframework.retry.support.RetryTemplate import org.springframework.retry.support.RetryTemplate
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder import org.springframework.web.util.UriComponentsBuilder
@ -28,8 +29,9 @@ import org.springframework.web.util.UriComponentsBuilder
class RestBwhcMtbFileSender( class RestBwhcMtbFileSender(
restTemplate: RestTemplate, restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties, private val restTargetProperties: RestTargetProperties,
retryTemplate: RetryTemplate retryTemplate: RetryTemplate,
) : RestMtbFileSender(restTemplate, restTargetProperties, retryTemplate) { reportService: ReportService,
) : RestMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) {
override fun sendUrl(): String { override fun sendUrl(): String {
return UriComponentsBuilder return UriComponentsBuilder

View File

@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService
import org.springframework.retry.support.RetryTemplate import org.springframework.retry.support.RetryTemplate
import org.springframework.web.client.RestTemplate import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder import org.springframework.web.util.UriComponentsBuilder
@ -28,8 +29,9 @@ import org.springframework.web.util.UriComponentsBuilder
class RestDipMtbFileSender( class RestDipMtbFileSender(
restTemplate: RestTemplate, restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties, private val restTargetProperties: RestTargetProperties,
retryTemplate: RetryTemplate retryTemplate: RetryTemplate,
) : RestMtbFileSender(restTemplate, restTargetProperties, retryTemplate) { reportService: ReportService
) : RestMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService) {
override fun sendUrl(): String { override fun sendUrl(): String {
return UriComponentsBuilder return UriComponentsBuilder

View File

@ -22,6 +22,8 @@ package dev.dnpm.etl.processor.output
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.asRequestStatus
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.HttpEntity import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
@ -34,7 +36,8 @@ import org.springframework.web.client.RestTemplate
abstract class RestMtbFileSender( abstract class RestMtbFileSender(
private val restTemplate: RestTemplate, private val restTemplate: RestTemplate,
private val restTargetProperties: RestTargetProperties, private val restTargetProperties: RestTargetProperties,
private val retryTemplate: RetryTemplate private val retryTemplate: RetryTemplate,
private val reportService: ReportService
) : MtbFileSender { ) : MtbFileSender {
private val logger = LoggerFactory.getLogger(RestMtbFileSender::class.java) private val logger = LoggerFactory.getLogger(RestMtbFileSender::class.java)
@ -56,19 +59,19 @@ abstract class RestMtbFileSender(
if (!response.statusCode.is2xxSuccessful) { if (!response.statusCode.is2xxSuccessful) {
logger.warn("Error sending to remote system: {}", response.body) logger.warn("Error sending to remote system: {}", response.body)
return@execute MtbFileSender.Response( return@execute MtbFileSender.Response(
response.statusCode.asRequestStatus(), reportService.deserialize(response.body).asRequestStatus(),
"Status-Code: ${response.statusCode.value()}" "Status-Code: ${response.statusCode.value()}"
) )
} }
logger.debug("Sent file via RestMtbFileSender") logger.debug("Sent file via RestMtbFileSender")
return@execute MtbFileSender.Response(response.statusCode.asRequestStatus(), response.body.orEmpty()) return@execute MtbFileSender.Response(reportService.deserialize(response.body).asRequestStatus(), response.body.orEmpty())
} }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
logger.error("Not a valid URI to export to: '{}'", restTargetProperties.uri!!) logger.error("Not a valid URI to export to: '{}'", restTargetProperties.uri!!)
} catch (e: RestClientResponseException) { } catch (e: RestClientResponseException) {
logger.info(restTargetProperties.uri!!.toString()) logger.info(restTargetProperties.uri!!.toString())
logger.error("Request data not accepted by remote system", e) logger.error("Request data not accepted by remote system", e)
return MtbFileSender.Response(e.statusCode.asRequestStatus(), e.responseBodyAsString) return MtbFileSender.Response(reportService.deserialize(e.responseBodyAsString).asRequestStatus(), e.responseBodyAsString)
} }
return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung") return MtbFileSender.Response(RequestStatus.ERROR, "Sonstiger Fehler bei der Übertragung")
} }

View File

@ -19,14 +19,18 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import org.springframework.http.HttpMethod import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
@ -45,6 +49,8 @@ class RestBwhcMtbFileSenderTest {
private lateinit var restMtbFileSender: RestMtbFileSender private lateinit var restMtbFileSender: RestMtbFileSender
private var reportService = ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build()))
@BeforeEach @BeforeEach
fun setup() { fun setup() {
val restTemplate = RestTemplate() val restTemplate = RestTemplate()
@ -53,7 +59,8 @@ class RestBwhcMtbFileSenderTest {
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender =
RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
} }
@ParameterizedTest @ParameterizedTest
@ -94,7 +101,8 @@ class RestBwhcMtbFileSenderTest {
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build()
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender =
RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
val expectedCount = when (requestWithResponse.httpStatus) { val expectedCount = when (requestWithResponse.httpStatus) {
// OK - No Retry // OK - No Retry
@ -123,7 +131,8 @@ class RestBwhcMtbFileSenderTest {
val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build() val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(3)).build()
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender =
RestBwhcMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
val expectedCount = when (requestWithResponse.httpStatus) { val expectedCount = when (requestWithResponse.httpStatus) {
// OK - No Retry // OK - No Retry
@ -154,24 +163,6 @@ class RestBwhcMtbFileSenderTest {
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
private val warningBody = """
{
"patient_id": "PID",
"issues": [
{ "severity": "warning", "message": "Something is not right" }
]
}
""".trimIndent()
private val errorBody = """
{
"patient_id": "PID",
"issues": [
{ "severity": "error", "message": "Something is very bad" }
]
}
""".trimIndent()
val mtbFile: MtbFile = MtbFile.builder() val mtbFile: MtbFile = MtbFile.builder()
.withPatient( .withPatient(
Patient.builder() Patient.builder()
@ -205,21 +196,34 @@ class RestBwhcMtbFileSenderTest {
@JvmStatic @JvmStatic
fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> { fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> {
return setOf( return setOf(
RequestWithResponse(HttpStatus.OK, "{}", MtbFileSender.Response(RequestStatus.SUCCESS, "{}")), RequestWithResponse(
HttpStatus.OK,
responseBodyWithMaxSeverity(ReportService.Severity.INFO),
MtbFileSender.Response(
RequestStatus.SUCCESS,
responseBodyWithMaxSeverity(ReportService.Severity.INFO)
)
),
RequestWithResponse( RequestWithResponse(
HttpStatus.CREATED, HttpStatus.CREATED,
warningBody, responseBodyWithMaxSeverity(ReportService.Severity.WARNING),
MtbFileSender.Response(RequestStatus.WARNING, warningBody) MtbFileSender.Response(
RequestStatus.WARNING,
responseBodyWithMaxSeverity(ReportService.Severity.WARNING)
)
), ),
RequestWithResponse( RequestWithResponse(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
ERROR_RESPONSE_BODY, responseBodyWithMaxSeverity(ReportService.Severity.ERROR),
MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY) MtbFileSender.Response(RequestStatus.ERROR, responseBodyWithMaxSeverity(ReportService.Severity.ERROR))
), ),
RequestWithResponse( RequestWithResponse(
HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.UNPROCESSABLE_ENTITY,
errorBody, responseBodyWithMaxSeverity(ReportService.Severity.FATAL),
MtbFileSender.Response(RequestStatus.ERROR, errorBody) MtbFileSender.Response(
RequestStatus.ERROR,
responseBodyWithMaxSeverity(ReportService.Severity.FATAL)
)
), ),
// Some more errors not mentioned in documentation // Some more errors not mentioned in documentation
RequestWithResponse( RequestWithResponse(
@ -256,6 +260,52 @@ class RestBwhcMtbFileSenderTest {
) )
) )
} }
fun responseBodyWithMaxSeverity(severity: ReportService.Severity): String {
return when (severity) {
ReportService.Severity.INFO -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" }
]
}
"""
ReportService.Severity.WARNING -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" }
]
}
"""
ReportService.Severity.ERROR -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" }
]
}
"""
ReportService.Severity.FATAL -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" },
{ "severity": "fatal", "message": "Fatal Message" }
]
}
"""
}
}
} }

View File

@ -19,13 +19,17 @@
package dev.dnpm.etl.processor.output package dev.dnpm.etl.processor.output
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.PatientPseudonym import dev.dnpm.etl.processor.PatientPseudonym
import dev.dnpm.etl.processor.RequestId import dev.dnpm.etl.processor.RequestId
import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.config.AppConfiguration import dev.dnpm.etl.processor.config.AppConfiguration
import dev.dnpm.etl.processor.config.RestTargetProperties import dev.dnpm.etl.processor.config.RestTargetProperties
import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.output.RestBwhcMtbFileSenderTest.Companion
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
@ -48,6 +52,8 @@ class RestDipMtbFileSenderTest {
private lateinit var restMtbFileSender: RestMtbFileSender private lateinit var restMtbFileSender: RestMtbFileSender
private var reportService = ReportService(ObjectMapper().registerModule(KotlinModule.Builder().build()))
@BeforeEach @BeforeEach
fun setup() { fun setup() {
val restTemplate = RestTemplate() val restTemplate = RestTemplate()
@ -56,7 +62,7 @@ class RestDipMtbFileSenderTest {
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
} }
@ParameterizedTest @ParameterizedTest
@ -98,11 +104,14 @@ class RestDipMtbFileSenderTest {
retryTemplate.setBackOffPolicy(NoBackOffPolicy()) retryTemplate.setBackOffPolicy(NoBackOffPolicy())
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender =
RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
val expectedCount = when (requestWithResponse.httpStatus) { val expectedCount = when (requestWithResponse.httpStatus) {
// OK - No Retry // OK - No Retry
HttpStatus.OK, HttpStatus.CREATED, HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.BAD_REQUEST -> ExpectedCount.max(1) HttpStatus.OK, HttpStatus.CREATED, HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.BAD_REQUEST -> ExpectedCount.max(
1
)
// Request failed - Retry max 3 times // Request failed - Retry max 3 times
else -> ExpectedCount.max(3) else -> ExpectedCount.max(3)
} }
@ -128,11 +137,14 @@ class RestDipMtbFileSenderTest {
retryTemplate.setBackOffPolicy(NoBackOffPolicy()) retryTemplate.setBackOffPolicy(NoBackOffPolicy())
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate) this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate)
this.restMtbFileSender = RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate) this.restMtbFileSender =
RestDipMtbFileSender(restTemplate, restTargetProperties, retryTemplate, reportService)
val expectedCount = when (requestWithResponse.httpStatus) { val expectedCount = when (requestWithResponse.httpStatus) {
// OK - No Retry // OK - No Retry
HttpStatus.OK, HttpStatus.CREATED, HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.BAD_REQUEST -> ExpectedCount.max(1) HttpStatus.OK, HttpStatus.CREATED, HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.BAD_REQUEST -> ExpectedCount.max(
1
)
// Request failed - Retry max 3 times // Request failed - Retry max 3 times
else -> ExpectedCount.max(3) else -> ExpectedCount.max(3)
} }
@ -159,24 +171,6 @@ class RestDipMtbFileSenderTest {
val TEST_REQUEST_ID = RequestId("TestId") val TEST_REQUEST_ID = RequestId("TestId")
val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID") val TEST_PATIENT_PSEUDONYM = PatientPseudonym("PID")
private val warningBody = """
{
"patient_id": "PID",
"issues": [
{ "severity": "warning", "message": "Something is not right" }
]
}
""".trimIndent()
private val errorBody = """
{
"patient_id": "PID",
"issues": [
{ "severity": "error", "message": "Something is very bad" }
]
}
""".trimIndent()
val mtbFile: MtbFile = MtbFile.builder() val mtbFile: MtbFile = MtbFile.builder()
.withPatient( .withPatient(
Patient.builder() Patient.builder()
@ -210,21 +204,28 @@ class RestDipMtbFileSenderTest {
@JvmStatic @JvmStatic
fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> { fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> {
return setOf( return setOf(
RequestWithResponse(HttpStatus.OK, "{}", MtbFileSender.Response(RequestStatus.SUCCESS, "{}")), RequestWithResponse(
HttpStatus.OK,
responseBodyWithMaxSeverity(ReportService.Severity.INFO),
MtbFileSender.Response(
RequestStatus.SUCCESS,
responseBodyWithMaxSeverity(ReportService.Severity.INFO)
)
),
RequestWithResponse( RequestWithResponse(
HttpStatus.CREATED, HttpStatus.CREATED,
warningBody, responseBodyWithMaxSeverity(ReportService.Severity.WARNING),
MtbFileSender.Response(RequestStatus.WARNING, warningBody) MtbFileSender.Response(RequestStatus.WARNING, responseBodyWithMaxSeverity(ReportService.Severity.WARNING))
), ),
RequestWithResponse( RequestWithResponse(
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
ERROR_RESPONSE_BODY, responseBodyWithMaxSeverity(ReportService.Severity.ERROR),
MtbFileSender.Response(RequestStatus.ERROR, ERROR_RESPONSE_BODY) MtbFileSender.Response(RequestStatus.ERROR, responseBodyWithMaxSeverity(ReportService.Severity.ERROR))
), ),
RequestWithResponse( RequestWithResponse(
HttpStatus.UNPROCESSABLE_ENTITY, HttpStatus.UNPROCESSABLE_ENTITY,
errorBody, responseBodyWithMaxSeverity(ReportService.Severity.ERROR),
MtbFileSender.Response(RequestStatus.ERROR, errorBody) MtbFileSender.Response(RequestStatus.ERROR, responseBodyWithMaxSeverity(ReportService.Severity.ERROR))
), ),
// Some more errors not mentioned in documentation // Some more errors not mentioned in documentation
RequestWithResponse( RequestWithResponse(
@ -261,6 +262,52 @@ class RestDipMtbFileSenderTest {
) )
) )
} }
fun responseBodyWithMaxSeverity(severity: ReportService.Severity): String {
return when (severity) {
ReportService.Severity.INFO -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" }
]
}
"""
ReportService.Severity.WARNING -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" }
]
}
"""
ReportService.Severity.ERROR -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" }
]
}
"""
ReportService.Severity.FATAL -> """
{
"patient": "PID",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" },
{ "severity": "fatal", "message": "Fatal Message" }
]
}
"""
}
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2025 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -22,9 +22,14 @@ package dev.dnpm.etl.processor.services
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.monitoring.ReportService
import dev.dnpm.etl.processor.monitoring.RequestStatus
import dev.dnpm.etl.processor.monitoring.asRequestStatus
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
class ReportServiceTest { class ReportServiceTest {
@ -60,6 +65,15 @@ class ReportServiceTest {
assertThat(actual[2].message).isEqualTo("Warning Message") assertThat(actual[2].message).isEqualTo("Warning Message")
assertThat(actual[3].severity).isEqualTo(ReportService.Severity.INFO) assertThat(actual[3].severity).isEqualTo(ReportService.Severity.INFO)
assertThat(actual[3].message).isEqualTo("Info Message") assertThat(actual[3].message).isEqualTo("Info Message")
assertThat(actual.asRequestStatus()).isEqualTo(RequestStatus.ERROR)
}
@ParameterizedTest
@MethodSource("testData")
fun shouldParseDataQualityReport(json: String, requestStatus: RequestStatus) {
val actual = this.reportService.deserialize(json)
assertThat(actual.asRequestStatus()).isEqualTo(requestStatus)
} }
@Test @Test
@ -73,4 +87,75 @@ class ReportServiceTest {
assertThat(actual[0].message).isEqualTo("Not parsable data quality report '$invalidResponse'") assertThat(actual[0].message).isEqualTo("Not parsable data quality report '$invalidResponse'")
} }
companion object {
@JvmStatic
fun testData(): Set<Arguments> {
return setOf(
Arguments.of(
"""
{
"patient": "4711",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" },
{ "severity": "fatal", "message": "Fatal Message" }
]
}
""".trimIndent(),
RequestStatus.ERROR
),
Arguments.of(
"""
{
"patient": "4711",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" }
]
}
""".trimIndent(),
RequestStatus.ERROR
),
Arguments.of(
"""
{
"patient": "4711",
"issues": [
{ "severity": "error", "message": "Error Message" }
{ "severity": "info", "message": "Info Message" }
]
}
""".trimIndent(),
RequestStatus.ERROR
),
Arguments.of(
"""
{
"patient": "4711",
"issues": [
{ "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" }
]
}
""".trimIndent(),
RequestStatus.WARNING
),
Arguments.of(
"""
{
"patient": "4711",
"issues": [
{ "severity": "info", "message": "Info Message" }
]
}
""".trimIndent(),
RequestStatus.SUCCESS
)
)
}
}
} }