1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-07-01 22:22:53 +00:00

3 Commits

Author SHA1 Message Date
0e1034d964 New version and add status badge to README.md 2023-08-11 09:47:20 +02:00
6ecb439007 Issue #3: Detect the request type of request with last known status (#5) 2023-08-11 09:22:54 +02:00
cb9c590472 Issue #2: Do not serialize JSON string as custom string (#4)
In addition to that, if REST request did not contain a response body, use empty
string as data quality report string.
2023-08-11 09:13:45 +02:00
10 changed files with 105 additions and 48 deletions

View File

@ -1,4 +1,4 @@
# ETL-Processor for bwHC data # ETL-Processor for bwHC data [![Run Tests](https://github.com/CCC-MF/etl-processor/actions/workflows/test.yml/badge.svg)](https://github.com/CCC-MF/etl-processor/actions/workflows/test.yml)
Diese Anwendung versendet ein bwHC-MTB-File an das bwHC-Backend und pseudonymisiert die Patienten-ID. Diese Anwendung versendet ein bwHC-MTB-File an das bwHC-Backend und pseudonymisiert die Patienten-ID.

View File

@ -11,7 +11,7 @@ plugins {
} }
group = "de.ukw.ccc" group = "de.ukw.ccc"
version = "0.1.0" version = "0.1.1"
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17

View File

