1
0
mirror of https://github.com/pcvolkmer/mv64e-etl-processor synced 2025-09-13 09:02:50 +00:00

17 Commits

Author SHA1 Message Date
Jakub Lidke
4bd6117ba8 fix: remove policyRule at consent resource 2025-09-05 16:00:11 +02:00
Jakub Lidke
ace5637ed8 test: added failing test 2025-09-05 15:58:31 +02:00
Jakub Lidke
88857cf201 refactor: cleanup GicsConsentService.java constant. 2025-09-05 14:59:12 +02:00
Jakub Lidke
db89d84353 fix: Setze in der Broad Consent Resource das MII Broad Consent Profil und die Consent Policy, falls diese fehlen sollten. Die von gICS einzeln gelieferte Consent Provisions werden in einer Ressource zusammengefasst. 2025-09-05 14:55:58 +02:00
3d9d84438d refactor: several changes related to code style and readability (#152)
* refactor: extract provision code extraction
* refactor: catch exceptions by type without later type check
* refactor: further code cleanup
* chore: log error with error level, not debug level
2025-09-04 12:47:56 +02:00
10b5bedac3 Merge branch '0.11.x'
# Conflicts:
#	build.gradle.kts
2025-09-03 22:03:52 +02:00
96f22a6744 feat: mark older request with unknown state (#150) 2025-09-03 21:30:36 +02:00
6dfec5c341 fix: add status badge for 'NO_CONSENT' (#149) 2025-09-03 21:18:28 +02:00
c38c0c6197 build: prepare for v0.12 development (#147) 2025-09-02 10:40:30 +02:00
4602032bcf chore: bump version 2025-09-01 13:33:29 +02:00
9cc9f130df chore: add custom banner file (#146) 2025-09-01 13:31:08 +02:00
b92fbae2c5 chore: update dependencies (#145) 2025-09-01 13:25:51 +02:00
5704282a1c docs: some additions to README.md (#143) 2025-08-28 19:37:57 +02:00
ba21d029d1 fix: add missing requestId to KafkaMtbFileSender (#142) 2025-08-27 15:07:43 +02:00
b7aa187293 fix: do not set unexpected config values (#141) 2025-08-26 09:16:07 +02:00
8402462c3b chore: use apache image including SSL config (#140)
The main purpose is to abandon bitnami kafka image.

The examples now include localhost certs and keys for development
purposes only.
More advanced support for SSL connections to kafka will be
available in later versions.
2025-08-25 12:43:32 +02:00
d3e6aa5821 fix: mime type representation in kafka header (#139) 2025-08-25 12:13:44 +02:00
18 changed files with 528 additions and 251 deletions

View File

@@ -268,7 +268,7 @@ zur Nutzung des MTB-File-Endpunkts eine HTTP-Basic-Authentifizierung voraussetze
![Tokenverwaltung](docs/tokens.png) ![Tokenverwaltung](docs/tokens.png)
In diesem Fall kann der Endpunkt für das Onkostar-Plugin * In diesem Fall kann der Endpunkt für das Onkostar-Plugin *
*[onkostar-plugin-dnpmexport](https://github.com/CCC-MF/onkostar-plugin-dnpmexport)** wie folgt *[mv64e-onkostar-plugin-export](https://github.com/pcvolkmer/mv64e-onkostar-plugin-export)** wie folgt
konfiguriert werden: konfiguriert werden:
``` ```
@@ -427,9 +427,9 @@ Die PEM-Datei mit dem/den Root CA Zertifikat(en) muss dabei im vorbereiteten Ver
#### Integration zur Laufzeit #### Integration zur Laufzeit
Hier muss die Umgebungsvariable `SERVICE_BINDING_ROOT` z.B. auf den Wert `/bindings` gesetzt sein. Hier muss die Umgebungsvariable `SERVICE_BINDING_ROOT` z.B. auf den Wert `/bindings` gesetzt sein.
Zudem muss ein Verzeichnis `bindings/ca-certificates` - analog zum Verzeichnis [ Zudem muss ein Verzeichnis `bindings/ca-certificates` - analog zum Verzeichnis
`bindings/ca-certificates`](bindings/ca-certificates) mit einer PEM-Datei als Docker-Volume [`bindings/ca-certificates`](bindings/ca-certificates) mit einer PEM-Datei und der
eingebunden werden. Datei [`bindings/ca-certificates/type`](bindings/ca-certificates/type) als Docker-Volume eingebunden werden.
Beispiel für Docker-Compose: Beispiel für Docker-Compose:

View File

@@ -5,24 +5,24 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins { plugins {
war war
id("org.springframework.boot") version "3.5.3" id("org.springframework.boot") version "3.5.5"
id("io.spring.dependency-management") version "1.1.7" id("io.spring.dependency-management") version "1.1.7"
kotlin("jvm") version "1.9.25" kotlin("jvm") version "2.2.10"
kotlin("plugin.spring") version "1.9.25" kotlin("plugin.spring") version "2.2.10"
jacoco jacoco
} }
group = "dev.dnpm" group = "dev.dnpm"
version = "0.11.0-SNAPSHOT" version = "0.12.0-SNAPSHOT"
var versions = mapOf( var versions = mapOf(
"mtb-dto" to "0.1.0-SNAPSHOT", "mtb-dto" to "0.1.0-SNAPSHOT",
"hapi-fhir" to "7.6.1", "hapi-fhir" to "8.4.0",
"mockito-kotlin" to "5.4.0", "mockito-kotlin" to "6.0.0",
"archunit" to "1.3.0", "archunit" to "1.4.1",
// Webjars // Webjars
"webjars-locator" to "0.52", "webjars-locator" to "0.52",
"echarts" to "5.4.3", "echarts" to "6.0.0",
"htmx.org" to "1.9.12" "htmx.org" to "1.9.12"
) )
@@ -111,6 +111,8 @@ dependencies {
integrationTestImplementation("com.tngtech.archunit:archunit:${versions["archunit"]}") integrationTestImplementation("com.tngtech.archunit:archunit:${versions["archunit"]}")
integrationTestImplementation("org.htmlunit:htmlunit") integrationTestImplementation("org.htmlunit:htmlunit")
integrationTestImplementation("org.springframework:spring-webflux") integrationTestImplementation("org.springframework:spring-webflux")
// Fix for CVE-2024-25710
integrationTestImplementation("org.apache.commons:commons-compress:1.26.0")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

View File

@@ -1,26 +1,121 @@
services: services:
kafka: kafka:
image: bitnami/kafka image: apache/kafka
hostname: kafka hostname: kafka
ports: ports:
- "9092:9092" - "9092:9092"
- "9094:9094" - "9094:9094"
environment: environment:
ALLOW_PLAINTEXT_LISTENER: "yes" ALLOW_PLAINTEXT_LISTENER: "yes"
KAFKA_CFG_NODE_ID: "0" KAFKA_NODE_ID: "0"
KAFKA_CFG_PROCESS_ROLES: "controller,broker" KAFKA_KRAFT_CLUSTER_ID: "mv64e-etl-processor-dev"
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094 KAFKA_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:9094 KAFKA_PROCESS_ROLES: "controller,broker"
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
healthcheck: KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
test: kafka-topics --bootstrap-server kafka:9092 --list KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
interval: 30s # Without SSL
timeout: 10s KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
retries: 3 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:9094
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
# Using SSL
# KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
# KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:9094
# KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:SSL,PLAINTEXT:PLAINTEXT
# KAFKA_SSL_KEYSTORE_TYPE: "PEM"
# KAFKA_SSL_KEYSTORE_CERTIFICATE_CHAIN: -----BEGIN CERTIFICATE-----
# MIIDCzCCAfOgAwIBAgIUaXNh4PahaKeLUaab2rUPSVESx28wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODEyMTFaFw0zNTA4
# MTkxODEyMTFaMBUxEzARBgNVBAMMCkV4YW1wbGUgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCsqalqVOLFglVbX9oSHU91ebyL1kPyb/2N90UGQIcD
# UAjzKxxysId1Vdvtbbwgli6UgfPwlzFP2Wlw51h496yL4QU/9tNV956UJ1RoS/fG
# qBAEHctqavfMI27UQmIzw4pGMkGzEQxRMc6a9pdabBhbMMTJsjtmOv2YMYHj1HHK
# Dr7wTBTt2l0eRyCR0kZ8XGIMWhYowPa4EMpC7+4e5Nf/7LSJZWLLy9jXPpazsjkJ
# jEmDNlFfx2tZiq0Wz2Xj1pZSDLbcuIX4IHcLfMvagibfrCMX/h6+WuW42sWPRuBW
# wB6cHGlXs+K/gBBWxtD7sOTacO5hbHFsfaJOhSEIGoIpAgMBAAGjUzBRMB0GA1Ud
# DgQWBBT2S/C2++ECY+CSuN5KKql0umfbTDAfBgNVHSMEGDAWgBT2S/C2++ECY+CS
# uN5KKql0umfbTDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj
# H4DdwqrOHg7sVsqiwDsZfTharpUDCYeG5XhrJQlnA9eKwyofTb929W/fjOwBdDtg
# 9THT/omR0lA8/UyHtezMT6nMsCn4HG2mXvx6ghgvA3jrFTEY7R80dHkboLMTV3u4
# RYgC9S3BJPcbJYpM0cXzkp2T0F4FxWZlfqefuedHuX3zcCxpgVD56qQb2a131TX7
# O3UDJfVg8a65IFtehndqILgLVrf7w6+pbmDAlCg5RKrt2USEYyZXYdyTryJbdtn4
# BCLp0avYtSYVUGwgH0oUCpkjQRwMg1003TTz8SNnmE7mAXHYljyYejnjL8vBHfch
# 8tTDVXQn08BT9H3jZTnF
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIC+TCCAeGgAwIBAgIUUoCwz8GS6xQ3mmI7RUUYSNPIOi4wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODE0NDhaFw0zNTA4
# MTkxODE0NDhaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAL9PW99MhhBwdEmTHyZgfnhfTrxZPrNU6z1UdV8b82Lk
# 3p75o8eCKa9iOd7DDQlo75hQBhhX0+Xc3mucrstx5p8TYFMbypif8ojWh3LM++P8
# tz3ezQZlq86ycyKpm8dqlA03b227tFDfiYTev2eD2HN40BU7yDAYhhqd/QW8+MV2
# jkcRGv5cE21GZxWmPUpkVN+bNoBC8H90WmkST90LfeYF+wZnlsAJZH6AQzR1GnGD
# ICE5evMhC78hvRnpgeA310SyxssZEigkePL5lTZOBPY2IuzBqL05agyVTiVq4Xd6
# y3xOqXoxxOhZu06yd3nymorqeTgbF1fW8wQF0u3KsFECAwEAAaNCMEAwHQYDVR0O
# BBYEFHk9jMWRAAt2YsBSxUcOQVoWayoHMB8GA1UdIwQYMBaAFPZL8Lb74QJj4JK4
# 3koqqXS6Z9tMMA0GCSqGSIb3DQEBCwUAA4IBAQBqabAA9INONDaLHqs9i9YQHm/g
# AnB7xRl/RFbERKKCTSMZUYM8oEaaH0W2ENoPMc/7xOB/R8a7Rm62PTr6syxwhZrY
# 5NtGKJOD+rh90/5l83tulf93KqOJtGkiv6NBDvCNrITcA+UKRk/z4GcFi2YjWAl4
# wvY44lzTasMKSpjUQ5N0VNANcW3nVuEgPQ8rrr0NOK/5j4guPjsXDsixa47gqblA
# 5xGfBKeVmEXdPbzawZfP4hPIw7DpX2m8Y0erswF1ZxkIV73V3TDsFSLcqSKSzZr6
# mtj8COlV9Us7zqaJbV5eOl7GN1T9orZJwZmX1Z46gCkkSLYDP/dqtl2j9JgN
# -----END CERTIFICATE-----
# KAFKA_SSL_KEYSTORE_KEY: -----BEGIN PRIVATE KEY-----
# MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/T1vfTIYQcHRJ
# kx8mYH54X068WT6zVOs9VHVfG/Ni5N6e+aPHgimvYjneww0JaO+YUAYYV9Pl3N5r
# nK7LceafE2BTG8qYn/KI1odyzPvj/Lc93s0GZavOsnMiqZvHapQNN29tu7RQ34mE
# 3r9ng9hzeNAVO8gwGIYanf0FvPjFdo5HERr+XBNtRmcVpj1KZFTfmzaAQvB/dFpp
# Ek/dC33mBfsGZ5bACWR+gEM0dRpxgyAhOXrzIQu/Ib0Z6YHgN9dEssbLGRIoJHjy
# +ZU2TgT2NiLswai9OWoMlU4lauF3est8Tql6McToWbtOsnd58pqK6nk4GxdX1vME
# BdLtyrBRAgMBAAECggEAC1wXfPlqxoQe65WAVoOJTvV90+JKvlRPCZu/wm+C8r7b
# Vz5Ekt6wQflHrWoQlpv0CivKSNzCONZ2IJazrGHti0mXwSeXzptEyApRDaiNVnrV
# mKdnrjcQThw7iPXgSaWS9/vwMmhgayLy5ABkBi4GhsjINlKP7wctw1vZP+N6NCNd
# Ql3taStvDKmG0SfJHF6/2o/XBpof3IJEL7ghbzyTTbWWaO34J1mJ8A+AmjGhj9GE
# Dp3XuOrO9W7MVd1nfZDtGBS8qf80AwROyodZZRma9vZuWJZ5aQFi2CnUEtU1T+Uv
# tW+F6tg2FOMr8M0Fb79wGIDwSF8u/QcTvwhEzZAfiQKBgQDioOofnE1oB1DOMnqZ
# SOFjs+vsirvS6G3lo27+HkE3TgvCHR4sk1305AiXtjmPu8iaUCo9qn18MtduY2RS
# CcKMOG/FxhmDyP5I29DhJRhvERIpJd0kcSDQOgtaoVPC1XzIlyTqte6nGX9kAnA/
# x/OOXrZ0hjhMNDcZzf2NasPYJQKBgQDYGqTobkVBk+eekNWklnTh41/649rUIgTu
# JStArtY2hgaEInYcGa2e7cEj7nIHA0iGy3EJ2yvwoUIyxtoXVcGohu2IrzlhS33T
# R4jA7nE2/yHZrEMEJovuSU0eMw7rgvEtL79Q0RToYnTY1EU6X/BBoFfiiEeNMHKz
# zjDOOQ6ZvQKBgGCWChIc0FSkwYiPtPZ9PCn89XCjk/cIPkYfiF9fT5Ydeh9pv4Fp
# 8SI8yXi3HgMnGhDCV65eagqztGMEky3voO2X4/MbQaaL0+wDWxuJbsdvNBk7XOt6
# F20HP+2JUiR4Ti1DVWV+0k5/LG7YJzTXp/KmZQZ2aan4mv8xbn2F4h/NAoGAI4ou
# OLN53FEQtHkpSYoc6tFUBZTXdi+qE+g09sxKGmlsROrN9c0bSpnbO6eJRTH7CYAH
# tRFAZrB+jI87ar8FvEuEYQhALYoWxVpsWR5drCfFT2EPHG2icavIbQEEoSLFuyKx
# Gf9oqtcWVFqEkBcbEg/mpDC5Y7TmCEAOsrubdRkCgYEAl7B+EzIdG0rabGoti09q
# QXfyiTjR7nQYkhpLxMCeNlCpQ8Y15XSa8bm1UIGYqj/ZBpeBNhrj64IHoub5Vd43
# tzbb8yNgoLUd16TU1VvyccCMGQVPIF8RkDsAtEawV2eoXbHAstN99xbC8jsLNZRQ
# fcfgTiQaXRJmlVx6jfbfZd4=
# -----END PRIVATE KEY-----
# #KAFKA_SSL_KEYSTORE_CREDENTIALS: example
# KAFKA_SSL_KEY_CREDENTIALS: example
# KAFKA_SSL_TRUSTSTORE_TYPE: "PEM"
# KAFKA_SSL_TRUSTSTORE_CERTIFICATES: -----BEGIN CERTIFICATE-----
# MIIDCzCCAfOgAwIBAgIUaXNh4PahaKeLUaab2rUPSVESx28wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODEyMTFaFw0zNTA4
# MTkxODEyMTFaMBUxEzARBgNVBAMMCkV4YW1wbGUgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCsqalqVOLFglVbX9oSHU91ebyL1kPyb/2N90UGQIcD
# UAjzKxxysId1Vdvtbbwgli6UgfPwlzFP2Wlw51h496yL4QU/9tNV956UJ1RoS/fG
# qBAEHctqavfMI27UQmIzw4pGMkGzEQxRMc6a9pdabBhbMMTJsjtmOv2YMYHj1HHK
# Dr7wTBTt2l0eRyCR0kZ8XGIMWhYowPa4EMpC7+4e5Nf/7LSJZWLLy9jXPpazsjkJ
# jEmDNlFfx2tZiq0Wz2Xj1pZSDLbcuIX4IHcLfMvagibfrCMX/h6+WuW42sWPRuBW
# wB6cHGlXs+K/gBBWxtD7sOTacO5hbHFsfaJOhSEIGoIpAgMBAAGjUzBRMB0GA1Ud
# DgQWBBT2S/C2++ECY+CSuN5KKql0umfbTDAfBgNVHSMEGDAWgBT2S/C2++ECY+CS
# uN5KKql0umfbTDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj
# H4DdwqrOHg7sVsqiwDsZfTharpUDCYeG5XhrJQlnA9eKwyofTb929W/fjOwBdDtg
# 9THT/omR0lA8/UyHtezMT6nMsCn4HG2mXvx6ghgvA3jrFTEY7R80dHkboLMTV3u4
# RYgC9S3BJPcbJYpM0cXzkp2T0F4FxWZlfqefuedHuX3zcCxpgVD56qQb2a131TX7
# O3UDJfVg8a65IFtehndqILgLVrf7w6+pbmDAlCg5RKrt2USEYyZXYdyTryJbdtn4
# BCLp0avYtSYVUGwgH0oUCpkjQRwMg1003TTz8SNnmE7mAXHYljyYejnjL8vBHfch
# 8tTDVXQn08BT9H3jZTnF
# -----END CERTIFICATE-----
# KAFKA_SSL_CLIENT_AUTH: none
###
## Use AKHQ as Kafka web frontend ## Use AKHQ as Kafka web frontend
akhq: akhq:

View File

@@ -282,6 +282,30 @@ class HomeControllerTest {
assertThat(page.querySelectorAll("tbody tr")).isEmpty() assertThat(page.querySelectorAll("tbody tr")).isEmpty()
assertThat(page.querySelectorAll("div.notification.info")).hasSize(1) assertThat(page.querySelectorAll("div.notification.info")).hasSize(1)
} }
@Test
@WithMockUser(username = "admin", roles = ["ADMIN"])
fun testShouldShowNoConsentStatusBadge() {
whenever(requestService.findRequestByPatientId(anyValueClass(), any<Pageable>())).thenReturn(
PageImpl(
listOf(
Request(
1L,
randomRequestId(),
PatientPseudonym("PSEUDO1"),
PatientId("PATIENT1"),
Fingerprint("ashdkasdh"),
RequestType.MTB_FILE,
RequestStatus.NO_CONSENT
)
)
)
)
val page = webClient.getPage<HtmlPage>("http://localhost/patient/PSEUDO1")
assertThat(page.querySelectorAll("tbody tr")).hasSize(1)
assertThat(page.querySelectorAll("tbody tr > td > small").first().textContent).isEqualTo("NO_CONSENT")
}
} }
} }

View File

@@ -39,12 +39,14 @@ public class GicsConsentService implements IConsentService {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final FhirContext fhirContext; private final FhirContext fhirContext;
private final GIcsConfigProperties gIcsConfigProperties; private final GIcsConfigProperties gIcsConfigProperties;
private final String BROAD_CONSENT_PROFILE_URI = "https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung";
private final String BROAD_CONSENT_POLICY = "urn:oid:2.16.840.1.113883.3.1937.777.24.2.1791";
public GicsConsentService( public GicsConsentService(
GIcsConfigProperties gIcsConfigProperties, GIcsConfigProperties gIcsConfigProperties,
RetryTemplate retryTemplate, RetryTemplate retryTemplate,
RestTemplate restTemplate, RestTemplate restTemplate,
AppFhirConfig appFhirConfig AppFhirConfig appFhirConfig
) { ) {
this.retryTemplate = retryTemplate; this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate; this.restTemplate = restTemplate;
@@ -54,34 +56,34 @@ public class GicsConsentService implements IConsentService {
} }
protected Parameters getFhirRequestParameters( protected Parameters getFhirRequestParameters(
String personIdentifierValue String personIdentifierValue
) { ) {
var result = new Parameters(); var result = new Parameters();
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("personIdentifier") .setName("personIdentifier")
.setValue( .setValue(
new Identifier() new Identifier()
.setValue(personIdentifierValue) .setValue(personIdentifierValue)
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
) )
); );
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("domain") .setName("domain")
.setValue( .setValue(
new StringType() new StringType()
.setValue(this.gIcsConfigProperties.getBroadConsentDomainName()) .setValue(this.gIcsConfigProperties.getBroadConsentDomainName())
) )
); );
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("policy") .setName("policy")
.setValue( .setValue(
new Coding() new Coding()
.setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode()) .setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode())
.setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem()) .setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem())
) )
); );
/* /*
@@ -89,10 +91,10 @@ public class GicsConsentService implements IConsentService {
* 'ignoreVersionNumber'. * 'ignoreVersionNumber'.
*/ */
result.addParameter( result.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("version") .setName("version")
.setValue(new StringType().setValue("1.1") .setValue(new StringType().setValue("1.1")
) )
); );
/* add config parameter with: /* add config parameter with:
@@ -101,17 +103,17 @@ public class GicsConsentService implements IConsentService {
* unknownStateIsConsideredAsDecline -> true * unknownStateIsConsideredAsDecline -> true
*/ */
var config = new ParametersParameterComponent() var config = new ParametersParameterComponent()
.setName("config") .setName("config")
.addPart( .addPart(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("ignoreVersionNumber") .setName("ignoreVersionNumber")
.setValue(new BooleanType().setValue(true)) .setValue(new BooleanType().setValue(true))
) )
.addPart( .addPart(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("unknownStateIsConsideredAsDecline") .setName("unknownStateIsConsideredAsDecline")
.setValue(new BooleanType().setValue(false)) .setValue(new BooleanType().setValue(false))
); );
result.addParameter(config); result.addParameter(config);
@@ -130,8 +132,8 @@ public class GicsConsentService implements IConsentService {
headers.setContentType(MediaType.APPLICATION_XML); headers.setContentType(MediaType.APPLICATION_XML);
if ( if (
StringUtils.isBlank(this.gIcsConfigProperties.getUsername()) StringUtils.isBlank(this.gIcsConfigProperties.getUsername())
|| StringUtils.isBlank(this.gIcsConfigProperties.getPassword()) || StringUtils.isBlank(this.gIcsConfigProperties.getPassword())
) { ) {
return headers; return headers;
} }
@@ -145,28 +147,28 @@ public class GicsConsentService implements IConsentService {
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth()); HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth());
try { try {
var responseEntity = retryTemplate.execute( var responseEntity = retryTemplate.execute(
ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class) ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class)
); );
if (responseEntity.getStatusCode().is2xxSuccessful()) { if (responseEntity.getStatusCode().is2xxSuccessful()) {
return responseEntity.getBody(); return responseEntity.getBody();
} else { } else {
var msg = String.format( var msg = String.format(
"Trusted party system reached but request failed! code: '%s' response: '%s'", "Trusted party system reached but request failed! code: '%s' response: '%s'",
responseEntity.getStatusCode(), responseEntity.getBody()); responseEntity.getStatusCode(), responseEntity.getBody());
log.error(msg); log.error(msg);
return null; return null;
} }
} catch (RestClientException e) { } catch (RestClientException e) {
var msg = String.format("Get consents status request failed reason: '%s", var msg = String.format("Get consents status request failed reason: '%s",
e.getMessage()); e.getMessage());
log.error(msg); log.error(msg);
return null; return null;
} catch (TerminatedRetryException terminatedRetryException) { } catch (TerminatedRetryException terminatedRetryException) {
var msg = String.format( var msg = String.format(
"Get consents status process has been terminated. termination reason: '%s", "Get consents status process has been terminated. termination reason: '%s",
terminatedRetryException.getMessage()); terminatedRetryException.getMessage());
log.error(msg); log.error(msg);
return null; return null;
} }
@@ -175,45 +177,45 @@ public class GicsConsentService implements IConsentService {
@Override @Override
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) { public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
var consentStatusResponse = callGicsApi( var consentStatusResponse = callGicsApi(
getFhirRequestParameters(personIdentifierValue), getFhirRequestParameters(personIdentifierValue),
GicsConsentService.IS_CONSENTED_ENDPOINT GicsConsentService.IS_CONSENTED_ENDPOINT
); );
return evaluateConsentResponse(consentStatusResponse); return evaluateConsentResponse(consentStatusResponse);
} }
protected Bundle currentConsentForPersonAndTemplate( protected Bundle currentConsentForPersonAndTemplate(
String personIdentifierValue, String personIdentifierValue,
ConsentDomain consentDomain, ConsentDomain consentDomain,
Date requestDate Date requestDate
) { ) {
var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson( var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson(
personIdentifierValue, personIdentifierValue,
requestDate, requestDate,
consentDomain consentDomain
); );
var consentDataSerialized = callGicsApi(requestParameter, var consentDataSerialized = callGicsApi(requestParameter,
GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT); GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
if (consentDataSerialized == null) { if (consentDataSerialized == null) {
// error occurred - should not process further! // error occurred - should not process further!
throw new IllegalStateException( throw new IllegalStateException(
"consent data request failed - stopping processing! - try again or fix other problems first."); "consent data request failed - stopping processing! - try again or fix other problems first.");
} }
var iBaseResource = fhirContext.newJsonParser() var iBaseResource = fhirContext.newJsonParser()
.parseResource(consentDataSerialized); .parseResource(consentDataSerialized);
if (iBaseResource instanceof OperationOutcome) { if (iBaseResource instanceof OperationOutcome) {
// log error - very likely a configuration error // log error - very likely a configuration error
String errorMessage = String errorMessage =
"Consent request failed! Check outcome:\n " + consentDataSerialized; "Consent request failed! Check outcome:\n " + consentDataSerialized;
log.error(errorMessage); log.error(errorMessage);
throw new IllegalStateException(errorMessage); throw new IllegalStateException(errorMessage);
} else if (iBaseResource instanceof Bundle bundle) { } else if (iBaseResource instanceof Bundle bundle) {
return bundle; return bundle;
} else { } else {
String errorMessage = "Consent request failed! Unexpected response received! -> " String errorMessage = "Consent request failed! Unexpected response received! -> "
+ consentDataSerialized; + consentDataSerialized;
log.error(errorMessage); log.error(errorMessage);
throw new IllegalStateException(errorMessage); throw new IllegalStateException(errorMessage);
} }
@@ -228,43 +230,43 @@ public class GicsConsentService implements IConsentService {
} }
protected Parameters buildRequestParameterCurrentPolicyStatesForPerson( protected Parameters buildRequestParameterCurrentPolicyStatesForPerson(
String personIdentifierValue, String personIdentifierValue,
Date requestDate, Date requestDate,
ConsentDomain consentDomain ConsentDomain consentDomain
) { ) {
var requestParameter = new Parameters(); var requestParameter = new Parameters();
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("personIdentifier") .setName("personIdentifier")
.setValue( .setValue(
new Identifier() new Identifier()
.setValue(personIdentifierValue) .setValue(personIdentifierValue)
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem()) .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
) )
); );
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("domain") .setName("domain")
.setValue(new StringType().setValue(getConsentDomainName(consentDomain))) .setValue(new StringType().setValue(getConsentDomainName(consentDomain)))
); );
Parameters nestedConfigParameters = new Parameters(); Parameters nestedConfigParameters = new Parameters();
nestedConfigParameters nestedConfigParameters
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("idMatchingType") .setName("idMatchingType")
.setValue(new Coding() .setValue(new Coding()
.setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType") .setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
.setCode("AT_LEAST_ONE") .setCode("AT_LEAST_ONE")
) )
) )
.addParameter("ignoreVersionNumber", false) .addParameter("ignoreVersionNumber", false)
.addParameter("unknownStateIsConsideredAsDecline", false) .addParameter("unknownStateIsConsideredAsDecline", false)
.addParameter("requestDate", new DateType().setValue(requestDate)); .addParameter("requestDate", new DateType().setValue(requestDate));
requestParameter.addParameter( requestParameter.addParameter(
new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters) new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters)
); );
return requestParameter; return requestParameter;
@@ -291,7 +293,7 @@ public class GicsConsentService implements IConsentService {
} }
} else if (response instanceof OperationOutcome outcome) { } else if (response instanceof OperationOutcome outcome) {
log.error("failed to get consent status from ttp. probably configuration error. " log.error("failed to get consent status from ttp. probably configuration error. "
+ "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome)); + "outcome: '{}'", fhirContext.newJsonParser().encodeToString(outcome));
} }
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
@@ -302,6 +304,36 @@ public class GicsConsentService implements IConsentService {
@Override @Override
public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) { public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) {
return currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate); Bundle gIcsResultBundle = currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate);
if (ConsentDomain.BROAD_CONSENT == consentDomain) {
return convertGicsResultToMiiBroadConsent(gIcsResultBundle);
}
return gIcsResultBundle;
}
protected Bundle convertGicsResultToMiiBroadConsent(Bundle gIcsResultBundle) {
if (gIcsResultBundle == null
|| gIcsResultBundle.getEntry().isEmpty()
|| !(gIcsResultBundle.getEntry().getFirst().getResource() instanceof Consent))
return gIcsResultBundle;
Bundle.BundleEntryComponent bundleEntryComponent = gIcsResultBundle.getEntry().getFirst();
var consentAsOne = (Consent) bundleEntryComponent.getResource();
if (consentAsOne.getPolicy().stream().noneMatch(p -> p.getUri().equals(BROAD_CONSENT_POLICY))) {
consentAsOne.addPolicy(new Consent.ConsentPolicyComponent().setUri(BROAD_CONSENT_POLICY));
}
if (consentAsOne.getMeta().getProfile().stream().noneMatch(p -> p.getValue().equals(BROAD_CONSENT_PROFILE_URI))) {
consentAsOne.getMeta().addProfile(BROAD_CONSENT_PROFILE_URI);
}
consentAsOne.setPolicyRule(null);
gIcsResultBundle.getEntry().stream().skip(1).forEach(c -> consentAsOne.getProvision().addProvision(((Consent) c.getResource()).getProvision().getProvisionFirstRep()));
gIcsResultBundle.getEntry().clear();
gIcsResultBundle.addEntry(bundleEntryComponent);
return gIcsResultBundle;
} }
} }

