summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt12
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/config/AppConfigurationTest.kt26
-rw-r--r--src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt30
-rw-r--r--src/main/java/dev/dnpm/etl/processor/consent/ConsentDomain.java6
-rw-r--r--src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java292
-rw-r--r--src/main/java/dev/dnpm/etl/processor/consent/IConsentService.java (renamed from src/main/java/dev/dnpm/etl/processor/consent/IGetConsent.java)2
-rw-r--r--src/main/java/dev/dnpm/etl/processor/consent/MtbFileConsentService.java (renamed from src/main/java/dev/dnpm/etl/processor/consent/ConsentByMtbFile.java)6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt4
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt18
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt8
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt66
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt6
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt16
-rw-r--r--src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt22
-rw-r--r--src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java192
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt287
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/helpers.kt13
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt63
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt150
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt4
-rw-r--r--src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt9
21 files changed, 940 insertions, 292 deletions
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
index 130fea7..1206c99 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/EtlProcessorApplicationTests.kt
@@ -102,6 +102,18 @@ class EtlProcessorApplicationTests : AbstractTestcontainerTest() {
.id("TEST_12345678")
.build()
)
+ .metadata(
+ MvhMetadata
+ .builder()
+ .modelProjectConsent(
+ ModelProjectConsent
+ .builder()
+ .provisions(
+ listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ ).build()
+ )
+ .build()
+ )
.diagnoses(
listOf(
MtbDiagnosis.builder()
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/AppConfigurationTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/AppConfigurationTest.kt
index 9db509c..66b62c8 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/AppConfigurationTest.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/AppConfigurationTest.kt
@@ -20,8 +20,9 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper
-import dev.dnpm.etl.processor.consent.ConsentByMtbFile
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.GicsConsentService
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.input.KafkaInputListener
import dev.dnpm.etl.processor.monitoring.RequestRepository
import dev.dnpm.etl.processor.output.KafkaMtbFileSender
@@ -53,7 +54,8 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean
AppSecurityConfiguration::class,
KafkaAutoConfiguration::class,
AppKafkaConfiguration::class,
- AppRestConfiguration::class
+ AppRestConfiguration::class,
+ ConsentEvaluator::class
]
)
@MockitoBean(types = [ObjectMapper::class])
@@ -281,7 +283,8 @@ class AppConfigurationTest {
@Nested
@TestPropertySource(
properties = [
- "app.consent.service=GICS"
+ "app.consent.service=GICS",
+ "app.consent.gics.uri=http://localhost:9000",
]
)
inner class AppConfigurationConsentGicsTest(private val context: ApplicationContext) {
@@ -294,26 +297,11 @@ class AppConfigurationTest {
}
@Nested
- @TestPropertySource(
- properties = [
- "app.consent.gics.enabled=true"
- ]
- )
- inner class AppConfigurationConsentGicsEnabledTest(private val context: ApplicationContext) {
-
- @Test
- fun shouldUseConfiguredGenerator() {
- assertThat(context.getBean(GicsConsentService::class.java)).isNotNull
- }
-
- }
-
- @Nested
inner class AppConfigurationConsentBuildinTest(private val context: ApplicationContext) {
@Test
fun shouldUseConfiguredGenerator() {
- assertThat(context.getBean(ConsentByMtbFile::class.java)).isNotNull
+ assertThat(context.getBean(MtbFileConsentService::class.java)).isNotNull
}
}
diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
index 78bdc8f..5f2d7ca 100644
--- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
+++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
@@ -20,11 +20,11 @@
package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper
-import dev.dnpm.etl.processor.anyValueClass
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
-import dev.dnpm.etl.processor.consent.ConsentByMtbFile
+import dev.dnpm.etl.processor.consent.ConsentEvaluation
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.consent.TtpConsentStatus
-import dev.dnpm.etl.processor.consent.IGetConsent
import dev.dnpm.etl.processor.security.TokenRepository
import dev.dnpm.etl.processor.security.UserRoleRepository
import dev.dnpm.etl.processor.services.RequestProcessor
@@ -57,32 +57,37 @@ import java.util.*
classes = [
MtbFileRestController::class,
AppSecurityConfiguration::class,
- ConsentByMtbFile::class, IGetConsent::class
+ MtbFileConsentService::class
]
)
-@MockitoBean(types = [TokenRepository::class, RequestProcessor::class])
+@MockitoBean(types = [TokenRepository::class, RequestProcessor::class, ConsentEvaluator::class])
@TestPropertySource(
properties = [
"app.pseudonymize.generator=BUILDIN",
"app.security.admin-user=admin",
"app.security.admin-password={noop}very-secret",
- "app.security.enable-tokens=true",
- "app.consent.gics.enabled=false"
+ "app.security.enable-tokens=true"
]
)
class MtbFileRestControllerTest {
- private lateinit var mockMvc: MockMvc
-
- private lateinit var requestProcessor: RequestProcessor
+ lateinit var mockMvc: MockMvc
+ lateinit var requestProcessor: RequestProcessor
+ lateinit var consentEvaluator: ConsentEvaluator
@BeforeEach
fun setup(
@Autowired mockMvc: MockMvc,
- @Autowired requestProcessor: RequestProcessor
+ @Autowired requestProcessor: RequestProcessor,
+ @Autowired consentEvaluator: ConsentEvaluator
) {
this.mockMvc = mockMvc
this.requestProcessor = requestProcessor
+ this.consentEvaluator = consentEvaluator
+
+ doAnswer {
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true)
+ }.whenever(consentEvaluator).check(any())
}
@Test
@@ -167,8 +172,7 @@ class MtbFileRestControllerTest {
"app.security.admin-user=admin",
"app.security.admin-password={noop}very-secret",
"app.security.enable-tokens=true",
- "app.security.enable-oidc=true",
- "app.consent.gics.enabled=false"
+ "app.security.enable-oidc=true"
]
)
inner class WithOidcEnabled {
diff --git a/src/main/java/dev/dnpm/etl/processor/consent/ConsentDomain.java b/src/main/java/dev/dnpm/etl/processor/consent/ConsentDomain.java
index 6d0b160..51bfd50 100644
--- a/src/main/java/dev/dnpm/etl/processor/consent/ConsentDomain.java
+++ b/src/main/java/dev/dnpm/etl/processor/consent/ConsentDomain.java
@@ -4,10 +4,10 @@ public enum ConsentDomain {
/**
* MII Broad consent
*/
- BroadConsent,
+ BROAD_CONSENT,
/**
- * GenomDe Modelvohaben §64e
+ * GenomDe Modellvorhaben §64e
*/
- Modelvorhaben64e
+ MODELLVORHABEN_64E
}
diff --git a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java
index 6f3c987..95e8e8f 100644
--- a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java
+++ b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java
@@ -4,17 +4,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import dev.dnpm.etl.processor.config.AppFhirConfig;
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
-import java.util.Date;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.r4.model.BooleanType;
-import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.Coding;
-import org.hl7.fhir.r4.model.DateType;
-import org.hl7.fhir.r4.model.Identifier;
-import org.hl7.fhir.r4.model.OperationOutcome;
-import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
-import org.hl7.fhir.r4.model.StringType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,112 +14,152 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
import org.springframework.retry.TerminatedRetryException;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
+import java.net.URI;
+import java.util.Date;
-public class GicsConsentService implements IGetConsent {
+/**
+ * Service to request Consent from remote gICS installation
+ *
+ * @since 0.11
+ */
+public class GicsConsentService implements IConsentService {
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
public static final String IS_CONSENTED_ENDPOINT = "/$isConsented";
public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson";
+
private final RetryTemplate retryTemplate;
private final RestTemplate restTemplate;
private final FhirContext fhirContext;
- private final HttpHeaders httpHeader;
private final GIcsConfigProperties gIcsConfigProperties;
- private String url;
-
- public GicsConsentService(GIcsConfigProperties gIcsConfigProperties,
- RetryTemplate retryTemplate, RestTemplate restTemplate, AppFhirConfig appFhirConfig) {
+ public GicsConsentService(
+ GIcsConfigProperties gIcsConfigProperties,
+ RetryTemplate retryTemplate,
+ RestTemplate restTemplate,
+ AppFhirConfig appFhirConfig
+ ) {
this.retryTemplate = retryTemplate;
this.restTemplate = restTemplate;
this.fhirContext = appFhirConfig.fhirContext();
- httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
- gIcsConfigProperties.getPassword());
this.gIcsConfigProperties = gIcsConfigProperties;
log.info("GicsConsentService initialized...");
}
- public String getGicsUri(String endpoint) {
- if (url == null) {
- final String gIcsBaseUri = gIcsConfigProperties.getUri();
- if (StringUtils.isBlank(gIcsBaseUri)) {
- throw new IllegalArgumentException(
- "gICS base URL is empty - should call gICS with false configuration.");
- }
- url = UriComponentsBuilder.fromUriString(gIcsBaseUri).path(endpoint)
- .toUriString();
- }
- return url;
- }
-
- @NotNull
- private static HttpHeaders buildHeader(String gPasUserName, String gPasPassword) {
- var headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_XML);
-
- if (StringUtils.isBlank(gPasUserName) || StringUtils.isBlank(gPasPassword)) {
- return headers;
- }
-
- headers.setBasicAuth(gPasUserName, gPasPassword);
- return headers;
- }
-
- protected static Parameters getIsConsentedRequestParam(GIcsConfigProperties configProperties,
- String personIdentifierValue) {
+ protected Parameters getFhirRequestParameters(
+ String personIdentifierValue
+ ) {
var result = new Parameters();
- result.addParameter(new ParametersParameterComponent().setName("personIdentifier").setValue(
- new Identifier().setValue(personIdentifierValue)
- .setSystem(configProperties.getPersonIdentifierSystem())));
- result.addParameter(new ParametersParameterComponent().setName("domain")
- .setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
- result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
- new Coding().setCode(configProperties.getBroadConsentPolicyCode())
- .setSystem(configProperties.getBroadConsentPolicySystem())));
+ result.addParameter(
+ new ParametersParameterComponent()
+ .setName("personIdentifier")
+ .setValue(
+ new Identifier()
+ .setValue(personIdentifierValue)
+ .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
+ )
+ );
+ result.addParameter(
+ new ParametersParameterComponent()
+ .setName("domain")
+ .setValue(
+ new StringType()
+ .setValue(this.gIcsConfigProperties.getBroadConsentDomainName())
+ )
+ );
+ result.addParameter(
+ new ParametersParameterComponent()
+ .setName("policy")
+ .setValue(
+ new Coding()
+ .setCode(this.gIcsConfigProperties.getBroadConsentPolicyCode())
+ .setSystem(this.gIcsConfigProperties.getBroadConsentPolicySystem())
+ )
+ );
/*
* is mandatory parameter, but we ignore it via additional configuration parameter
* 'ignoreVersionNumber'.
*/
- result.addParameter(new ParametersParameterComponent().setName("version")
- .setValue(new StringType().setValue("1.1")));
+ result.addParameter(
+ new ParametersParameterComponent()
+ .setName("version")
+ .setValue(new StringType().setValue("1.1")
+ )
+ );
/* add config parameter with:
* ignoreVersionNumber -> true ->> Reason is we cannot know which policy version each patient
* has possibly signed or not, therefore we are happy with any version found.
* unknownStateIsConsideredAsDecline -> true
*/
- var config = new ParametersParameterComponent().setName("config").addPart(
- new ParametersParameterComponent().setName("ignoreVersionNumber")
- .setValue(new BooleanType().setValue(true))).addPart(
- new ParametersParameterComponent().setName("unknownStateIsConsideredAsDecline")
- .setValue(new BooleanType().setValue(false)));
+ var config = new ParametersParameterComponent()
+ .setName("config")
+ .addPart(
+ new ParametersParameterComponent()
+ .setName("ignoreVersionNumber")
+ .setValue(new BooleanType().setValue(true))
+ )
+ .addPart(
+ new ParametersParameterComponent()
+ .setName("unknownStateIsConsideredAsDecline")
+ .setValue(new BooleanType().setValue(false))
+ );
+
result.addParameter(config);
return result;
}
+ private URI endpointUri(String endpoint) {
+ assert this.gIcsConfigProperties.getUri() != null;
+ return UriComponentsBuilder.fromUriString(this.gIcsConfigProperties.getUri()).path(endpoint).build().toUri();
+ }
+
+ private HttpHeaders headersWithHttpBasicAuth() {
+ assert this.gIcsConfigProperties.getUri() != null;
+
+ var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_XML);
+
+ if (
+ StringUtils.isBlank(this.gIcsConfigProperties.getUsername())
+ || StringUtils.isBlank(this.gIcsConfigProperties.getPassword())
+ ) {
+ return headers;
+ }
+
+ headers.setBasicAuth(this.gIcsConfigProperties.getUsername(), this.gIcsConfigProperties.getPassword());
+ return headers;
+ }
+
protected String callGicsApi(Parameters parameter, String endpoint) {
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
-
- HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
- ResponseEntity<String> responseEntity;
+ HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth());
try {
- var url = getGicsUri(endpoint);
-
- responseEntity = retryTemplate.execute(
- ctx -> restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class));
+ var responseEntity = retryTemplate.execute(
+ ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class)
+ );
+
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
+ return responseEntity.getBody();
+ } else {
+ var msg = String.format(
+ "Trusted party system reached but request failed! code: '%s' response: '%s'",
+ responseEntity.getStatusCode(), responseEntity.getBody());
+ log.error(msg);
+ return null;
+ }
} catch (RestClientException e) {
var msg = String.format("Get consents status request failed reason: '%s",
- e.getMessage());
+ e.getMessage());
log.error(msg);
return null;
@@ -137,39 +169,32 @@ public class GicsConsentService implements IGetConsent {
terminatedRetryException.getMessage());
log.error(msg);
return null;
-
- }
- if (responseEntity.getStatusCode().is2xxSuccessful()) {
- return responseEntity.getBody();
- } else {
- var msg = String.format(
- "Trusted party system reached but request failed! code: '%s' response: '%s'",
- responseEntity.getStatusCode(), responseEntity.getBody());
- log.error(msg);
- return null;
}
}
@Override
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
- var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
- personIdentifierValue);
-
- var consentStatusResponse = callGicsApi(parameter,
- GicsConsentService.IS_CONSENTED_ENDPOINT);
+ var consentStatusResponse = callGicsApi(
+ getFhirRequestParameters(personIdentifierValue),
+ GicsConsentService.IS_CONSENTED_ENDPOINT
+ );
return evaluateConsentResponse(consentStatusResponse);
}
- protected Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
- ConsentDomain targetConsentDomain, Date requestDate) {
-
- String consentDomain = getConsentDomain(targetConsentDomain);
+ protected Bundle currentConsentForPersonAndTemplate(
+ String personIdentifierValue,
+ ConsentDomain consentDomain,
+ Date requestDate
+ ) {
- var requestParameter = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
- gIcsConfigProperties, personIdentifierValue, requestDate, consentDomain);
+ var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson(
+ personIdentifierValue,
+ requestDate,
+ consentDomain
+ );
var consentDataSerialized = callGicsApi(requestParameter,
- GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
+ GicsConsentService.IS_POLICY_STATES_FOR_PERSON_ENDPOINT);
if (consentDataSerialized == null) {
// error occurred - should not process further!
@@ -177,15 +202,15 @@ public class GicsConsentService implements IGetConsent {
"consent data request failed - stopping processing! - try again or fix other problems first.");
}
var iBaseResource = fhirContext.newJsonParser()
- .parseResource(consentDataSerialized);
+ .parseResource(consentDataSerialized);
if (iBaseResource instanceof OperationOutcome) {
// log error - very likely a configuration error
String errorMessage =
"Consent request failed! Check outcome:\n " + consentDataSerialized;
log.error(errorMessage);
throw new IllegalStateException(errorMessage);
- } else if (iBaseResource instanceof Bundle) {
- return (Bundle) iBaseResource;
+ } else if (iBaseResource instanceof Bundle bundle) {
+ return bundle;
} else {
String errorMessage = "Consent request failed! Unexpected response received! -> "
+ consentDataSerialized;
@@ -195,40 +220,52 @@ public class GicsConsentService implements IGetConsent {
}
@NotNull
- private String getConsentDomain(ConsentDomain targetConsentDomain) {
- String consentDomain;
- switch (targetConsentDomain) {
- case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
- case Modelvorhaben64e ->
- consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
- default -> throw new IllegalArgumentException(
- "target ConsentDomain is missing but must be provided!");
- }
- return consentDomain;
+ private String getConsentDomainName(ConsentDomain targetConsentDomain) {
+ return switch (targetConsentDomain) {
+ case BROAD_CONSENT -> gIcsConfigProperties.getBroadConsentDomainName();
+ case MODELLVORHABEN_64E -> gIcsConfigProperties.getGenomDeConsentDomainName();
+ };
}
- protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
- GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
- String targetDomain) {
+ protected Parameters buildRequestParameterCurrentPolicyStatesForPerson(
+ String personIdentifierValue,
+ Date requestDate,
+ ConsentDomain consentDomain
+ ) {
var requestParameter = new Parameters();
- requestParameter.addParameter(new ParametersParameterComponent().setName("personIdentifier")
- .setValue(new Identifier().setValue(personIdentifierValue)
- .setSystem(gIcsConfigProperties.getPersonIdentifierSystem())));
-
- requestParameter.addParameter(new ParametersParameterComponent().setName("domain")
- .setValue(new StringType().setValue(targetDomain)));
+ requestParameter.addParameter(
+ new ParametersParameterComponent()
+ .setName("personIdentifier")
+ .setValue(
+ new Identifier()
+ .setValue(personIdentifierValue)
+ .setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
+ )
+ );
+
+ requestParameter.addParameter(
+ new ParametersParameterComponent()
+ .setName("domain")
+ .setValue(new StringType().setValue(getConsentDomainName(consentDomain)))
+ );
Parameters nestedConfigParameters = new Parameters();
- nestedConfigParameters.addParameter(
- new ParametersParameterComponent().setName("idMatchingType").setValue(
- new Coding().setSystem(
- "https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
- .setCode("AT_LEAST_ONE"))).addParameter("ignoreVersionNumber", false)
+ nestedConfigParameters
+ .addParameter(
+ new ParametersParameterComponent()
+ .setName("idMatchingType")
+ .setValue(new Coding()
+ .setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
+ .setCode("AT_LEAST_ONE")
+ )
+ )
+ .addParameter("ignoreVersionNumber", false)
.addParameter("unknownStateIsConsideredAsDecline", false)
.addParameter("requestDate", new DateType().setValue(requestDate));
- requestParameter.addParameter(new ParametersParameterComponent().setName("config").addPart()
- .setResource(nestedConfigParameters));
+ requestParameter.addParameter(
+ new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters)
+ );
return requestParameter;
}
@@ -254,7 +291,7 @@ public class GicsConsentService implements IGetConsent {
}
} else if (response instanceof OperationOutcome outcome) {
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) {
@@ -265,17 +302,6 @@ public class GicsConsentService implements IGetConsent {
@Override
public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) {
- switch (consentDomain) {
- case BroadConsent -> {
- return currentConsentForPersonAndTemplate(patientId, ConsentDomain.BroadConsent,
- requestDate);
- }
- case Modelvorhaben64e -> {
- return currentConsentForPersonAndTemplate(patientId,
- ConsentDomain.Modelvorhaben64e, requestDate);
- }
- }
-
- return new Bundle();
+ return currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate);
}
}
diff --git a/src/main/java/dev/dnpm/etl/processor/consent/IGetConsent.java b/src/main/java/dev/dnpm/etl/processor/consent/IConsentService.java
index 3482b9a..ded3515 100644
--- a/src/main/java/dev/dnpm/etl/processor/consent/IGetConsent.java
+++ b/src/main/java/dev/dnpm/etl/processor/consent/IConsentService.java
@@ -3,7 +3,7 @@ package dev.dnpm.etl.processor.consent;
import java.util.Date;
import org.hl7.fhir.r4.model.Bundle;
-public interface IGetConsent {
+public interface IConsentService {
/**
* Get broad consent status for a patient identifier
diff --git a/src/main/java/dev/dnpm/etl/processor/consent/ConsentByMtbFile.java b/src/main/java/dev/dnpm/etl/processor/consent/MtbFileConsentService.java
index f7ce39e..24cb8f7 100644
--- a/src/main/java/dev/dnpm/etl/processor/consent/ConsentByMtbFile.java
+++ b/src/main/java/dev/dnpm/etl/processor/consent/MtbFileConsentService.java
@@ -5,11 +5,11 @@ import org.hl7.fhir.r4.model.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class ConsentByMtbFile implements IGetConsent {
+public class MtbFileConsentService implements IConsentService {
- private static final Logger log = LoggerFactory.getLogger(ConsentByMtbFile.class);
+ private static final Logger log = LoggerFactory.getLogger(MtbFileConsentService.class);
- public ConsentByMtbFile() {
+ public MtbFileConsentService() {
log.info("ConsentCheckFileBased initialized...");
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
index 1f3c650..5dea8ab 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt
@@ -73,8 +73,8 @@ data class GIcsConfigProperties(
*
*/
val uri: String?,
- val username: String?,
- val password: String?,
+ val username: String? = null,
+ val password: String? = null,
/**
* gICS specific system
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
index 8f90947..f32ecaa 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfiguration.kt
@@ -20,9 +20,9 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper
-import dev.dnpm.etl.processor.consent.ConsentByMtbFile
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.consent.GicsConsentService
-import dev.dnpm.etl.processor.consent.IGetConsent
+import dev.dnpm.etl.processor.consent.IConsentService
import dev.dnpm.etl.processor.monitoring.*
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
import dev.dnpm.etl.processor.pseudonym.Generator
@@ -218,7 +218,7 @@ class AppConfiguration {
retryTemplate: RetryTemplate,
restTemplate: RestTemplate,
appFhirConfig: AppFhirConfig
- ): IGetConsent {
+ ): IConsentService {
return GicsConsentService(
gIcsConfigProperties,
retryTemplate,
@@ -234,7 +234,7 @@ class AppConfiguration {
gIcsConfigProperties: GIcsConfigProperties,
getObjectMapper: ObjectMapper,
appFhirConfig: AppFhirConfig,
- gicsConsentService: IGetConsent
+ gicsConsentService: IConsentService
): ConsentProcessor {
return ConsentProcessor(
configProperties,
@@ -261,8 +261,8 @@ class AppConfiguration {
@Bean
@ConditionalOnMissingBean
- fun iGetConsentService(): IGetConsent {
- return ConsentByMtbFile()
+ fun iGetConsentService(): IConsentService {
+ return MtbFileConsentService()
}
}
@@ -271,13 +271,9 @@ class GicsEnabledCondition :
AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) {
@ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
+ @ConditionalOnProperty(name = ["app.consent.gics.uri"])
class OnGicsServiceSelected {
// Just for Condition
}
- @ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
- class OnGicsEnabled {
- // Just for Condition
- }
-
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
index de11cbb..6551713 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppKafkaConfiguration.kt
@@ -20,6 +20,7 @@
package dev.dnpm.etl.processor.config
import com.fasterxml.jackson.databind.ObjectMapper
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.input.KafkaInputListener
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
@@ -100,9 +101,10 @@ class AppKafkaConfiguration {
@ConditionalOnProperty(value = ["app.kafka.input-topic"])
fun kafkaInputListener(
requestProcessor: RequestProcessor,
- objectMapper: ObjectMapper
+ objectMapper: ObjectMapper,
+ consentEvaluator: ConsentEvaluator
): KafkaInputListener {
- return KafkaInputListener(requestProcessor, objectMapper)
+ return KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
}
@Bean
@@ -113,4 +115,4 @@ class AppKafkaConfiguration {
return KafkaConnectionCheckService(consumerFactory.createConsumer(), connectionCheckUpdateProducer)
}
-} \ No newline at end of file
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
new file mode 100644
index 0000000..195346d
--- /dev/null
+++ b/src/main/kotlin/dev/dnpm/etl/processor/consent/ConsentEvaluator.kt
@@ -0,0 +1,66 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * 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
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package dev.dnpm.etl.processor.consent
+
+import dev.pcvolkmer.mv64e.mtb.ConsentProvision
+import dev.pcvolkmer.mv64e.mtb.ModelProjectConsentPurpose
+import dev.pcvolkmer.mv64e.mtb.Mtb
+import org.springframework.stereotype.Service
+
+/**
+ * Evaluates consent using provided consent service and file based consent information
+ */
+@Service
+class ConsentEvaluator(
+ private val consentService: IConsentService
+) {
+ fun check(mtbFile: Mtb): ConsentEvaluation {
+ val ttpConsentStatus = consentService.getTtpBroadConsentStatus(mtbFile.patient.id)
+ val consentGiven = ttpConsentStatus == TtpConsentStatus.BROAD_CONSENT_GIVEN
+ || ttpConsentStatus == TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT
+ // Aktuell nur Modellvorhaben Consent im File
+ || ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE && mtbFile.metadata?.modelProjectConsent?.provisions?.any {
+ it.purpose == ModelProjectConsentPurpose.SEQUENCING
+ && it.type == ConsentProvision.PERMIT
+ } == true
+
+ return ConsentEvaluation(ttpConsentStatus, consentGiven)
+ }
+}
+
+data class ConsentEvaluation(private val ttpConsentStatus: TtpConsentStatus, private val consentGiven: Boolean) {
+ /**
+ * Checks if any required consent is present
+ */
+ fun hasConsent(): Boolean {
+ return consentGiven
+ }
+
+ /**
+ * Returns the consent status
+ */
+ fun getStatus(): TtpConsentStatus {
+ if (ttpConsentStatus == TtpConsentStatus.UNKNOWN_CHECK_FILE) {
+ // in case ttp check is disabled - we propagate rejected status anyway
+ return TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED
+ }
+ return ttpConsentStatus
+ }
+}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
index 47615be..4ac9f2d 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/KafkaInputListener.kt
@@ -23,9 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientId
import dev.dnpm.etl.processor.RequestId
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.ConsentProvision
import dev.pcvolkmer.mv64e.mtb.Mtb
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.slf4j.LoggerFactory
@@ -34,6 +34,7 @@ import org.springframework.kafka.listener.MessageListener
class KafkaInputListener(
private val requestProcessor: RequestProcessor,
+ private val consentEvaluator: ConsentEvaluator,
private val objectMapper: ObjectMapper
) : MessageListener<String, String> {
private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java)
@@ -70,8 +71,7 @@ class KafkaInputListener(
RequestId("")
}
- // TODO: Use MV Consent for now - needs to be replaced with proper consent evaluation
- if (mtbFile.metadata.modelProjectConsent.provisions.filter { it.type == ConsentProvision.PERMIT }.isNotEmpty()) {
+ if (consentEvaluator.check(mtbFile).hasConsent()) {
logger.debug("Accepted MTB File for processing")
if (requestId.isBlank()) {
requestProcessor.processMtbFile(mtbFile)
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
index d00ad25..e154536 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/input/MtbFileRestController.kt
@@ -21,7 +21,7 @@ package dev.dnpm.etl.processor.input
import dev.dnpm.etl.processor.CustomMediaType
import dev.dnpm.etl.processor.PatientId
-import dev.dnpm.etl.processor.consent.IGetConsent
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
import dev.pcvolkmer.mv64e.mtb.Mtb
@@ -34,9 +34,8 @@ import org.springframework.web.bind.annotation.*
@RequestMapping(path = ["mtbfile", "mtb"])
class MtbFileRestController(
private val requestProcessor: RequestProcessor,
- private val iGetConsent: IGetConsent
+ private val consentEvaluator: ConsentEvaluator
) {
-
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
@GetMapping
@@ -46,8 +45,15 @@ class MtbFileRestController(
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE])
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
- logger.debug("Accepted MTB File (DNPM V2) for processing")
- requestProcessor.processMtbFile(mtbFile)
+ val consentEvaluation = consentEvaluator.check(mtbFile)
+ if (consentEvaluation.hasConsent()) {
+ logger.debug("Accepted MTB File (DNPM V2) for processing")
+ requestProcessor.processMtbFile(mtbFile)
+ } else {
+ logger.debug("Accepted MTB File (DNPM V2) and process deletion")
+ val patientId = PatientId(mtbFile.patient.id)
+ requestProcessor.processDeletion(patientId, consentEvaluation.getStatus())
+ }
return ResponseEntity.accepted().build()
}
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
index 11aff57..6688087 100644
--- a/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
+++ b/src/main/kotlin/dev/dnpm/etl/processor/services/ConsentProcessor.kt
@@ -6,9 +6,9 @@ import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.config.GIcsConfigProperties
-import dev.dnpm.etl.processor.consent.ConsentByMtbFile
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.consent.ConsentDomain
-import dev.dnpm.etl.processor.consent.IGetConsent
+import dev.dnpm.etl.processor.consent.IConsentService
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
import dev.pcvolkmer.mv64e.mtb.*
import org.apache.commons.lang3.NotImplementedException
@@ -31,7 +31,7 @@ class ConsentProcessor(
private val gIcsConfigProperties: GIcsConfigProperties,
private val objectMapper: ObjectMapper,
private val fhirContext: FhirContext,
- private val consentService: IGetConsent
+ private val consentService: IConsentService
) {
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
@@ -49,7 +49,7 @@ class ConsentProcessor(
*
*/
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
- if (consentService is ConsentByMtbFile) {
+ if (consentService is MtbFileConsentService) {
// consent check is disabled
return true
}
@@ -70,7 +70,7 @@ class ConsentProcessor(
* broad consent
*/
val broadConsent = consentService.getConsent(
- personIdentifierValue, requestDate, ConsentDomain.BroadConsent
+ personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT
)
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
@@ -78,7 +78,7 @@ class ConsentProcessor(
if (!broadConsentHasBeenAsked) return false
val genomeDeConsent = consentService.getConsent(
- personIdentifierValue, requestDate, ConsentDomain.Modelvorhaben64e
+ personIdentifierValue, requestDate, ConsentDomain.MODELLVORHABEN_64E
)
addGenomeDbProvisions(mtbFile, genomeDeConsent)
@@ -88,11 +88,11 @@ class ConsentProcessor(
embedBroadConsentResources(mtbFile, broadConsent)
val broadConsentStatus = getProvisionTypeByPolicyCode(
- broadConsent, requestDate, ConsentDomain.BroadConsent
+ broadConsent, requestDate, ConsentDomain.BROAD_CONSENT
)
val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
- genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
+ genomeDeConsent, requestDate, ConsentDomain.MODELLVORHABEN_64E
)
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
@@ -204,10 +204,10 @@ class ConsentProcessor(
): Consent.ConsentProvisionType {
val code: String?
val system: String?
- if (ConsentDomain.BroadConsent == consentDomain) {
+ if (ConsentDomain.BROAD_CONSENT == consentDomain) {
code = gIcsConfigProperties.broadConsentPolicyCode
system = gIcsConfigProperties.broadConsentPolicySystem
- } else if (ConsentDomain.Modelvorhaben64e == consentDomain) {
+ } else if (ConsentDomain.MODELLVORHABEN_64E == consentDomain) {
code = gIcsConfigProperties.genomeDePolicyCode
system = gIcsConfigProperties.genomeDePolicySystem
} else {
@@ -279,4 +279,4 @@ class ConsentProcessor(
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
}
-} \ No newline at end of file
+}
diff --git a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java
index d26eca2..6fa8f08 100644
--- a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java
+++ b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java
@@ -1,85 +1,112 @@
package dev.dnpm.etl.processor.consent;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
-import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dnpm.etl.processor.config.AppConfiguration;
import dev.dnpm.etl.processor.config.AppFhirConfig;
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
-import java.time.Instant;
-import java.util.Date;
-import org.hl7.fhir.r4.model.BooleanType;
-import org.hl7.fhir.r4.model.Identifier;
-import org.hl7.fhir.r4.model.OperationOutcome;
+import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
-import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
-import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.MediaType;
+import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.RestTemplate;
-@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
-@TestPropertySource(properties = {"app.consent.gics.enabled=true",
- "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"})
-@RestClientTest
-public class GicsConsentServiceTest {
+import java.time.Instant;
+import java.util.Date;
- public static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
- @Autowired
- MockRestServiceServer mockRestServiceServer;
+import static org.assertj.core.api.Assertions.assertThat;
+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.withSuccess;
- @Autowired
- GicsConsentService gicsConsentService;
+@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
+@TestPropertySource(properties = {
+ "app.consent.service=gics",
+ "app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"
+})
+@RestClientTest
+class GicsConsentServiceTest {
- @Autowired
- AppConfiguration appConfiguration;
+ static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
- @Autowired
+ MockRestServiceServer mockRestServiceServer;
AppFhirConfig appFhirConfig;
-
- @Autowired
GIcsConfigProperties gIcsConfigProperties;
+ GicsConsentService gicsConsentService;
+
@BeforeEach
- public void setUp() {
- mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
+ void setUp(
+ @Autowired AppFhirConfig appFhirConfig,
+ @Autowired GIcsConfigProperties gIcsConfigProperties
+ ) {
+ this.appFhirConfig = appFhirConfig;
+ this.gIcsConfigProperties = gIcsConfigProperties;
+
+ var restTemplate = new RestTemplate();
+
+ this.mockRestServiceServer = MockRestServiceServer.createServer(restTemplate);
+ this.gicsConsentService = new GicsConsentService(
+ this.gIcsConfigProperties,
+ RetryTemplate.builder().maxAttempts(1).build(),
+ restTemplate,
+ this.appFhirConfig
+ );
}
@Test
- void getTtpBroadConsentStatus() {
- final Parameters responseConsented = new Parameters().addParameter(
- new ParametersParameterComponent().setName("consented")
- .setValue(new BooleanType().setValue(true)));
-
- mockRestServiceServer.expect(requestTo(
- "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
- .andRespond(withSuccess(appFhirConfig.fhirContext().newJsonParser()
- .encodeResourceToString(responseConsented), MediaType.APPLICATION_JSON));
+ void shouldReturnTtpBroadConsentStatus() {
+ final Parameters consentedResponse = new Parameters()
+ .addParameter(
+ new ParametersParameterComponent()
+ .setName("consented")
+ .setValue(new BooleanType().setValue(true))
+ );
+
+ mockRestServiceServer
+ .expect(
+ requestTo(
+ "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
+ )
+ .andRespond(
+ withSuccess(
+ appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(consentedResponse),
+ MediaType.APPLICATION_JSON
+ )
+ );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
}
@Test
- void consentRevoced() {
- final Parameters responseRevoced = new Parameters().addParameter(
- new ParametersParameterComponent().setName("consented")
- .setValue(new BooleanType().setValue(false)));
-
- mockRestServiceServer.expect(requestTo(
- "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
- .andRespond(withSuccess(
- appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseRevoced),
- MediaType.APPLICATION_JSON));
+ void shouldReturnRevokedConsent() {
+ final Parameters revokedResponse = new Parameters()
+ .addParameter(
+ new ParametersParameterComponent()
+ .setName("consented")
+ .setValue(new BooleanType().setValue(false))
+ );
+
+ mockRestServiceServer
+ .expect(
+ requestTo(
+ "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
+ )
+ .andRespond(
+ withSuccess(
+ appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse),
+ MediaType.APPLICATION_JSON)
+ );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED);
@@ -87,15 +114,39 @@ public class GicsConsentServiceTest {
@Test
- void gicsParameterInvalid() {
- final OperationOutcome responseErrorOutcome = new OperationOutcome().addIssue(
- new OperationOutcomeIssueComponent().setSeverity(IssueSeverity.ERROR)
- .setCode(IssueType.PROCESSING).setDiagnostics("Invalid policy parameter..."));
+ void shouldReturnInvalidParameterResponse() {
+ final OperationOutcome responseWithErrorOutcome = new OperationOutcome()
+ .addIssue(
+ new OperationOutcomeIssueComponent()
+ .setSeverity(IssueSeverity.ERROR)
+ .setCode(IssueType.PROCESSING)
+ .setDiagnostics("Invalid policy parameter...")
+ );
+
+ mockRestServiceServer
+ .expect(
+ requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
+ )
+ .andRespond(
+ withSuccess(
+ appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
+ MediaType.APPLICATION_JSON
+ )
+ );
- mockRestServiceServer.expect(
- requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
- withSuccess(appFhirConfig.fhirContext().newJsonParser()
- .encodeResourceToString(responseErrorOutcome), MediaType.APPLICATION_JSON));
+ var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
+ assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
+ }
+
+ @Test
+ void shouldReturnRequestError() {
+ mockRestServiceServer
+ .expect(
+ requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
+ )
+ .andRespond(
+ withServerError()
+ );
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
@@ -103,20 +154,27 @@ public class GicsConsentServiceTest {
@Test
void buildRequestParameterCurrentPolicyStatesForPersonTest() {
-
String pid = "12345678";
- var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
- gIcsConfigProperties, pid, Date.from(Instant.now()),
- gIcsConfigProperties.getGenomDeConsentDomainName());
-
- assertThat(result.getParameter().size()).as("should contain 3 parameter resources")
- .isEqualTo(3);
-
- assertThat(((StringType) result.getParameter("domain").getValue()).getValue()).isEqualTo(
- gIcsConfigProperties.getGenomDeConsentDomainName());
- assertThat(
- ((Identifier) result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(
- pid);
+ var result = gicsConsentService
+ .buildRequestParameterCurrentPolicyStatesForPerson(
+ pid,
+ Date.from(Instant.now()),
+ ConsentDomain.MODELLVORHABEN_64E
+ );
+
+ assertThat(result.getParameter())
+ .as("should contain 3 parameter resources")
+ .hasSize(3);
+
+ assertThat(((StringType) result.getParameter("domain").getValue()).getValue())
+ .isEqualTo(
+ gIcsConfigProperties.getGenomDeConsentDomainName()
+ );
+
+ assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue())
+ .isEqualTo(
+ pid
+ );
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt
new file mode 100644
index 0000000..adbec2f
--- /dev/null
+++ b/src/test/kotlin/dev/dnpm/etl/processor/consent/Dnpm21BasedConsentEvaluatorTest.kt
@@ -0,0 +1,287 @@
+/*
+ * This file is part of ETL-Processor
+ *
+ * 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
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package dev.dnpm.etl.processor.consent
+
+import dev.dnpm.etl.processor.ArgProvider
+import dev.pcvolkmer.mv64e.mtb.*
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsSource
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.junit.jupiter.MockitoExtension
+import org.mockito.kotlin.whenever
+import java.time.Instant
+import java.util.*
+
+@ExtendWith(MockitoExtension::class)
+class Dnpm21BasedConsentEvaluatorTest {
+
+ @Nested
+ inner class WithGicsConsentEnabled {
+
+ lateinit var consentService: GicsConsentService
+ lateinit var consentEvaluator: ConsentEvaluator
+
+ @BeforeEach
+ fun setUp(
+ @Mock consentService: GicsConsentService
+ ) {
+ this.consentService = consentService
+ this.consentEvaluator = ConsentEvaluator(consentService)
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(WithGicsMtbFileProvider::class)
+ fun test(
+ mtbFile: Mtb,
+ ttpConsentStatus: TtpConsentStatus,
+ expectedConsentEvaluation: ConsentEvaluation
+ ) {
+ whenever(consentService.getTtpBroadConsentStatus(anyString())).thenReturn(
+ ttpConsentStatus
+ )
+ assertThat(consentEvaluator.check(mtbFile)).isEqualTo(expectedConsentEvaluation)
+ }
+ }
+
+ @Nested
+ inner class WithFileConsentOnly {
+
+ lateinit var consentService: MtbFileConsentService
+ lateinit var consentEvaluator: ConsentEvaluator
+
+ @BeforeEach
+ fun setUp() {
+ this.consentService = MtbFileConsentService()
+ this.consentEvaluator = ConsentEvaluator(consentService)
+ }
+
+ @ParameterizedTest
+ @ArgumentsSource(MtbFileProvider::class)
+ fun test(mtbFile: Mtb, expectedConsentEvaluation: ConsentEvaluation) {
+ assertThat(consentEvaluator.check(mtbFile)).isEqualTo(expectedConsentEvaluation)
+ }
+ }
+
+ // Util classes
+
+ class WithGicsMtbFileProvider : ArgProvider(
+ // Has file ModelProjectConsent and broad consent => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true)
+ ),
+ // Has file ModelProjectConsent and broad consent missing => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.BROAD_CONSENT_MISSING,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING, false)
+ ),
+ // Has file ModelProjectConsent and broad consent missing or rejected => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, false)
+ ),
+ // Has file ModelProjectConsent and MV consent => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, true)
+ ),
+ // Has file ModelProjectConsent and MV consent rejected => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED, false)
+ ),
+ // Has file ModelProjectConsent and MV consent missing => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_MISSING, false)
+ ),
+ // Has file ModelProjectConsent and no broad consent result => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.UNKNOWN_CHECK_FILE,
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, true)
+ ),
+ // Has file ModelProjectConsent and failed to ask => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ TtpConsentStatus.FAILED_TO_ASK,
+ ConsentEvaluation(TtpConsentStatus.FAILED_TO_ASK, false)
+ ),
+ // File ModelProjectConsent rejected and broad consent => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true)
+ ),
+ // File ModelProjectConsent rejected and broad consent missing => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.BROAD_CONSENT_MISSING,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING, false)
+ ),
+ // File ModelProjectConsent rejected and broad consent missing or rejected => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
+ ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED, false)
+ ),
+ // File ModelProjectConsent rejected and MV consent => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_SEQUENCING_PERMIT, true)
+ ),
+ // File ModelProjectConsent rejected and MV consent rejected => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_SEQUENCING_REJECTED, false)
+ ),
+ // File ModelProjectConsent rejected and MV consent missing => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.GENOM_DE_CONSENT_MISSING,
+ ConsentEvaluation(TtpConsentStatus.GENOM_DE_CONSENT_MISSING, false)
+ ),
+ // File ModelProjectConsent rejected and no broad consent result => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.UNKNOWN_CHECK_FILE,
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ ),
+ // File ModelProjectConsent rejected and failed to ask => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ TtpConsentStatus.FAILED_TO_ASK,
+ ConsentEvaluation(TtpConsentStatus.FAILED_TO_ASK, false)
+ )
+ ) {
+
+ companion object {
+ fun buildMtb(consentProvision: ConsentProvision): Mtb {
+ return Mtb.builder()
+ .patient(
+ Patient.builder().id("TEST_12345678")
+ .birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z"))).gender(
+ GenderCoding.builder().code(GenderCodingCode.MALE).build()
+ ).build()
+ )
+ .metadata(
+ MvhMetadata.builder().modelProjectConsent(
+ ModelProjectConsent.builder().provisions(
+ listOf(
+ Provision.builder().date(Date()).type(consentProvision)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
+ ).build()
+ ).build()
+ )
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder().id("1")
+ .patient(Reference.builder().id("TEST_12345678").build())
+ .build()
+ )
+ )
+ .build()
+ }
+ }
+ }
+
+ class MtbFileProvider : ArgProvider(
+ // Has file consent => consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.PERMIT),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, true)
+ ),
+ // File consent rejected => no consent given
+ Arguments.of(
+ buildMtb(ConsentProvision.DENY),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ ),
+ // policy REIDENTIFICATION has no effect on ConsentEvaluation
+ Arguments.of(
+ buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvision.DENY),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ ), Arguments.of(
+ buildMtb(ModelProjectConsentPurpose.REIDENTIFICATION, ConsentProvision.PERMIT),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ ),
+ // policy CASE_IDENTIFICATION has no effect on ConsentEvaluation
+ Arguments.of(
+ buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvision.DENY),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ ), Arguments.of(
+ buildMtb(ModelProjectConsentPurpose.CASE_IDENTIFICATION, ConsentProvision.PERMIT),
+ ConsentEvaluation(TtpConsentStatus.UNKNOWN_CHECK_FILE, false)
+ )
+ ) {
+
+ companion object {
+ fun buildMtb(consentProvision: ConsentProvision): Mtb {
+ return buildMtb(ModelProjectConsentPurpose.SEQUENCING, consentProvision)
+ }
+
+ fun buildMtb(
+ policy: ModelProjectConsentPurpose,
+ consentProvision: ConsentProvision
+ ): Mtb {
+ return Mtb.builder()
+ .patient(
+ Patient.builder().id("TEST_12345678")
+ .birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z"))).gender(
+ GenderCoding.builder().code(GenderCodingCode.MALE).build()
+ ).build()
+ )
+ .metadata(
+ MvhMetadata.builder().modelProjectConsent(
+ ModelProjectConsent.builder().provisions(
+ listOf(
+ Provision.builder().date(Date()).type(consentProvision)
+ .purpose(policy).build()
+ )
+ ).build()
+ ).build()
+ )
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder().id("1")
+ .patient(Reference.builder().id("TEST_12345678").build())
+ .build()
+ )
+ )
+ .build()
+ }
+ }
+ }
+
+}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt b/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt
index 8caa908..2dfb1e1 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/helpers.kt
@@ -17,4 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-package dev.dnpm.etl.processor \ No newline at end of file
+package dev.dnpm.etl.processor
+
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsProvider
+import java.util.stream.Stream
+
+open class ArgProvider(vararg val data: Arguments) : ArgumentsProvider {
+ override fun provideArguments(
+ context: ExtensionContext?
+ ): Stream<out Arguments> = Stream.of(*data)
+}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
index 1239cdf..a047f74 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/input/KafkaInputListenerTest.kt
@@ -20,8 +20,10 @@
package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper
-import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.CustomMediaType
+import dev.dnpm.etl.processor.consent.ConsentEvaluation
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
+import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
import dev.pcvolkmer.mv64e.mtb.*
import org.apache.kafka.clients.consumer.ConsumerRecord
@@ -40,21 +42,32 @@ import java.util.*
class KafkaInputListenerTest {
private lateinit var requestProcessor: RequestProcessor
+ private lateinit var consentEvaluator: ConsentEvaluator
private lateinit var objectMapper: ObjectMapper
+
private lateinit var kafkaInputListener: KafkaInputListener
@BeforeEach
fun setup(
@Mock requestProcessor: RequestProcessor,
+ @Mock consentEvaluator: ConsentEvaluator,
) {
this.requestProcessor = requestProcessor
+ this.consentEvaluator = consentEvaluator
this.objectMapper = ObjectMapper()
- this.kafkaInputListener = KafkaInputListener(requestProcessor, objectMapper)
+ this.kafkaInputListener = KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
}
@Test
fun shouldProcessMtbFileRequest() {
+ whenever(consentEvaluator.check(any())).thenReturn(
+ ConsentEvaluation(
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ true
+ )
+ )
+
val mtbFile = Mtb.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
@@ -64,7 +77,10 @@ class KafkaInputListenerTest {
ModelProjectConsent
.builder()
.provisions(
- listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ listOf(
+ Provision.builder().type(ConsentProvision.PERMIT)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
).build()
)
.build()
@@ -86,6 +102,13 @@ class KafkaInputListenerTest {
@Test
fun shouldProcessDeleteRequest() {
+ whenever(consentEvaluator.check(any())).thenReturn(
+ ConsentEvaluation(
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ false
+ )
+ )
+
val mtbFile = Mtb.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
@@ -95,7 +118,10 @@ class KafkaInputListenerTest {
ModelProjectConsent
.builder()
.provisions(
- listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ listOf(
+ Provision.builder().type(ConsentProvision.DENY)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
).build()
)
.build()
@@ -120,6 +146,13 @@ class KafkaInputListenerTest {
@Test
fun shouldProcessMtbFileRequestWithExistingRequestId() {
+ whenever(consentEvaluator.check(any())).thenReturn(
+ ConsentEvaluation(
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ true
+ )
+ )
+
val mtbFile = Mtb.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
@@ -129,7 +162,10 @@ class KafkaInputListenerTest {
ModelProjectConsent
.builder()
.provisions(
- listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ listOf(
+ Provision.builder().type(ConsentProvision.PERMIT)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
).build()
)
.build()
@@ -158,6 +194,13 @@ class KafkaInputListenerTest {
@Test
fun shouldProcessDeleteRequestWithExistingRequestId() {
+ whenever(consentEvaluator.check(any())).thenReturn(
+ ConsentEvaluation(
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ false
+ )
+ )
+
val mtbFile = Mtb.builder()
.patient(Patient.builder().id("DUMMY_12345678").build())
.metadata(
@@ -167,7 +210,10 @@ class KafkaInputListenerTest {
ModelProjectConsent
.builder()
.provisions(
- listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ listOf(
+ Provision.builder().type(ConsentProvision.DENY)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
).build()
)
.build()
@@ -208,7 +254,10 @@ class KafkaInputListenerTest {
ModelProjectConsent
.builder()
.provisions(
- listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
+ listOf(
+ Provision.builder().type(ConsentProvision.DENY)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
).build()
)
.build()
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
index 845f325..ae9e4e2 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/input/MtbFileRestControllerTest.kt
@@ -20,23 +20,34 @@
package dev.dnpm.etl.processor.input
import com.fasterxml.jackson.databind.ObjectMapper
+import dev.dnpm.etl.processor.ArgProvider
import dev.dnpm.etl.processor.CustomMediaType
-import dev.dnpm.etl.processor.consent.GicsConsentService
+import dev.dnpm.etl.processor.consent.ConsentEvaluation
+import dev.dnpm.etl.processor.consent.ConsentEvaluator
+import dev.dnpm.etl.processor.consent.TtpConsentStatus
import dev.dnpm.etl.processor.services.RequestProcessor
-import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.mtb.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.ArgumentsSource
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyValueClass
+import org.mockito.kotlin.whenever
import org.springframework.core.io.ClassPathResource
import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.delete
import org.springframework.test.web.servlet.post
import org.springframework.test.web.servlet.setup.MockMvcBuilders
+import java.time.Instant
+import java.util.*
@ExtendWith(MockitoExtension::class)
class MtbFileRestControllerTest {
@@ -49,22 +60,31 @@ class MtbFileRestControllerTest {
private lateinit var mockMvc: MockMvc
private lateinit var requestProcessor: RequestProcessor
+ private lateinit var consentEvaluator: ConsentEvaluator
@BeforeEach
fun setup(
@Mock requestProcessor: RequestProcessor,
- @Mock gicsConsentService: GicsConsentService
+ @Mock consentEvaluator: ConsentEvaluator
) {
this.requestProcessor = requestProcessor
+ this.consentEvaluator = consentEvaluator
val controller = MtbFileRestController(
requestProcessor,
- gicsConsentService
+ consentEvaluator
)
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
}
@Test
fun shouldRespondPostRequest() {
+ whenever(consentEvaluator.check(any())).thenReturn(
+ ConsentEvaluation(
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ true
+ )
+ )
+
val mtbFileContent =
ClassPathResource("mv64e-mtb-fake-patient.json").inputStream.readAllBytes().toString(Charsets.UTF_8)
@@ -80,5 +100,127 @@ class MtbFileRestControllerTest {
verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
}
+ @ParameterizedTest
+ @ArgumentsSource(Dnpm21MtbFile::class)
+ fun shouldProcessPostRequest(mtb: Mtb, broadConsent: TtpConsentStatus, shouldProcess: String) {
+ whenever(consentEvaluator.check(any<Mtb>())).thenReturn(
+ ConsentEvaluation(
+ broadConsent,
+ shouldProcess == "process"
+ )
+ )
+
+ mockMvc.post("/mtbfile") {
+ content = objectMapper.writeValueAsString(mtb)
+ contentType = CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON
+ }.andExpect {
+ status {
+ isAccepted()
+ }
+ }
+
+ if (shouldProcess == "process") {
+ verify(requestProcessor, times(1)).processMtbFile(any<Mtb>())
+ } else {
+ verify(requestProcessor, times(1)).processDeletion(
+ anyValueClass(),
+ org.mockito.kotlin.eq(broadConsent)
+ )
+ }
+ }
+
+ @Test
+ fun shouldProcessDeleteRequest() {
+ mockMvc.delete("/mtbfile/TEST_12345678").andExpect {
+ status {
+ isAccepted()
+ }
+ }
+
+ verify(requestProcessor, times(1)).processDeletion(
+ anyValueClass(),
+ org.mockito.kotlin.eq(TtpConsentStatus.UNKNOWN_CHECK_FILE)
+ )
+ verify(consentEvaluator, times(0)).check(any<Mtb>())
+ }
+ }
+}
+
+class Dnpm21MtbFile : ArgProvider(
+ // No Metadata and no broad consent => delete
+ Arguments.of(
+ buildMtb(null),
+ TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
+ "delete"
+ ),
+ // No Metadata and broad consent given => process
+ Arguments.of(
+ buildMtb(null),
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ "process"
+ ),
+ // No model project consent and no broad consent => delete
+ Arguments.of(
+ buildMtb(MvhMetadata.builder().modelProjectConsent(ModelProjectConsent.builder().build()).build()),
+ TtpConsentStatus.BROAD_CONSENT_MISSING_OR_REJECTED,
+ "delete"
+ ),
+ // No model project consent and broad consent given => process
+ Arguments.of(
+ buildMtb(MvhMetadata.builder().modelProjectConsent(ModelProjectConsent.builder().build()).build()),
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ "process"
+ ),
+ // Model project consent given and no broad consent => process
+ Arguments.of(
+ buildMtb(
+ MvhMetadata.builder().modelProjectConsent(
+ ModelProjectConsent.builder().provisions(
+ listOf(
+ Provision.builder().date(Date()).type(ConsentProvision.PERMIT)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
+ ).build()
+ ).build()
+ ),
+ TtpConsentStatus.UNKNOWN_CHECK_FILE,
+ "process"
+ ),
+ // Model project consent given and broad consent given => process
+ Arguments.of(
+ buildMtb(
+ MvhMetadata.builder().modelProjectConsent(
+ ModelProjectConsent.builder().provisions(
+ listOf(
+ Provision.builder().date(Date()).type(ConsentProvision.PERMIT)
+ .purpose(ModelProjectConsentPurpose.SEQUENCING).build()
+ )
+ ).build()
+ ).build()
+ ),
+ TtpConsentStatus.BROAD_CONSENT_GIVEN,
+ "process"
+ )
+) {
+
+ companion object {
+ fun buildMtb(metadata: MvhMetadata?): Mtb {
+ return Mtb.builder()
+ .patient(
+ Patient.builder().id("TEST_12345678")
+ .birthDate(Date.from(Instant.parse("2000-08-08T12:34:56Z"))).gender(
+ GenderCoding.builder().code(GenderCodingCode.MALE).build()
+ ).build()
+ )
+ .metadata(metadata)
+ .episodesOfCare(
+ listOf(
+ MtbEpisodeOfCare.builder().id("1")
+ .patient(Reference.builder().id("TEST_12345678").build())
+ .build()
+ )
+ )
+ .build()
+ }
}
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
index 58405cd..8460293 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/pseudonym/ExtensionsTest.kt
@@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import dev.dnpm.etl.processor.config.AppConfigProperties
import dev.dnpm.etl.processor.config.GIcsConfigProperties
import dev.dnpm.etl.processor.config.JacksonConfig
-import dev.dnpm.etl.processor.consent.ConsentByMtbFile
+import dev.dnpm.etl.processor.consent.MtbFileConsentService
import dev.dnpm.etl.processor.services.ConsentProcessor
import dev.dnpm.etl.processor.services.ConsentProcessorTest
import dev.pcvolkmer.mv64e.mtb.*
@@ -95,7 +95,7 @@ class ExtensionsTest {
gIcsConfigProperties,
JacksonConfig().objectMapper(),
FhirContext.forR4(),
- ConsentByMtbFile()
+ MtbFileConsentService()
).embedBroadConsentResources(mtbFile, bundle)
}
diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
index af93f7b..5a3fad0 100644
--- a/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
+++ b/src/test/kotlin/dev/dnpm/etl/processor/services/ConsentProcessorTest.kt
@@ -7,7 +7,8 @@ import dev.dnpm.etl.processor.config.GIcsConfigProperties
import dev.dnpm.etl.processor.config.JacksonConfig
import dev.dnpm.etl.processor.consent.ConsentDomain
import dev.dnpm.etl.processor.consent.GicsConsentService
-import dev.pcvolkmer.mv64e.mtb.*
+import dev.pcvolkmer.mv64e.mtb.Mtb
+import dev.pcvolkmer.mv64e.mtb.Patient
import org.assertj.core.api.Assertions.assertThat
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.CodeableConcept
@@ -46,7 +47,7 @@ class ConsentProcessorTest {
@Mock gicsConsentService: GicsConsentService,
) {
- this.gIcsConfigProperties = GIcsConfigProperties(null, null, null)
+ this.gIcsConfigProperties = GIcsConfigProperties("https://gics.example.com")
val jacksonConfig = JacksonConfig()
this.objectMapper = jacksonConfig.objectMapper()
this.fhirContext = JacksonConfig.fhirContext()
@@ -67,10 +68,10 @@ class ConsentProcessorTest {
assertThat(consentProcessor.toString()).isNotNull
// prep gICS response
doAnswer { getDummyBroadConsentBundle() }.whenever(gicsConsentService)
- .getConsent(any(), any(), eq(ConsentDomain.BroadConsent))
+ .getConsent(any(), any(), eq(ConsentDomain.BROAD_CONSENT))
doAnswer { Bundle() }.whenever(gicsConsentService)
- .getConsent(any(), any(), eq(ConsentDomain.Modelvorhaben64e))
+ .getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
val inputMtb = Mtb.builder()
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build()).build()