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

feat: add new MTB endpoint path (#93)

This commit is contained in:
Paul-Christian Volkmer 2025-04-04 14:34:31 +02:00 committed by GitHub
parent 033750eb10
commit 7ae34719fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 133 additions and 82 deletions

View File

@ -24,6 +24,16 @@ Die Erkennung von Duplikaten ist normalerweise immer aktiv, kann jedoch über de
Anfragen werden, wenn nicht als Duplikat behandelt, nach der Pseudonymisierung direkt an das bwHC-Backend gesendet. Anfragen werden, wenn nicht als Duplikat behandelt, nach der Pseudonymisierung direkt an das bwHC-Backend gesendet.
Ein HTTP Request kann, angenommen die Installation erfolgte auf dem Host `dnpm.example.com` an nachfolgende URLs gesendet werden:
| HTTP-Request | URL | Consent-Status im Datensatz | Bemerkung |
|--------------|-----------------------------------------|-----------------------------|---------------------------------------------------------------------------------|
| POST | `https://dnpm.example.com/mtb` | ACTIVE | Die Anwendung verarbeitet den eingehenden Datensatz |
| POST | `https://dnpm.example.com/mtb` | REJECT | Die Anwendung sendet einen Lösch-Request für die im Datensatz angegebene Pat-ID |
| DELETE | `https://dnpm.example.com/mtb/12345678` | - | Die Anwendung sendet einen Lösch-Request für Pat-ID `12345678` |
Anstelle des Pfads `/mtb` kann auch, wie in Version 0.9 und älter üblich, `/mtbfile` verwendet werden.
### Datenübermittlung mit Apache Kafka ### Datenübermittlung mit Apache Kafka
Anfragen werden, wenn nicht als Duplikat behandelt, nach der Pseudonymisierung an Apache Kafka übergeben. Anfragen werden, wenn nicht als Duplikat behandelt, nach der Pseudonymisierung an Apache Kafka übergeben.
@ -42,6 +52,9 @@ In Versionen des ETL-Processors **nach Version 0.10** werden die folgenden Konfi
* `APP_KAFKA_TOPIC`: Nutzen Sie nun die Konfigurationsoption `APP_KAFKA_OUTPUT_TOPIC` * `APP_KAFKA_TOPIC`: Nutzen Sie nun die Konfigurationsoption `APP_KAFKA_OUTPUT_TOPIC`
* `APP_KAFKA_RESPONSE_TOPIC`: Nutzen Sie nun die Konfigurationsoption `APP_KAFKA_OUTPUT_RESPONSE_TOPIC` * `APP_KAFKA_RESPONSE_TOPIC`: Nutzen Sie nun die Konfigurationsoption `APP_KAFKA_OUTPUT_RESPONSE_TOPIC`
Der Pfad zum Versenden von MTB-Daten ist nun offiziell `/mtb`.
In Versionen **nach Version 0.10** wird die Unterstützung des Pfads `/mtbfile` entfernt.
### Pseudonymisierung der Patienten-ID ### Pseudonymisierung der Patienten-ID
Wenn eine URI zu einer gPAS-Instanz (Version >= 2023.1.0) angegeben ist, wird diese verwendet. Wenn eine URI zu einer gPAS-Instanz (Version >= 2023.1.0) angegeben ist, wird diese verwendet.
@ -161,7 +174,7 @@ zur Nutzung des MTB-File-Endpunkts eine HTTP-Basic-Authentifizierung voraussetze
![Tokenverwaltung](docs/tokens.png) ![Tokenverwaltung](docs/tokens.png)
In diesem Fall können den Endpunkt für das Onkostar-Plugin **[onkostar-plugin-dnpmexport](https://github.com/CCC-MF/onkostar-plugin-dnpmexport)** wie folgt konfigurieren: In diesem Fall kann der Endpunkt für das Onkostar-Plugin **[onkostar-plugin-dnpmexport](https://github.com/CCC-MF/onkostar-plugin-dnpmexport)** wie folgt konfiguriert werden:
``` ```
https://testonkostar:MTg1NTL...NGU4@etl.example.com/mtbfile https://testonkostar:MTg1NTL...NGU4@etl.example.com/mtbfile
@ -266,7 +279,7 @@ Dieses Vorgehen empfiehlt sich, wenn Sie gespeicherte Records nachgelagert für
### Antworten und Statusauswertung ### Antworten und Statusauswertung
Anfragen and bwHC-Backend aus Versionen bis 0.9.x wurden wie folgt behandelt: Anfragen an das bwHC-Backend aus Versionen bis 0.9.x wurden wie folgt behandelt:
| HTTP-Response | Status | | HTTP-Response | Status |
|----------------|-----------| |----------------|-----------|

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2024 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
@ -28,7 +28,7 @@ import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
@RestController @RestController
@RequestMapping(path = ["mtbfile"]) @RequestMapping(path = ["mtbfile", "mtb"])
class MtbFileRestController( class MtbFileRestController(
private val requestProcessor: RequestProcessor, private val requestProcessor: RequestProcessor,
) { ) {

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2024 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
@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import de.ukw.ccc.bwhc.dto.* import de.ukw.ccc.bwhc.dto.*
import dev.dnpm.etl.processor.services.RequestProcessor import dev.dnpm.etl.processor.services.RequestProcessor
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock import org.mockito.Mock
@ -40,24 +41,122 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders
@ExtendWith(MockitoExtension::class) @ExtendWith(MockitoExtension::class)
class MtbFileRestControllerTest { class MtbFileRestControllerTest {
private lateinit var mockMvc: MockMvc
private lateinit var requestProcessor: RequestProcessor
private val objectMapper = ObjectMapper() private val objectMapper = ObjectMapper()
@BeforeEach @Nested
fun setup( inner class BwhcRequests {
@Mock requestProcessor: RequestProcessor
) { private lateinit var mockMvc: MockMvc
this.requestProcessor = requestProcessor
val controller = MtbFileRestController(requestProcessor) private lateinit var requestProcessor: RequestProcessor
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
@BeforeEach
fun setup(
@Mock requestProcessor: RequestProcessor
) {
this.requestProcessor = requestProcessor
val controller = MtbFileRestController(requestProcessor)
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
@Test
fun shouldProcessPostRequest() {
mockMvc.post("/mtbfile") {
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.ACTIVE))
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processMtbFile(any())
}
@Test
fun shouldProcessPostRequestWithRejectedConsent() {
mockMvc.post("/mtbfile") {
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.REJECTED))
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
@Test
fun shouldProcessDeleteRequest() {
mockMvc.delete("/mtbfile/TEST_12345678").andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
} }
@Test @Nested
fun shouldProcessMtbFilePostRequest() { inner class BwhcRequestsWithAlias {
val mtbFile = MtbFile.builder()
private lateinit var mockMvc: MockMvc
private lateinit var requestProcessor: RequestProcessor
@BeforeEach
fun setup(
@Mock requestProcessor: RequestProcessor
) {
this.requestProcessor = requestProcessor
val controller = MtbFileRestController(requestProcessor)
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
@Test
fun shouldProcessPostRequest() {
mockMvc.post("/mtb") {
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.ACTIVE))
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processMtbFile(any())
}
@Test
fun shouldProcessPostRequestWithRejectedConsent() {
mockMvc.post("/mtb") {
content = objectMapper.writeValueAsString(bwhcMtbFileContent(Consent.Status.REJECTED))
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
@Test
fun shouldProcessDeleteRequest() {
mockMvc.delete("/mtb/TEST_12345678").andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
}
companion object {
fun bwhcMtbFileContent(consentStatus: Consent.Status) = MtbFile.builder()
.withPatient( .withPatient(
Patient.builder() Patient.builder()
.withId("TEST_12345678") .withId("TEST_12345678")
@ -68,7 +167,7 @@ class MtbFileRestControllerTest {
.withConsent( .withConsent(
Consent.builder() Consent.builder()
.withId("1") .withId("1")
.withStatus(Consent.Status.ACTIVE) .withStatus(consentStatus)
.withPatient("TEST_12345678") .withPatient("TEST_12345678")
.build() .build()
) )
@ -80,66 +179,5 @@ class MtbFileRestControllerTest {
.build() .build()
) )
.build() .build()
mockMvc.post("/mtbfile") {
content = objectMapper.writeValueAsString(mtbFile)
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processMtbFile(any())
} }
}
@Test
fun shouldProcessMtbFilePostRequestWithRejectedConsent() {
val mtbFile = MtbFile.builder()
.withPatient(
Patient.builder()
.withId("TEST_12345678")
.withBirthDate("2000-08-08")
.withGender(Patient.Gender.MALE)
.build()
)
.withConsent(
Consent.builder()
.withId("1")
.withStatus(Consent.Status.REJECTED)
.withPatient("TEST_12345678")
.build()
)
.withEpisode(
Episode.builder()
.withId("1")
.withPatient("TEST_12345678")
.withPeriod(PeriodStart("2023-08-08"))
.build()
)
.build()
mockMvc.post("/mtbfile") {
content = objectMapper.writeValueAsString(mtbFile)
contentType = MediaType.APPLICATION_JSON
}.andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
@Test
fun shouldProcessMtbFileDeleteRequest() {
mockMvc.delete("/mtbfile/TEST_12345678").andExpect {
status {
isAccepted()
}
}
verify(requestProcessor, times(1)).processDeletion(anyValueClass())
}
}