View File

@@ -23,8 +23,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.AppFhirConfig;
import dev.dnpm.etl.processor.config.GPasConfigProperties; import dev.dnpm.etl.processor.config.GPasConfigProperties;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.hc.core5.net.URIBuilder; import org.apache.hc.core5.net.URIBuilder;
@@ -39,9 +37,11 @@ import org.springframework.http.*;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.HttpClientErrorException.BadRequest; import org.springframework.web.client.HttpClientErrorException.BadRequest;
import org.springframework.web.client.HttpClientErrorException.Unauthorized; import org.springframework.web.client.HttpClientErrorException.Unauthorized;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.URISyntaxException;
public class GpasPseudonymGenerator implements Generator { public class GpasPseudonymGenerator implements Generator {
private final FhirContext r4Context; private final FhirContext r4Context;
@@ -52,10 +52,10 @@ public class GpasPseudonymGenerator implements Generator {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final @NotNull String genomDeTanDomain; private final @NotNull String genomDeTanDomain;
private final @NotNull String pidPsnDomain; private final @NotNull String pidPsnDomain;
protected final static String createOrGetPsn = "$pseudonymizeAllowCreate"; protected static final String CREATE_OR_GET_PSN = "$pseudonymizeAllowCreate";
protected final static String createMultiDomainPsn = "$pseudonymize-secondary"; protected static final String CREATE_MULTI_DOMAIN_PSN = "$pseudonymize-secondary";
private final static String SINGLE_PSN_PART_NAME = "pseudonym"; private static final String SINGLE_PSN_PART_NAME = "pseudonym";
private final static String MULTI_PSN_PART_NAME = "value"; private static final String MULTI_PSN_PART_NAME = "value";
public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate, public GpasPseudonymGenerator(GPasConfigProperties gpasCfg, RetryTemplate retryTemplate,
RestTemplate restTemplate, AppFhirConfig appFhirConfig) { RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
@@ -85,7 +85,7 @@ public class GpasPseudonymGenerator implements Generator {
switch (domainType) { switch (domainType) {
case SINGLE_PSN_DOMAIN -> { case SINGLE_PSN_DOMAIN -> {
final var requestBody = createSinglePsnRequestBody(id, pidPsnDomain); final var requestBody = createSinglePsnRequestBody(id, pidPsnDomain);
final var responseEntity = getGpasPseudonym(requestBody, createOrGetPsn); final var responseEntity = getGpasPseudonym(requestBody, CREATE_OR_GET_PSN);
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser() final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
.parseResource(responseEntity.getBody()); .parseResource(responseEntity.getBody());
@@ -93,7 +93,7 @@ public class GpasPseudonymGenerator implements Generator {
} }
case MULTI_PSN_DOMAIN -> { case MULTI_PSN_DOMAIN -> {
final var requestBody = createMultiPsnRequestBody(id, genomDeTanDomain); final var requestBody = createMultiPsnRequestBody(id, genomDeTanDomain);
final var responseEntity = getGpasPseudonym(requestBody, createMultiDomainPsn); final var responseEntity = getGpasPseudonym(requestBody, CREATE_MULTI_DOMAIN_PSN);
final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser() final var gPasPseudonymResult = (Parameters) r4Context.newJsonParser()
.parseResource(responseEntity.getBody()); .parseResource(responseEntity.getBody());
@@ -150,23 +150,22 @@ public class GpasPseudonymGenerator implements Generator {
log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode()); log.debug("API request succeeded. Response: {}", responseEntity.getStatusCode());
return responseEntity; return responseEntity;
} }
} catch (RestClientException rce) { } catch (BadRequest e) {
if (rce instanceof BadRequest) { String msg = "gPas or request configuration is incorrect. Please check both."
String msg = "gPas or request configuration is incorrect. Please check both." + e.getMessage();
+ rce.getMessage(); log.error(msg);
log.debug( throw new PseudonymRequestFailed(msg, e);
msg); } catch (Unauthorized e) {
throw new PseudonymRequestFailed(msg, rce); var msg = "gPas access credentials are invalid check your configuration. msg: '%s"
} .formatted(e.getMessage());
if (rce instanceof Unauthorized) { log.error(msg);
var msg = "gPas access credentials are invalid check your configuration. msg: '%s".formatted( throw new PseudonymRequestFailed(msg, e);
rce.getMessage()); }
log.error(msg); catch (Exception unexpected) {
throw new PseudonymRequestFailed(msg, rce);
}
} catch (Exception unexpected) {
throw new PseudonymRequestFailed( throw new PseudonymRequestFailed(
"API request due unexpected error unsuccessful gPas unsuccessful.", unexpected); "API request due unexpected error unsuccessful gPas unsuccessful.",
unexpected
);
} }
throw new PseudonymRequestFailed( throw new PseudonymRequestFailed(
"API request due unexpected error unsuccessful gPas unsuccessful."); "API request due unexpected error unsuccessful gPas unsuccessful.");

View File

@@ -31,6 +31,7 @@ import org.apache.kafka.clients.consumer.ConsumerRecord
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.kafka.listener.MessageListener import org.springframework.kafka.listener.MessageListener
import java.nio.charset.Charset
class KafkaInputListener( class KafkaInputListener(
private val requestProcessor: RequestProcessor, private val requestProcessor: RequestProcessor,
@@ -49,19 +50,16 @@ class KafkaInputListener(
} }
} }
private fun guessMimeType(record: ConsumerRecord<String, String>): String { private fun guessMimeType(record: ConsumerRecord<String, String>): String? {
if (record.headers().headers("contentType").toList().isEmpty()) { if (record.headers().headers("contentType").toList().isEmpty()) {
// Fallback if no contentType set (old behavior) // Fallback if no contentType set (old behavior)
return MediaType.APPLICATION_JSON_VALUE return MediaType.APPLICATION_JSON_VALUE
} }
return record.headers().headers("contentType")?.firstOrNull()?.value().contentToString() return record.headers().headers("contentType")?.firstOrNull()?.value()?.toString(Charset.forName("UTF-8"))
} }
private fun handleDnpmV2Message(record: ConsumerRecord<String, String>) { private fun handleDnpmV2Message(record: ConsumerRecord<String, String>) {
// Do not handle DNPM-V2 for now
logger.warn("Ignoring MTB File in DNPM V2 format: Not implemented yet")
val mtbFile = objectMapper.readValue(record.value(), Mtb::class.java) val mtbFile = objectMapper.readValue(record.value(), Mtb::class.java)
val patientId = PatientId(mtbFile.patient.id) val patientId = PatientId(mtbFile.patient.id)
val firstRequestIdHeader = record.headers().headers("requestId")?.firstOrNull() val firstRequestIdHeader = record.headers().headers("requestId")?.firstOrNull()

View File

@@ -30,6 +30,7 @@ import org.springframework.data.relational.core.mapping.Table
import org.springframework.data.repository.CrudRepository import org.springframework.data.repository.CrudRepository
import org.springframework.data.repository.PagingAndSortingRepository import org.springframework.data.repository.PagingAndSortingRepository
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
@Table("request") @Table("request")
@@ -65,6 +66,12 @@ data class Request(
processedAt: Instant processedAt: Instant
) : ) :
this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt) this(null, uuid, patientPseudonym, pid, fingerprint, type, status, processedAt)
fun isPendingUnknown(): Boolean {
return this.status == RequestStatus.UNKNOWN && this.processedAt.isBefore(
Instant.now().minus(10, ChronoUnit.MINUTES)
)
}
} }
@JvmRecord @JvmRecord
@@ -90,19 +97,23 @@ interface RequestRepository : CrudRepository<Request, Long>, PagingAndSortingRep
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;") @Query("SELECT count(*) AS count, status FROM request WHERE type = 'MTB_FILE' GROUP BY status ORDER BY status, count DESC;")
fun countStates(): List<CountedState> fun countStates(): List<CountedState>
@Query("SELECT count(*) AS count, status FROM (" + @Query(
"SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " + "SELECT count(*) AS count, status FROM (" +
"WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " + "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;") "WHERE type = 'MTB_FILE' AND status NOT IN ('DUPLICATION') " +
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
)
fun findPatientUniqueStates(): List<CountedState> fun findPatientUniqueStates(): List<CountedState>
@Query("SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;") @Query("SELECT count(*) AS count, status FROM request WHERE type = 'DELETE' GROUP BY status ORDER BY status, count DESC;")
fun countDeleteStates(): List<CountedState> fun countDeleteStates(): List<CountedState>
@Query("SELECT count(*) AS count, status FROM (" + @Query(
"SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " + "SELECT count(*) AS count, status FROM (" +
"WHERE type = 'DELETE'" + "SELECT status, rank() OVER (PARTITION BY patient_pseudonym ORDER BY processed_at DESC) AS rank FROM request " +
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;") "WHERE type = 'DELETE'" +
") rank WHERE rank = 1 GROUP BY status ORDER BY status, count DESC;"
)
fun findPatientUniqueDeleteStates(): List<CountedState> fun findPatientUniqueDeleteStates(): List<CountedState>
} }

View File

@@ -27,7 +27,6 @@ import dev.pcvolkmer.mv64e.mtb.Mtb
import dev.pcvolkmer.mv64e.mtb.MvhMetadata import dev.pcvolkmer.mv64e.mtb.MvhMetadata
import org.apache.kafka.clients.producer.ProducerRecord import org.apache.kafka.clients.producer.ProducerRecord
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.kafka.core.KafkaTemplate import org.springframework.kafka.core.KafkaTemplate
import org.springframework.retry.support.RetryTemplate import org.springframework.retry.support.RetryTemplate
@@ -47,8 +46,9 @@ class KafkaMtbFileSender(
ProducerRecord( ProducerRecord(
kafkaProperties.outputTopic, kafkaProperties.outputTopic,
key(request), key(request),
objectMapper.writeValueAsString(request) objectMapper.writeValueAsString(request),
) )
record.headers().add("requestId", request.requestId.value.toByteArray())
when (request) { when (request) {
is DnpmV2MtbFileRequest -> record.headers() is DnpmV2MtbFileRequest -> record.headers()
.add( .add(
@@ -82,7 +82,6 @@ class KafkaMtbFileSender(
ProducerRecord( ProducerRecord(
kafkaProperties.outputTopic, kafkaProperties.outputTopic,
key(request), key(request),
// Always use old BwhcV1FileRequest with Consent REJECT
objectMapper.writeValueAsString( objectMapper.writeValueAsString(
DnpmV2MtbFileRequest( DnpmV2MtbFileRequest(
request.requestId, request.requestId,
@@ -90,7 +89,7 @@ class KafkaMtbFileSender(
) )
) )
) )
record.headers().add("requestId", request.requestId.value.toByteArray())
val result = kafkaTemplate.send(record) val result = kafkaTemplate.send(record)
if (result.get() != null) { if (result.get() != null) {
logger.debug("Sent deletion request via KafkaMtbFileSender") logger.debug("Sent deletion request via KafkaMtbFileSender")

View File

@@ -137,15 +137,7 @@ class ConsentProcessor(
} }
val provisionComponent: ProvisionComponent = provisions.first() val provisionComponent: ProvisionComponent = provisions.first()
val provisionCode = getProvisionCode(provisionComponent)
var provisionCode: String? = null
if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
val codableConcept: CodeableConcept = provisionComponent.code.first()
if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
provisionCode = codableConcept.coding.first().code
}
}
if (provisionCode != null) { if (provisionCode != null) {
try { try {
val modelProjectConsentPurpose = val modelProjectConsentPurpose =
@@ -177,6 +169,17 @@ class ConsentProcessor(
} }
} }
private fun getProvisionCode(provisionComponent: ProvisionComponent): String? {
var provisionCode: String? = null
if (provisionComponent.code != null && provisionComponent.code.isNotEmpty()) {
val codableConcept: CodeableConcept = provisionComponent.code.first()
if (codableConcept.coding != null && codableConcept.coding.isNotEmpty()) {
provisionCode = codableConcept.coding.first().code
}
}
return provisionCode
}
private fun setGenomDeSubmissionType(mtbFile: Mtb) { private fun setGenomDeSubmissionType(mtbFile: Mtb) {
if (appConfigProperties.genomDeTestSubmission) { if (appConfigProperties.genomDeTestSubmission) {
mtbFile.metadata.type = MvhSubmissionType.TEST mtbFile.metadata.type = MvhSubmissionType.TEST
@@ -238,9 +241,9 @@ class ConsentProcessor(
consent.provision.provision.filter { subProvision -> consent.provision.provision.filter { subProvision ->
isRequestDateInRange(requestDate, subProvision.period) isRequestDateInRange(requestDate, subProvision.period)
// search coding entries of current provision for code and system // search coding entries of current provision for code and system
subProvision.code.map { c -> c.coding }.flatten().firstOrNull { code -> subProvision.code.map { c -> c.coding }.flatten().any { code ->
targetCode.equals(code.code) && targetSystem.equals(code.system) targetCode.equals(code.code) && targetSystem.equals(code.system)
} != null }
}.map { subProvision -> }.map { subProvision ->
subProvision subProvision
} }
@@ -257,11 +260,11 @@ class ConsentProcessor(
researchAllowedPolicySystem: String?, researchAllowedPolicySystem: String?,
policyRules: Collection<Coding> policyRules: Collection<Coding>
): Boolean { ): Boolean {
return policyRules.find { code -> return policyRules.any { code ->
researchAllowedPolicySystem.equals(code.getSystem()) && (researchAllowedPolicyOid.equals( researchAllowedPolicySystem.equals(code.getSystem()) && (researchAllowedPolicyOid.equals(
code.getCode() code.getCode()
)) ))
} != null }
} }
fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean { fun isRequestDateInRange(requestDate: Date?, provPeriod: Period): Boolean {

View File

@@ -18,13 +18,107 @@ spring:
issuer-uri: https://dnpm.dev/auth/realms/intern issuer-uri: https://dnpm.dev/auth/realms/intern
user-name-attribute: name user-name-attribute: name
# kafka:
# security:
# protocol: "SSL"
# ssl:
# key-store-type: "PEM"
# key-store-certificate-chain: -----BEGIN CERTIFICATE-----
# MIIDCzCCAfOgAwIBAgIUaXNh4PahaKeLUaab2rUPSVESx28wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODEyMTFaFw0zNTA4
# MTkxODEyMTFaMBUxEzARBgNVBAMMCkV4YW1wbGUgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCsqalqVOLFglVbX9oSHU91ebyL1kPyb/2N90UGQIcD
# UAjzKxxysId1Vdvtbbwgli6UgfPwlzFP2Wlw51h496yL4QU/9tNV956UJ1RoS/fG
# qBAEHctqavfMI27UQmIzw4pGMkGzEQxRMc6a9pdabBhbMMTJsjtmOv2YMYHj1HHK
# Dr7wTBTt2l0eRyCR0kZ8XGIMWhYowPa4EMpC7+4e5Nf/7LSJZWLLy9jXPpazsjkJ
# jEmDNlFfx2tZiq0Wz2Xj1pZSDLbcuIX4IHcLfMvagibfrCMX/h6+WuW42sWPRuBW
# wB6cHGlXs+K/gBBWxtD7sOTacO5hbHFsfaJOhSEIGoIpAgMBAAGjUzBRMB0GA1Ud
# DgQWBBT2S/C2++ECY+CSuN5KKql0umfbTDAfBgNVHSMEGDAWgBT2S/C2++ECY+CS
# uN5KKql0umfbTDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj
# H4DdwqrOHg7sVsqiwDsZfTharpUDCYeG5XhrJQlnA9eKwyofTb929W/fjOwBdDtg
# 9THT/omR0lA8/UyHtezMT6nMsCn4HG2mXvx6ghgvA3jrFTEY7R80dHkboLMTV3u4
# RYgC9S3BJPcbJYpM0cXzkp2T0F4FxWZlfqefuedHuX3zcCxpgVD56qQb2a131TX7
# O3UDJfVg8a65IFtehndqILgLVrf7w6+pbmDAlCg5RKrt2USEYyZXYdyTryJbdtn4
# BCLp0avYtSYVUGwgH0oUCpkjQRwMg1003TTz8SNnmE7mAXHYljyYejnjL8vBHfch
# 8tTDVXQn08BT9H3jZTnF
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIC+TCCAeGgAwIBAgIUUoCwz8GS6xQ3mmI7RUUYSNPIOi4wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODE0NDhaFw0zNTA4
# MTkxODE0NDhaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAL9PW99MhhBwdEmTHyZgfnhfTrxZPrNU6z1UdV8b82Lk
# 3p75o8eCKa9iOd7DDQlo75hQBhhX0+Xc3mucrstx5p8TYFMbypif8ojWh3LM++P8
# tz3ezQZlq86ycyKpm8dqlA03b227tFDfiYTev2eD2HN40BU7yDAYhhqd/QW8+MV2
# jkcRGv5cE21GZxWmPUpkVN+bNoBC8H90WmkST90LfeYF+wZnlsAJZH6AQzR1GnGD
# ICE5evMhC78hvRnpgeA310SyxssZEigkePL5lTZOBPY2IuzBqL05agyVTiVq4Xd6
# y3xOqXoxxOhZu06yd3nymorqeTgbF1fW8wQF0u3KsFECAwEAAaNCMEAwHQYDVR0O
# BBYEFHk9jMWRAAt2YsBSxUcOQVoWayoHMB8GA1UdIwQYMBaAFPZL8Lb74QJj4JK4
# 3koqqXS6Z9tMMA0GCSqGSIb3DQEBCwUAA4IBAQBqabAA9INONDaLHqs9i9YQHm/g
# AnB7xRl/RFbERKKCTSMZUYM8oEaaH0W2ENoPMc/7xOB/R8a7Rm62PTr6syxwhZrY
# 5NtGKJOD+rh90/5l83tulf93KqOJtGkiv6NBDvCNrITcA+UKRk/z4GcFi2YjWAl4
# wvY44lzTasMKSpjUQ5N0VNANcW3nVuEgPQ8rrr0NOK/5j4guPjsXDsixa47gqblA
# 5xGfBKeVmEXdPbzawZfP4hPIw7DpX2m8Y0erswF1ZxkIV73V3TDsFSLcqSKSzZr6
# mtj8COlV9Us7zqaJbV5eOl7GN1T9orZJwZmX1Z46gCkkSLYDP/dqtl2j9JgN
# -----END CERTIFICATE-----
# ### For dev/testing purposes only!
# key-store-key: -----BEGIN PRIVATE KEY-----
# MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/T1vfTIYQcHRJ
# kx8mYH54X068WT6zVOs9VHVfG/Ni5N6e+aPHgimvYjneww0JaO+YUAYYV9Pl3N5r
# nK7LceafE2BTG8qYn/KI1odyzPvj/Lc93s0GZavOsnMiqZvHapQNN29tu7RQ34mE
# 3r9ng9hzeNAVO8gwGIYanf0FvPjFdo5HERr+XBNtRmcVpj1KZFTfmzaAQvB/dFpp
# Ek/dC33mBfsGZ5bACWR+gEM0dRpxgyAhOXrzIQu/Ib0Z6YHgN9dEssbLGRIoJHjy
# +ZU2TgT2NiLswai9OWoMlU4lauF3est8Tql6McToWbtOsnd58pqK6nk4GxdX1vME
# BdLtyrBRAgMBAAECggEAC1wXfPlqxoQe65WAVoOJTvV90+JKvlRPCZu/wm+C8r7b
# Vz5Ekt6wQflHrWoQlpv0CivKSNzCONZ2IJazrGHti0mXwSeXzptEyApRDaiNVnrV
# mKdnrjcQThw7iPXgSaWS9/vwMmhgayLy5ABkBi4GhsjINlKP7wctw1vZP+N6NCNd
# Ql3taStvDKmG0SfJHF6/2o/XBpof3IJEL7ghbzyTTbWWaO34J1mJ8A+AmjGhj9GE
# Dp3XuOrO9W7MVd1nfZDtGBS8qf80AwROyodZZRma9vZuWJZ5aQFi2CnUEtU1T+Uv
# tW+F6tg2FOMr8M0Fb79wGIDwSF8u/QcTvwhEzZAfiQKBgQDioOofnE1oB1DOMnqZ
# SOFjs+vsirvS6G3lo27+HkE3TgvCHR4sk1305AiXtjmPu8iaUCo9qn18MtduY2RS
# CcKMOG/FxhmDyP5I29DhJRhvERIpJd0kcSDQOgtaoVPC1XzIlyTqte6nGX9kAnA/
# x/OOXrZ0hjhMNDcZzf2NasPYJQKBgQDYGqTobkVBk+eekNWklnTh41/649rUIgTu
# JStArtY2hgaEInYcGa2e7cEj7nIHA0iGy3EJ2yvwoUIyxtoXVcGohu2IrzlhS33T
# R4jA7nE2/yHZrEMEJovuSU0eMw7rgvEtL79Q0RToYnTY1EU6X/BBoFfiiEeNMHKz
# zjDOOQ6ZvQKBgGCWChIc0FSkwYiPtPZ9PCn89XCjk/cIPkYfiF9fT5Ydeh9pv4Fp
# 8SI8yXi3HgMnGhDCV65eagqztGMEky3voO2X4/MbQaaL0+wDWxuJbsdvNBk7XOt6
# F20HP+2JUiR4Ti1DVWV+0k5/LG7YJzTXp/KmZQZ2aan4mv8xbn2F4h/NAoGAI4ou
# OLN53FEQtHkpSYoc6tFUBZTXdi+qE+g09sxKGmlsROrN9c0bSpnbO6eJRTH7CYAH
# tRFAZrB+jI87ar8FvEuEYQhALYoWxVpsWR5drCfFT2EPHG2icavIbQEEoSLFuyKx
# Gf9oqtcWVFqEkBcbEg/mpDC5Y7TmCEAOsrubdRkCgYEAl7B+EzIdG0rabGoti09q
# QXfyiTjR7nQYkhpLxMCeNlCpQ8Y15XSa8bm1UIGYqj/ZBpeBNhrj64IHoub5Vd43
# tzbb8yNgoLUd16TU1VvyccCMGQVPIF8RkDsAtEawV2eoXbHAstN99xbC8jsLNZRQ
# fcfgTiQaXRJmlVx6jfbfZd4=
# -----END PRIVATE KEY-----
# trust-store-type: "PEM"
# trust-store-certificates: -----BEGIN CERTIFICATE-----
# MIIDCzCCAfOgAwIBAgIUaXNh4PahaKeLUaab2rUPSVESx28wDQYJKoZIhvcNAQEL
# BQAwFTETMBEGA1UEAwwKRXhhbXBsZSBDQTAeFw0yNTA4MjExODEyMTFaFw0zNTA4
# MTkxODEyMTFaMBUxEzARBgNVBAMMCkV4YW1wbGUgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCsqalqVOLFglVbX9oSHU91ebyL1kPyb/2N90UGQIcD
# UAjzKxxysId1Vdvtbbwgli6UgfPwlzFP2Wlw51h496yL4QU/9tNV956UJ1RoS/fG
# qBAEHctqavfMI27UQmIzw4pGMkGzEQxRMc6a9pdabBhbMMTJsjtmOv2YMYHj1HHK
# Dr7wTBTt2l0eRyCR0kZ8XGIMWhYowPa4EMpC7+4e5Nf/7LSJZWLLy9jXPpazsjkJ
# jEmDNlFfx2tZiq0Wz2Xj1pZSDLbcuIX4IHcLfMvagibfrCMX/h6+WuW42sWPRuBW
# wB6cHGlXs+K/gBBWxtD7sOTacO5hbHFsfaJOhSEIGoIpAgMBAAGjUzBRMB0GA1Ud
# DgQWBBT2S/C2++ECY+CSuN5KKql0umfbTDAfBgNVHSMEGDAWgBT2S/C2++ECY+CS
# uN5KKql0umfbTDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj
# H4DdwqrOHg7sVsqiwDsZfTharpUDCYeG5XhrJQlnA9eKwyofTb929W/fjOwBdDtg
# 9THT/omR0lA8/UyHtezMT6nMsCn4HG2mXvx6ghgvA3jrFTEY7R80dHkboLMTV3u4
# RYgC9S3BJPcbJYpM0cXzkp2T0F4FxWZlfqefuedHuX3zcCxpgVD56qQb2a131TX7
# O3UDJfVg8a65IFtehndqILgLVrf7w6+pbmDAlCg5RKrt2USEYyZXYdyTryJbdtn4
# BCLp0avYtSYVUGwgH0oUCpkjQRwMg1003TTz8SNnmE7mAXHYljyYejnjL8vBHfch
# 8tTDVXQn08BT9H3jZTnF
# -----END CERTIFICATE-----
app: app:
rest: rest:
uri: http://localhost/api uri: http://localhost/api
#kafka: #kafka:
# topic: test
# response-topic: test_response
# servers: localhost:9094 # servers: localhost:9094
# group-id: "test1234"
# input-topic: test_input
# output-topic: test_output
# output-response-topic: test_response
security: security:
admin-user: admin admin-user: admin
admin-password: "{noop}very-secret" admin-password: "{noop}very-secret"

View File

@@ -16,7 +16,5 @@ spring:
content: content:
enabled: true enabled: true
paths: /**/*.js,/**/*.css,/**/*.svg,/**/*.jpeg paths: /**/*.js,/**/*.css,/**/*.svg,/**/*.jpeg
app:
isGenomDeTestSubmission: true
server: server:
forward-headers-strategy: framework forward-headers-strategy: framework

View File

@@ -0,0 +1,7 @@
__ _ _ _ _
_ __ _____ __/ /_ | || | ___ ___| |_| | _ __ _ __ ___ ___ ___ ___ ___ ___ _ __
| '_ ` _ \ \ / / '_ \| || |_ / _ \_____ / _ \ __| |_____| '_ \| '__/ _ \ / __/ _ \/ __/ __|/ _ \| '__|
| | | | | \ V /| (_) |__ _| __/_____| __/ |_| |_____| |_) | | | (_) | (_| __/\__ \__ \ (_) | |
|_| |_| |_|\_/ \___/ |_| \___| \___|\__|_| | .__/|_| \___/ \___\___||___/___/\___/|_|
|_|
:: mv64e-etl-processor :: ${application.formatted-version}

View File

@@ -52,8 +52,10 @@
<td th:if="${request.status.value.contains('success')}" class="bg-green"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('success')}" class="bg-green"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value.contains('warning')}" class="bg-yellow"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('warning')}" class="bg-yellow"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value.contains('error')}" class="bg-red"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value.contains('error')}" class="bg-red"><small>[[ ${request.status} ]]</small></td>
<td th:if="${request.status.value == 'unknown'}" class="bg-gray"><small>[[ ${request.status} ]]</small></td> <td th:if="${request.status.value == 'unknown' and not request.isPendingUnknown()}" class="bg-gray"><small>[[ ${request.status} ]]</small></td>
<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 == '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:style="${request.type.value == 'delete'} ? 'color: red;'"><small>[[ ${request.type} ]]</small></td>
<td th:if="not ${request.report}">[[ ${request.uuid} ]]</td> <td th:if="not ${request.report}">[[ ${request.uuid} ]]</td>
<td th:if="${request.report}"> <td th:if="${request.report}">
@@ -100,4 +102,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -24,14 +24,15 @@ import java.time.Instant;
import java.util.Date; import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class}) @ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
@TestPropertySource(properties = { @TestPropertySource(properties = {
"app.consent.service=gics", "app.consent.service=gics",
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics" "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"
}) })
@RestClientTest @RestClientTest
class GicsConsentServiceTest { class GicsConsentServiceTest {
@@ -46,8 +47,8 @@ class GicsConsentServiceTest {
@BeforeEach @BeforeEach
void setUp( void setUp(
@Autowired AppFhirConfig appFhirConfig, @Autowired AppFhirConfig appFhirConfig,
@Autowired GIcsConfigProperties gIcsConfigProperties @Autowired GIcsConfigProperties gIcsConfigProperties
) { ) {
this.appFhirConfig = appFhirConfig; this.appFhirConfig = appFhirConfig;
this.gIcsConfigProperties = gIcsConfigProperties; this.gIcsConfigProperties = gIcsConfigProperties;
@@ -56,33 +57,33 @@ class GicsConsentServiceTest {
this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate); this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate);
this.gicsConsentService = new GicsConsentService( this.gicsConsentService = new GicsConsentService(
this.gIcsConfigProperties, this.gIcsConfigProperties,
RetryTemplate.builder().maxAttempts(1).build(), RetryTemplate.builder().maxAttempts(1).build(),
restTemplate, restTemplate,
this.appFhirConfig this.appFhirConfig
); );
} }
@Test @Test
void shouldReturnTtpBroadConsentStatus() { void shouldReturnTtpBroadConsentStatus() {
final Parameters consentedResponse = new Parameters() final Parameters consentedResponse = new Parameters()
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("consented") .setName("consented")
.setValue(new BooleanType().setValue(true)) .setValue(new BooleanType().setValue(true))
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo( requestTo(
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
)
.andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse),
MediaType.APPLICATION_JSON
) )
); .andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse),
MediaType.APPLICATION_JSON
)
);
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
@@ -91,22 +92,22 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnRevokedConsent() { void shouldReturnRevokedConsent() {
final Parameters revokedResponse = new Parameters() final Parameters revokedResponse = new Parameters()
.addParameter( .addParameter(
new ParametersParameterComponent() new ParametersParameterComponent()
.setName("consented") .setName("consented")
.setValue(new BooleanType().setValue(false)) .setValue(new BooleanType().setValue(false))
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo( requestTo(
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
) )
.andRespond( .andRespond(
withSuccess( withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse), appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse),
MediaType.APPLICATION_JSON) MediaType.APPLICATION_JSON)
); );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED); assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED);
@@ -116,23 +117,23 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnInvalidParameterResponse() { void shouldReturnInvalidParameterResponse() {
final OperationOutcome responseWithErrorOutcome = new OperationOutcome() final OperationOutcome responseWithErrorOutcome = new OperationOutcome()
.addIssue( .addIssue(
new OperationOutcomeIssueComponent() new OperationOutcomeIssueComponent()
.setSeverity(IssueSeverity.ERROR) .setSeverity(IssueSeverity.ERROR)
.setCode(IssueType.PROCESSING) .setCode(IssueType.PROCESSING)
.setDiagnostics("Invalid policy parameter...") .setDiagnostics("Invalid policy parameter...")
); );
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
)
.andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
MediaType.APPLICATION_JSON
) )
); .andRespond(
withSuccess(
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
MediaType.APPLICATION_JSON
)
);
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@@ -141,12 +142,12 @@ class GicsConsentServiceTest {
@Test @Test
void shouldReturnRequestError() { void shouldReturnRequestError() {
mockRestServiceServer mockRestServiceServer
.expect( .expect(
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
) )
.andRespond( .andRespond(
withServerError() withServerError()
); );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456"); var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK); assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@@ -156,26 +157,29 @@ class GicsConsentServiceTest {
void buildRequestParameterCurrentPolicyStatesForPersonTest() { void buildRequestParameterCurrentPolicyStatesForPersonTest() {
String pid = "12345678"; String pid = "12345678";
var result = gicsConsentService var result = gicsConsentService
.buildRequestParameterCurrentPolicyStatesForPerson( .buildRequestParameterCurrentPolicyStatesForPerson(
pid, pid,
Date.from(Instant.now()), Date.from(Instant.now()),
ConsentDomain.MODELLVORHABEN_64E ConsentDomain.MODELLVORHABEN_64E
); );
assertThat(result.getParameter()) assertThat(result.getParameter())
.as("should contain 3 parameter resources") .as("should contain 3 parameter resources")
.hasSize(3); .hasSize(3);
assertThat(((StringType) result.getParameter("domain").getValue()).getValue()) assertThat(((StringType) result.getParameter("domain").getValue()).getValue())
.isEqualTo( .isEqualTo(
gIcsConfigProperties.getGenomDeConsentDomainName() gIcsConfigProperties.getGenomDeConsentDomainName()
); );
assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue()) assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue())
.isEqualTo( .isEqualTo(
pid pid
); );
} }
@Test
public void convertGicsResultToMiiBroadConsent() {
fail("todo: implement Test gicsConsentService.convertGicsResultToMiiBroadConsent");
}
} }

View File

@@ -244,7 +244,14 @@ class KafkaInputListenerTest {
} }
@Test @Test
fun shouldNotProcessDnpmV2Request() { fun shouldProcessDnpmV2Request() {
whenever(consentEvaluator.check(any())).thenReturn(
ConsentEvaluation(
TtpConsentStatus.BROAD_CONSENT_GIVEN,
false
)
)
val mtbFile = Mtb.builder() val mtbFile = Mtb.builder()
.patient(Patient.builder().id("DUMMY_12345678").build()) .patient(Patient.builder().id("DUMMY_12345678").build())
.metadata( .metadata(
@@ -285,7 +292,7 @@ class KafkaInputListenerTest {
Optional.empty() Optional.empty()
) )
) )
verify(requestProcessor, times(0)).processDeletion( verify(requestProcessor, times(1)).processDeletion(
anyValueClass(), anyValueClass(), eq( anyValueClass(), anyValueClass(), eq(
TtpConsentStatus.UNKNOWN_CHECK_FILE TtpConsentStatus.UNKNOWN_CHECK_FILE
) )

View File

@@ -163,6 +163,8 @@ class KafkaMtbFileSenderTest {
assertThat(captor.firstValue.key()).isEqualTo("{\"pid\": \"PID\"}") assertThat(captor.firstValue.key()).isEqualTo("{\"pid\": \"PID\"}")
assertThat(captor.firstValue.headers().headers("contentType")).isNotNull assertThat(captor.firstValue.headers().headers("contentType")).isNotNull
assertThat(captor.firstValue.headers().headers("contentType")?.firstOrNull()?.value()).isEqualTo(CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray()) assertThat(captor.firstValue.headers().headers("contentType")?.firstOrNull()?.value()).isEqualTo(CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE.toByteArray())
assertThat(captor.firstValue.headers().headers("requestId")).isNotNull
assertThat(captor.firstValue.headers().headers("requestId")?.firstOrNull()?.value()).isEqualTo(TEST_REQUEST_ID.value.toByteArray())
assertThat(captor.firstValue.value()).isNotNull assertThat(captor.firstValue.value()).isNotNull
assertThat(captor.firstValue.value()).isEqualTo(objectMapper.writeValueAsString(dnmpV2kafkaRecordData(TEST_REQUEST_ID))) assertThat(captor.firstValue.value()).isEqualTo(objectMapper.writeValueAsString(dnmpV2kafkaRecordData(TEST_REQUEST_ID)))
} }

View File

@@ -80,7 +80,7 @@ class ConsentProcessorTest {
val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb) val checkResult = consentProcessor.consentGatedCheckAndTryEmbedding(inputMtb)
assertThat(checkResult).isTrue assertThat(checkResult).isTrue
assertThat(inputMtb.metadata.researchConsents).hasSize(26) assertThat(inputMtb.metadata.researchConsents).isNotEmpty
} }
companion object { companion object {