@ -116,7 +116,7 @@ class RequestServiceIntegrationTest : AbstractTestcontainerTest() {
fun shouldReturnDeleteRequestAsLastRequest() { fun shouldReturnDeleteRequestAsLastRequest() {
setupTestData() setupTestData()
val actual = requestService.isLastRequestDeletion("TEST_12345678901") val actual = requestService.isLastRequestWithKnownStatusDeletion("TEST_12345678901")
assertThat(actual).isTrue() assertThat(actual).isTrue()
} }

View File

@ -50,7 +50,7 @@ class RestMtbFileSender(
return MtbFileSender.Response(response.statusCode.asRequestStatus(), "Status-Code: ${response.statusCode.value()}") return MtbFileSender.Response(response.statusCode.asRequestStatus(), "Status-Code: ${response.statusCode.value()}")
} }
logger.debug("Sent file via RestMtbFileSender") logger.debug("Sent file via RestMtbFileSender")
return MtbFileSender.Response(response.statusCode.asRequestStatus()) return MtbFileSender.Response(response.statusCode.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: RestClientException) { } catch (e: RestClientException) {

View File

@ -95,7 +95,7 @@ class RequestProcessor(
private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean { private fun isDuplication(pseudonymizedMtbFile: MtbFile): Boolean {
val lastMtbFileRequestForPatient = val lastMtbFileRequestForPatient =
requestService.lastMtbFileRequestForPatientPseudonym(pseudonymizedMtbFile.patient.id) requestService.lastMtbFileRequestForPatientPseudonym(pseudonymizedMtbFile.patient.id)
val isLastRequestDeletion = requestService.isLastRequestDeletion(pseudonymizedMtbFile.patient.id) val isLastRequestDeletion = requestService.isLastRequestWithKnownStatusDeletion(pseudonymizedMtbFile.patient.id)
return null != lastMtbFileRequestForPatient return null != lastMtbFileRequestForPatient
&& !isLastRequestDeletion && !isLastRequestDeletion

View File

@ -38,8 +38,8 @@ class RequestService(
fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: String) = fun lastMtbFileRequestForPatientPseudonym(patientPseudonym: String) =
Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym)) Companion.lastMtbFileRequestForPatientPseudonym(allRequestsByPatientPseudonym(patientPseudonym))
fun isLastRequestDeletion(patientPseudonym: String) = fun isLastRequestWithKnownStatusDeletion(patientPseudonym: String) =
Companion.isLastRequestDeletion(allRequestsByPatientPseudonym(patientPseudonym)) Companion.isLastRequestWithKnownStatusDeletion(allRequestsByPatientPseudonym(patientPseudonym))
companion object { companion object {
@ -48,7 +48,8 @@ class RequestService(
.sortedByDescending { it.processedAt } .sortedByDescending { it.processedAt }
.firstOrNull { it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING } .firstOrNull { it.status == RequestStatus.SUCCESS || it.status == RequestStatus.WARNING }
fun isLastRequestDeletion(allRequests: List<Request>) = allRequests fun isLastRequestWithKnownStatusDeletion(allRequests: List<Request>) = allRequests
.filter { it.status != RequestStatus.UNKNOWN }
.maxByOrNull { it.processedAt }?.type == RequestType.DELETE .maxByOrNull { it.processedAt }?.type == RequestType.DELETE
} }

View File

@ -55,14 +55,14 @@ class ResponseProcessor(
RequestStatus.WARNING -> { RequestStatus.WARNING -> {
it.report = Report( it.report = Report(
"Warnungen über mangelhafte Daten", "Warnungen über mangelhafte Daten",
objectMapper.writeValueAsString(event.body) event.body.orElse("")
) )
} }
RequestStatus.ERROR -> { RequestStatus.ERROR -> {
it.report = Report( it.report = Report(
"Fehler bei der Datenübertragung oder Inhalt nicht verarbeitbar", "Fehler bei der Datenübertragung oder Inhalt nicht verarbeitbar",
objectMapper.writeValueAsString(event.body) event.body.orElse("")
) )
} }

View File

@ -61,7 +61,8 @@ class RestMtbFileSenderTest {
} }
val response = restMtbFileSender.send(MtbFileSender.DeleteRequest("TestID", "PID")) val response = restMtbFileSender.send(MtbFileSender.DeleteRequest("TestID", "PID"))
assertThat(response.status).isEqualTo(requestWithResponse.requestStatus) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
@ParameterizedTest @ParameterizedTest
@ -75,11 +76,16 @@ class RestMtbFileSenderTest {
} }
val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest("TestID", mtbFile)) val response = restMtbFileSender.send(MtbFileSender.MtbFileRequest("TestID", mtbFile))
assertThat(response.status).isEqualTo(requestWithResponse.requestStatus) assertThat(response.status).isEqualTo(requestWithResponse.response.status)
assertThat(response.body).isEqualTo(requestWithResponse.response.body)
} }
companion object { companion object {
data class RequestWithResponse(val httpStatus: HttpStatus, val body: String, val requestStatus: RequestStatus) data class RequestWithResponse(
val httpStatus: HttpStatus,
val body: String,
val response: MtbFileSender.Response
)
private val warningBody = """ private val warningBody = """
{ {
@ -123,6 +129,8 @@ class RestMtbFileSenderTest {
) )
.build() .build()
private val errorResponseBody = "Sonstiger Fehler bei der Übertragung"
/** /**
* Synthetic http responses with related request status * Synthetic http responses with related request status
* Also see: https://ibmi-intra.cs.uni-tuebingen.de/display/ZPM/bwHC+REST+API * Also see: https://ibmi-intra.cs.uni-tuebingen.de/display/ZPM/bwHC+REST+API
@ -130,13 +138,33 @@ class RestMtbFileSenderTest {
@JvmStatic @JvmStatic
fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> { fun mtbFileRequestWithResponseSource(): Set<RequestWithResponse> {
return setOf( return setOf(
RequestWithResponse(HttpStatus.OK, "{}", RequestStatus.SUCCESS), RequestWithResponse(HttpStatus.OK, "{}", MtbFileSender.Response(RequestStatus.SUCCESS, "{}")),
RequestWithResponse(HttpStatus.CREATED, warningBody, RequestStatus.WARNING), RequestWithResponse(
RequestWithResponse(HttpStatus.BAD_REQUEST, "??", RequestStatus.ERROR), HttpStatus.CREATED,
RequestWithResponse(HttpStatus.UNPROCESSABLE_ENTITY, errorBody, RequestStatus.ERROR), warningBody,
MtbFileSender.Response(RequestStatus.WARNING, warningBody)
),
RequestWithResponse(
HttpStatus.BAD_REQUEST,
"??",
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
),
RequestWithResponse(
HttpStatus.UNPROCESSABLE_ENTITY,
errorBody,
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
),
// Some more errors not mentioned in documentation // Some more errors not mentioned in documentation
RequestWithResponse(HttpStatus.NOT_FOUND, "what????", RequestStatus.ERROR), RequestWithResponse(
RequestWithResponse(HttpStatus.INTERNAL_SERVER_ERROR, "what????", RequestStatus.ERROR) HttpStatus.NOT_FOUND,
"what????",
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
),
RequestWithResponse(
HttpStatus.INTERNAL_SERVER_ERROR,
"what????",
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
)
) )
} }
@ -147,10 +175,18 @@ class RestMtbFileSenderTest {
@JvmStatic @JvmStatic
fun deleteRequestWithResponseSource(): Set<RequestWithResponse> { fun deleteRequestWithResponseSource(): Set<RequestWithResponse> {
return setOf( return setOf(
RequestWithResponse(HttpStatus.OK, "", RequestStatus.SUCCESS), RequestWithResponse(HttpStatus.OK, "", MtbFileSender.Response(RequestStatus.SUCCESS)),
// Some more errors not mentioned in documentation // Some more errors not mentioned in documentation
RequestWithResponse(HttpStatus.NOT_FOUND, "what????", RequestStatus.ERROR), RequestWithResponse(
RequestWithResponse(HttpStatus.INTERNAL_SERVER_ERROR, "what????", RequestStatus.ERROR) HttpStatus.NOT_FOUND,
"what????",
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
),
RequestWithResponse(
HttpStatus.INTERNAL_SERVER_ERROR,
"what????",
MtbFileSender.Response(RequestStatus.ERROR, errorResponseBody)
)
) )
} }
} }

View File

@ -92,7 +92,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
false false
}.`when`(requestService).isLastRequestDeletion(anyString()) }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
@ -147,7 +147,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
false false
}.`when`(requestService).isLastRequestDeletion(anyString()) }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
doAnswer { doAnswer {
it.arguments[0] as String it.arguments[0] as String
@ -202,7 +202,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
false false
}.`when`(requestService).isLastRequestDeletion(anyString()) }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.SUCCESS) MtbFileSender.Response(status = RequestStatus.SUCCESS)
@ -261,7 +261,7 @@ class RequestProcessorTest {
doAnswer { doAnswer {
false false
}.`when`(requestService).isLastRequestDeletion(anyString()) }.`when`(requestService).isLastRequestWithKnownStatusDeletion(anyString())
doAnswer { doAnswer {
MtbFileSender.Response(status = RequestStatus.ERROR) MtbFileSender.Response(status = RequestStatus.ERROR)

View File

@ -68,23 +68,33 @@ class RequestServiceTest {
patientId = "TEST_12345678901", patientId = "TEST_12345678901",
pid = "P1", pid = "P1",
fingerprint = "0123456789abcdef1", fingerprint = "0123456789abcdef1",
type = RequestType.DELETE,
status = RequestStatus.SUCCESS,
processedAt = Instant.parse("2023-08-08T02:00:00Z")
),
Request(
id = 1L,
uuid = UUID.randomUUID().toString(),
patientId = "TEST_12345678902",
pid = "P2",
fingerprint = "0123456789abcdef2",
type = RequestType.MTB_FILE, type = RequestType.MTB_FILE,
status = RequestStatus.WARNING, status = RequestStatus.WARNING,
processedAt = Instant.parse("2023-08-08T00:00:00Z") processedAt = Instant.parse("2023-07-07T00:00:00Z")
),
Request(
id = 2L,
uuid = UUID.randomUUID().toString(),
patientId = "TEST_12345678901",
pid = "P1",
fingerprint = "0123456789abcdefd",
type = RequestType.DELETE,
status = RequestStatus.WARNING,
processedAt = Instant.parse("2023-07-07T02:00:00Z")
),
Request(
id = 3L,
uuid = UUID.randomUUID().toString(),
patientId = "TEST_12345678901",
pid = "P1",
fingerprint = "0123456789abcdef1",
type = RequestType.MTB_FILE,
status = RequestStatus.UNKNOWN,
processedAt = Instant.parse("2023-08-11T00:00:00Z")
) )
) )
val actual = RequestService.isLastRequestDeletion(requests) val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests)
assertThat(actual).isTrue() assertThat(actual).isTrue()
} }
@ -98,23 +108,33 @@ class RequestServiceTest {
patientId = "TEST_12345678901", patientId = "TEST_12345678901",
pid = "P1", pid = "P1",
fingerprint = "0123456789abcdef1", fingerprint = "0123456789abcdef1",
type = RequestType.DELETE, type = RequestType.MTB_FILE,
status = RequestStatus.SUCCESS, status = RequestStatus.WARNING,
processedAt = Instant.parse("2023-07-07T00:00:00Z")
),
Request(
id = 2L,
uuid = UUID.randomUUID().toString(),
patientId = "TEST_12345678901",
pid = "P1",
fingerprint = "0123456789abcdef1",
type = RequestType.MTB_FILE,
status = RequestStatus.WARNING,
processedAt = Instant.parse("2023-07-07T02:00:00Z") processedAt = Instant.parse("2023-07-07T02:00:00Z")
), ),
Request( Request(
id = 1L, id = 3L,
uuid = UUID.randomUUID().toString(), uuid = UUID.randomUUID().toString(),
patientId = "TEST_12345678902", patientId = "TEST_12345678901",
pid = "P2", pid = "P1",
fingerprint = "0123456789abcdef2", fingerprint = "0123456789abcdef1",
type = RequestType.MTB_FILE, type = RequestType.MTB_FILE,
status = RequestStatus.WARNING, status = RequestStatus.UNKNOWN,
processedAt = Instant.parse("2023-08-08T00:00:00Z") processedAt = Instant.parse("2023-08-11T00:00:00Z")
) )
) )
val actual = RequestService.isLastRequestDeletion(requests) val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests)
assertThat(actual).isFalse() assertThat(actual).isFalse()
} }
@ -197,7 +217,7 @@ class RequestServiceTest {
@Test @Test
fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() { fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() {
requestService.isLastRequestDeletion("TEST_12345678901") requestService.isLastRequestWithKnownStatusDeletion("TEST_12345678901")
verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString()) verify(requestRepository, times(1)).findAllByPatientIdOrderByProcessedAtDesc(anyString())
} }