mirror of
https://github.com/pcvolkmer/mv64e-etl-processor
synced 2025-09-13 09:02:50 +00:00
feat: check consent for DNPM 2.1 requests (#126)
Co-authored-by: Jakub Lidke <jakub.lidke@uni-marburg.de>
This commit is contained in:
@@ -102,6 +102,18 @@ class EtlProcessorApplicationTests : AbstractTestcontainerTest() {
|
|||||||
.id("TEST_12345678")
|
.id("TEST_12345678")
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
.metadata(
|
||||||
|
MvhMetadata
|
||||||
|
.builder()
|
||||||
|
.modelProjectConsent(
|
||||||
|
ModelProjectConsent
|
||||||
|
.builder()
|
||||||
|
.provisions(
|
||||||
|
listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.diagnoses(
|
.diagnoses(
|
||||||
listOf(
|
listOf(
|
||||||
MtbDiagnosis.builder()
|
MtbDiagnosis.builder()
|
||||||
|
@@ -20,8 +20,9 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
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.GicsConsentService
|
||||||
|
import dev.dnpm.etl.processor.consent.MtbFileConsentService
|
||||||
import dev.dnpm.etl.processor.input.KafkaInputListener
|
import dev.dnpm.etl.processor.input.KafkaInputListener
|
||||||
import dev.dnpm.etl.processor.monitoring.RequestRepository
|
import dev.dnpm.etl.processor.monitoring.RequestRepository
|
||||||
import dev.dnpm.etl.processor.output.KafkaMtbFileSender
|
import dev.dnpm.etl.processor.output.KafkaMtbFileSender
|
||||||
@@ -53,7 +54,8 @@ import org.springframework.test.context.bean.override.mockito.MockitoBean
|
|||||||
AppSecurityConfiguration::class,
|
AppSecurityConfiguration::class,
|
||||||
KafkaAutoConfiguration::class,
|
KafkaAutoConfiguration::class,
|
||||||
AppKafkaConfiguration::class,
|
AppKafkaConfiguration::class,
|
||||||
AppRestConfiguration::class
|
AppRestConfiguration::class,
|
||||||
|
ConsentEvaluator::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@MockitoBean(types = [ObjectMapper::class])
|
@MockitoBean(types = [ObjectMapper::class])
|
||||||
@@ -281,7 +283,8 @@ class AppConfigurationTest {
|
|||||||
@Nested
|
@Nested
|
||||||
@TestPropertySource(
|
@TestPropertySource(
|
||||||
properties = [
|
properties = [
|
||||||
"app.consent.service=GICS"
|
"app.consent.service=GICS",
|
||||||
|
"app.consent.gics.uri=http://localhost:9000",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
inner class AppConfigurationConsentGicsTest(private val context: ApplicationContext) {
|
inner class AppConfigurationConsentGicsTest(private val context: ApplicationContext) {
|
||||||
@@ -293,27 +296,12 @@ 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
|
@Nested
|
||||||
inner class AppConfigurationConsentBuildinTest(private val context: ApplicationContext) {
|
inner class AppConfigurationConsentBuildinTest(private val context: ApplicationContext) {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldUseConfiguredGenerator() {
|
fun shouldUseConfiguredGenerator() {
|
||||||
assertThat(context.getBean(ConsentByMtbFile::class.java)).isNotNull
|
assertThat(context.getBean(MtbFileConsentService::class.java)).isNotNull
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,11 +20,11 @@
|
|||||||
package dev.dnpm.etl.processor.input
|
package dev.dnpm.etl.processor.input
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dev.dnpm.etl.processor.anyValueClass
|
|
||||||
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
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.TtpConsentStatus
|
||||||
import dev.dnpm.etl.processor.consent.IGetConsent
|
|
||||||
import dev.dnpm.etl.processor.security.TokenRepository
|
import dev.dnpm.etl.processor.security.TokenRepository
|
||||||
import dev.dnpm.etl.processor.security.UserRoleRepository
|
import dev.dnpm.etl.processor.security.UserRoleRepository
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
@@ -57,32 +57,37 @@ import java.util.*
|
|||||||
classes = [
|
classes = [
|
||||||
MtbFileRestController::class,
|
MtbFileRestController::class,
|
||||||
AppSecurityConfiguration::class,
|
AppSecurityConfiguration::class,
|
||||||
ConsentByMtbFile::class, IGetConsent::class
|
MtbFileConsentService::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@MockitoBean(types = [TokenRepository::class, RequestProcessor::class])
|
@MockitoBean(types = [TokenRepository::class, RequestProcessor::class, ConsentEvaluator::class])
|
||||||
@TestPropertySource(
|
@TestPropertySource(
|
||||||
properties = [
|
properties = [
|
||||||
"app.pseudonymize.generator=BUILDIN",
|
"app.pseudonymize.generator=BUILDIN",
|
||||||
"app.security.admin-user=admin",
|
"app.security.admin-user=admin",
|
||||||
"app.security.admin-password={noop}very-secret",
|
"app.security.admin-password={noop}very-secret",
|
||||||
"app.security.enable-tokens=true",
|
"app.security.enable-tokens=true"
|
||||||
"app.consent.gics.enabled=false"
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
class MtbFileRestControllerTest {
|
class MtbFileRestControllerTest {
|
||||||
|
|
||||||
private lateinit var mockMvc: MockMvc
|
lateinit var mockMvc: MockMvc
|
||||||
|
lateinit var requestProcessor: RequestProcessor
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
lateinit var consentEvaluator: ConsentEvaluator
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Autowired mockMvc: MockMvc,
|
@Autowired mockMvc: MockMvc,
|
||||||
@Autowired requestProcessor: RequestProcessor
|
@Autowired requestProcessor: RequestProcessor,
|
||||||
|
@Autowired consentEvaluator: ConsentEvaluator
|
||||||
) {
|
) {
|
||||||
this.mockMvc = mockMvc
|
this.mockMvc = mockMvc
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
|
this.consentEvaluator = consentEvaluator
|
||||||
|
|
||||||
|
doAnswer {
|
||||||
|
ConsentEvaluation(TtpConsentStatus.BROAD_CONSENT_GIVEN, true)
|
||||||
|
}.whenever(consentEvaluator).check(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -167,8 +172,7 @@ class MtbFileRestControllerTest {
|
|||||||
"app.security.admin-user=admin",
|
"app.security.admin-user=admin",
|
||||||
"app.security.admin-password={noop}very-secret",
|
"app.security.admin-password={noop}very-secret",
|
||||||
"app.security.enable-tokens=true",
|
"app.security.enable-tokens=true",
|
||||||
"app.security.enable-oidc=true",
|
"app.security.enable-oidc=true"
|
||||||
"app.consent.gics.enabled=false"
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
inner class WithOidcEnabled {
|
inner class WithOidcEnabled {
|
||||||
|
@@ -4,10 +4,10 @@ public enum ConsentDomain {
|
|||||||
/**
|
/**
|
||||||
* MII Broad consent
|
* MII Broad consent
|
||||||
*/
|
*/
|
||||||
BroadConsent,
|
BROAD_CONSENT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenomDe Modelvohaben §64e
|
* GenomDe Modellvorhaben §64e
|
||||||
*/
|
*/
|
||||||
Modelvorhaben64e
|
MODELLVORHABEN_64E
|
||||||
}
|
}
|
||||||
|
@@ -4,17 +4,9 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
||||||
import java.util.Date;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
import org.hl7.fhir.r4.model.*;
|
||||||
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.Parameters.ParametersParameterComponent;
|
import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -22,112 +14,152 @@ import org.springframework.http.HttpEntity;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.retry.TerminatedRetryException;
|
import org.springframework.retry.TerminatedRetryException;
|
||||||
import org.springframework.retry.support.RetryTemplate;
|
import org.springframework.retry.support.RetryTemplate;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
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);
|
private final Logger log = LoggerFactory.getLogger(GicsConsentService.class);
|
||||||
|
|
||||||
public static final String IS_CONSENTED_ENDPOINT = "/$isConsented";
|
public static final String IS_CONSENTED_ENDPOINT = "/$isConsented";
|
||||||
public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson";
|
public static final String IS_POLICY_STATES_FOR_PERSON_ENDPOINT = "/$currentPolicyStatesForPerson";
|
||||||
|
|
||||||
private final RetryTemplate retryTemplate;
|
private final RetryTemplate retryTemplate;
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final FhirContext fhirContext;
|
private final FhirContext fhirContext;
|
||||||
private final HttpHeaders httpHeader;
|
|
||||||
private final GIcsConfigProperties gIcsConfigProperties;
|
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.retryTemplate = retryTemplate;
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
this.fhirContext = appFhirConfig.fhirContext();
|
this.fhirContext = appFhirConfig.fhirContext();
|
||||||
httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
|
|
||||||
gIcsConfigProperties.getPassword());
|
|
||||||
this.gIcsConfigProperties = gIcsConfigProperties;
|
this.gIcsConfigProperties = gIcsConfigProperties;
|
||||||
log.info("GicsConsentService initialized...");
|
log.info("GicsConsentService initialized...");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGicsUri(String endpoint) {
|
protected Parameters getFhirRequestParameters(
|
||||||
if (url == null) {
|
String personIdentifierValue
|
||||||
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) {
|
|
||||||
var result = new Parameters();
|
var result = new Parameters();
|
||||||
result.addParameter(new ParametersParameterComponent().setName("personIdentifier").setValue(
|
result.addParameter(
|
||||||
new Identifier().setValue(personIdentifierValue)
|
new ParametersParameterComponent()
|
||||||
.setSystem(configProperties.getPersonIdentifierSystem())));
|
.setName("personIdentifier")
|
||||||
result.addParameter(new ParametersParameterComponent().setName("domain")
|
.setValue(
|
||||||
.setValue(new StringType().setValue(configProperties.getBroadConsentDomainName())));
|
new Identifier()
|
||||||
result.addParameter(new ParametersParameterComponent().setName("policy").setValue(
|
.setValue(personIdentifierValue)
|
||||||
new Coding().setCode(configProperties.getBroadConsentPolicyCode())
|
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
|
||||||
.setSystem(configProperties.getBroadConsentPolicySystem())));
|
)
|
||||||
|
);
|
||||||
|
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
|
* is mandatory parameter, but we ignore it via additional configuration parameter
|
||||||
* 'ignoreVersionNumber'.
|
* 'ignoreVersionNumber'.
|
||||||
*/
|
*/
|
||||||
result.addParameter(new ParametersParameterComponent().setName("version")
|
result.addParameter(
|
||||||
.setValue(new StringType().setValue("1.1")));
|
new ParametersParameterComponent()
|
||||||
|
.setName("version")
|
||||||
|
.setValue(new StringType().setValue("1.1")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
/* add config parameter with:
|
/* add config parameter with:
|
||||||
* ignoreVersionNumber -> true ->> Reason is we cannot know which policy version each patient
|
* 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.
|
* has possibly signed or not, therefore we are happy with any version found.
|
||||||
* unknownStateIsConsideredAsDecline -> true
|
* unknownStateIsConsideredAsDecline -> true
|
||||||
*/
|
*/
|
||||||
var config = new ParametersParameterComponent().setName("config").addPart(
|
var config = new ParametersParameterComponent()
|
||||||
new ParametersParameterComponent().setName("ignoreVersionNumber")
|
.setName("config")
|
||||||
.setValue(new BooleanType().setValue(true))).addPart(
|
.addPart(
|
||||||
new ParametersParameterComponent().setName("unknownStateIsConsideredAsDecline")
|
new ParametersParameterComponent()
|
||||||
.setValue(new BooleanType().setValue(false)));
|
.setName("ignoreVersionNumber")
|
||||||
|
.setValue(new BooleanType().setValue(true))
|
||||||
|
)
|
||||||
|
.addPart(
|
||||||
|
new ParametersParameterComponent()
|
||||||
|
.setName("unknownStateIsConsideredAsDecline")
|
||||||
|
.setValue(new BooleanType().setValue(false))
|
||||||
|
);
|
||||||
|
|
||||||
result.addParameter(config);
|
result.addParameter(config);
|
||||||
|
|
||||||
return result;
|
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) {
|
protected String callGicsApi(Parameters parameter, String endpoint) {
|
||||||
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
var parameterAsXml = fhirContext.newXmlParser().encodeResourceToString(parameter);
|
||||||
|
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.headersWithHttpBasicAuth());
|
||||||
HttpEntity<String> requestEntity = new HttpEntity<>(parameterAsXml, this.httpHeader);
|
|
||||||
ResponseEntity<String> responseEntity;
|
|
||||||
try {
|
try {
|
||||||
var url = getGicsUri(endpoint);
|
var responseEntity = retryTemplate.execute(
|
||||||
|
ctx -> restTemplate.exchange(endpointUri(endpoint), HttpMethod.POST, requestEntity, String.class)
|
||||||
|
);
|
||||||
|
|
||||||
responseEntity = retryTemplate.execute(
|
if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
||||||
ctx -> restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class));
|
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) {
|
} 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;
|
||||||
|
|
||||||
@@ -137,39 +169,32 @@ public class GicsConsentService implements IGetConsent {
|
|||||||
terminatedRetryException.getMessage());
|
terminatedRetryException.getMessage());
|
||||||
log.error(msg);
|
log.error(msg);
|
||||||
return null;
|
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
|
@Override
|
||||||
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
|
public TtpConsentStatus getTtpBroadConsentStatus(String personIdentifierValue) {
|
||||||
var parameter = GicsConsentService.getIsConsentedRequestParam(gIcsConfigProperties,
|
var consentStatusResponse = callGicsApi(
|
||||||
personIdentifierValue);
|
getFhirRequestParameters(personIdentifierValue),
|
||||||
|
GicsConsentService.IS_CONSENTED_ENDPOINT
|
||||||
var consentStatusResponse = callGicsApi(parameter,
|
);
|
||||||
GicsConsentService.IS_CONSENTED_ENDPOINT);
|
|
||||||
return evaluateConsentResponse(consentStatusResponse);
|
return evaluateConsentResponse(consentStatusResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Bundle currentConsentForPersonAndTemplate(String personIdentifierValue,
|
protected Bundle currentConsentForPersonAndTemplate(
|
||||||
ConsentDomain targetConsentDomain, Date requestDate) {
|
String personIdentifierValue,
|
||||||
|
ConsentDomain consentDomain,
|
||||||
|
Date requestDate
|
||||||
|
) {
|
||||||
|
|
||||||
String consentDomain = getConsentDomain(targetConsentDomain);
|
var requestParameter = buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
|
personIdentifierValue,
|
||||||
var requestParameter = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
|
requestDate,
|
||||||
gIcsConfigProperties, personIdentifierValue, 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!
|
||||||
@@ -177,15 +202,15 @@ public class GicsConsentService implements IGetConsent {
|
|||||||
"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) {
|
} else if (iBaseResource instanceof Bundle bundle) {
|
||||||
return (Bundle) iBaseResource;
|
return bundle;
|
||||||
} else {
|
} else {
|
||||||
String errorMessage = "Consent request failed! Unexpected response received! -> "
|
String errorMessage = "Consent request failed! Unexpected response received! -> "
|
||||||
+ consentDataSerialized;
|
+ consentDataSerialized;
|
||||||
@@ -195,40 +220,52 @@ public class GicsConsentService implements IGetConsent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private String getConsentDomain(ConsentDomain targetConsentDomain) {
|
private String getConsentDomainName(ConsentDomain targetConsentDomain) {
|
||||||
String consentDomain;
|
return switch (targetConsentDomain) {
|
||||||
switch (targetConsentDomain) {
|
case BROAD_CONSENT -> gIcsConfigProperties.getBroadConsentDomainName();
|
||||||
case BroadConsent -> consentDomain = gIcsConfigProperties.getBroadConsentDomainName();
|
case MODELLVORHABEN_64E -> gIcsConfigProperties.getGenomDeConsentDomainName();
|
||||||
case Modelvorhaben64e ->
|
};
|
||||||
consentDomain = gIcsConfigProperties.getGenomDeConsentDomainName();
|
|
||||||
default -> throw new IllegalArgumentException(
|
|
||||||
"target ConsentDomain is missing but must be provided!");
|
|
||||||
}
|
|
||||||
return consentDomain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
protected Parameters buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
GIcsConfigProperties gIcsConfigProperties, String personIdentifierValue, Date requestDate,
|
String personIdentifierValue,
|
||||||
String targetDomain) {
|
Date requestDate,
|
||||||
|
ConsentDomain consentDomain
|
||||||
|
) {
|
||||||
var requestParameter = new Parameters();
|
var requestParameter = new Parameters();
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("personIdentifier")
|
requestParameter.addParameter(
|
||||||
.setValue(new Identifier().setValue(personIdentifierValue)
|
new ParametersParameterComponent()
|
||||||
.setSystem(gIcsConfigProperties.getPersonIdentifierSystem())));
|
.setName("personIdentifier")
|
||||||
|
.setValue(
|
||||||
|
new Identifier()
|
||||||
|
.setValue(personIdentifierValue)
|
||||||
|
.setSystem(this.gIcsConfigProperties.getPersonIdentifierSystem())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("domain")
|
requestParameter.addParameter(
|
||||||
.setValue(new StringType().setValue(targetDomain)));
|
new ParametersParameterComponent()
|
||||||
|
.setName("domain")
|
||||||
|
.setValue(new StringType().setValue(getConsentDomainName(consentDomain)))
|
||||||
|
);
|
||||||
|
|
||||||
Parameters nestedConfigParameters = new Parameters();
|
Parameters nestedConfigParameters = new Parameters();
|
||||||
nestedConfigParameters.addParameter(
|
nestedConfigParameters
|
||||||
new ParametersParameterComponent().setName("idMatchingType").setValue(
|
.addParameter(
|
||||||
new Coding().setSystem(
|
new ParametersParameterComponent()
|
||||||
"https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
|
.setName("idMatchingType")
|
||||||
.setCode("AT_LEAST_ONE"))).addParameter("ignoreVersionNumber", false)
|
.setValue(new Coding()
|
||||||
|
.setSystem("https://ths-greifswald.de/fhir/CodeSystem/gics/IdMatchingType")
|
||||||
|
.setCode("AT_LEAST_ONE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addParameter("ignoreVersionNumber", false)
|
||||||
.addParameter("unknownStateIsConsideredAsDecline", false)
|
.addParameter("unknownStateIsConsideredAsDecline", false)
|
||||||
.addParameter("requestDate", new DateType().setValue(requestDate));
|
.addParameter("requestDate", new DateType().setValue(requestDate));
|
||||||
|
|
||||||
requestParameter.addParameter(new ParametersParameterComponent().setName("config").addPart()
|
requestParameter.addParameter(
|
||||||
.setResource(nestedConfigParameters));
|
new ParametersParameterComponent().setName("config").addPart().setResource(nestedConfigParameters)
|
||||||
|
);
|
||||||
|
|
||||||
return requestParameter;
|
return requestParameter;
|
||||||
}
|
}
|
||||||
@@ -254,7 +291,7 @@ public class GicsConsentService implements IGetConsent {
|
|||||||
}
|
}
|
||||||
} 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) {
|
||||||
@@ -265,17 +302,6 @@ public class GicsConsentService implements IGetConsent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) {
|
public Bundle getConsent(String patientId, Date requestDate, ConsentDomain consentDomain) {
|
||||||
switch (consentDomain) {
|
return currentConsentForPersonAndTemplate(patientId, consentDomain, requestDate);
|
||||||
case BroadConsent -> {
|
|
||||||
return currentConsentForPersonAndTemplate(patientId, ConsentDomain.BroadConsent,
|
|
||||||
requestDate);
|
|
||||||
}
|
|
||||||
case Modelvorhaben64e -> {
|
|
||||||
return currentConsentForPersonAndTemplate(patientId,
|
|
||||||
ConsentDomain.Modelvorhaben64e, requestDate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Bundle();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ package dev.dnpm.etl.processor.consent;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
|
||||||
public interface IGetConsent {
|
public interface IConsentService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get broad consent status for a patient identifier
|
* Get broad consent status for a patient identifier
|
@@ -5,11 +5,11 @@ import org.hl7.fhir.r4.model.Bundle;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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...");
|
log.info("ConsentCheckFileBased initialized...");
|
||||||
}
|
}
|
||||||
|
|
@@ -73,8 +73,8 @@ data class GIcsConfigProperties(
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
val uri: String?,
|
val uri: String?,
|
||||||
val username: String?,
|
val username: String? = null,
|
||||||
val password: String?,
|
val password: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gICS specific system
|
* gICS specific system
|
||||||
|
@@ -20,9 +20,9 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
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.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.monitoring.*
|
||||||
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
||||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||||
@@ -218,7 +218,7 @@ class AppConfiguration {
|
|||||||
retryTemplate: RetryTemplate,
|
retryTemplate: RetryTemplate,
|
||||||
restTemplate: RestTemplate,
|
restTemplate: RestTemplate,
|
||||||
appFhirConfig: AppFhirConfig
|
appFhirConfig: AppFhirConfig
|
||||||
): IGetConsent {
|
): IConsentService {
|
||||||
return GicsConsentService(
|
return GicsConsentService(
|
||||||
gIcsConfigProperties,
|
gIcsConfigProperties,
|
||||||
retryTemplate,
|
retryTemplate,
|
||||||
@@ -234,7 +234,7 @@ class AppConfiguration {
|
|||||||
gIcsConfigProperties: GIcsConfigProperties,
|
gIcsConfigProperties: GIcsConfigProperties,
|
||||||
getObjectMapper: ObjectMapper,
|
getObjectMapper: ObjectMapper,
|
||||||
appFhirConfig: AppFhirConfig,
|
appFhirConfig: AppFhirConfig,
|
||||||
gicsConsentService: IGetConsent
|
gicsConsentService: IConsentService
|
||||||
): ConsentProcessor {
|
): ConsentProcessor {
|
||||||
return ConsentProcessor(
|
return ConsentProcessor(
|
||||||
configProperties,
|
configProperties,
|
||||||
@@ -261,8 +261,8 @@ class AppConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
fun iGetConsentService(): IGetConsent {
|
fun iGetConsentService(): IConsentService {
|
||||||
return ConsentByMtbFile()
|
return MtbFileConsentService()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -271,13 +271,9 @@ class GicsEnabledCondition :
|
|||||||
AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) {
|
AnyNestedCondition(ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN) {
|
||||||
|
|
||||||
@ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
|
@ConditionalOnProperty(name = ["app.consent.service"], havingValue = "gics")
|
||||||
|
@ConditionalOnProperty(name = ["app.consent.gics.uri"])
|
||||||
class OnGicsServiceSelected {
|
class OnGicsServiceSelected {
|
||||||
// Just for Condition
|
// Just for Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
|
||||||
class OnGicsEnabled {
|
|
||||||
// Just for Condition
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
package dev.dnpm.etl.processor.config
|
package dev.dnpm.etl.processor.config
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
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.input.KafkaInputListener
|
||||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||||
@@ -100,9 +101,10 @@ class AppKafkaConfiguration {
|
|||||||
@ConditionalOnProperty(value = ["app.kafka.input-topic"])
|
@ConditionalOnProperty(value = ["app.kafka.input-topic"])
|
||||||
fun kafkaInputListener(
|
fun kafkaInputListener(
|
||||||
requestProcessor: RequestProcessor,
|
requestProcessor: RequestProcessor,
|
||||||
objectMapper: ObjectMapper
|
objectMapper: ObjectMapper,
|
||||||
|
consentEvaluator: ConsentEvaluator
|
||||||
): KafkaInputListener {
|
): KafkaInputListener {
|
||||||
return KafkaInputListener(requestProcessor, objectMapper)
|
return KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -113,4 +115,4 @@ class AppKafkaConfiguration {
|
|||||||
return KafkaConnectionCheckService(consumerFactory.createConsumer(), connectionCheckUpdateProducer)
|
return KafkaConnectionCheckService(consumerFactory.createConsumer(), connectionCheckUpdateProducer)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -23,9 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.PatientId
|
import dev.dnpm.etl.processor.PatientId
|
||||||
import dev.dnpm.etl.processor.RequestId
|
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.consent.TtpConsentStatus
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import dev.pcvolkmer.mv64e.mtb.ConsentProvision
|
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -34,6 +34,7 @@ import org.springframework.kafka.listener.MessageListener
|
|||||||
|
|
||||||
class KafkaInputListener(
|
class KafkaInputListener(
|
||||||
private val requestProcessor: RequestProcessor,
|
private val requestProcessor: RequestProcessor,
|
||||||
|
private val consentEvaluator: ConsentEvaluator,
|
||||||
private val objectMapper: ObjectMapper
|
private val objectMapper: ObjectMapper
|
||||||
) : MessageListener<String, String> {
|
) : MessageListener<String, String> {
|
||||||
private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java)
|
private val logger = LoggerFactory.getLogger(KafkaInputListener::class.java)
|
||||||
@@ -70,8 +71,7 @@ class KafkaInputListener(
|
|||||||
RequestId("")
|
RequestId("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use MV Consent for now - needs to be replaced with proper consent evaluation
|
if (consentEvaluator.check(mtbFile).hasConsent()) {
|
||||||
if (mtbFile.metadata.modelProjectConsent.provisions.filter { it.type == ConsentProvision.PERMIT }.isNotEmpty()) {
|
|
||||||
logger.debug("Accepted MTB File for processing")
|
logger.debug("Accepted MTB File for processing")
|
||||||
if (requestId.isBlank()) {
|
if (requestId.isBlank()) {
|
||||||
requestProcessor.processMtbFile(mtbFile)
|
requestProcessor.processMtbFile(mtbFile)
|
||||||
|
@@ -21,7 +21,7 @@ package dev.dnpm.etl.processor.input
|
|||||||
|
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
import dev.dnpm.etl.processor.CustomMediaType
|
||||||
import dev.dnpm.etl.processor.PatientId
|
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.consent.TtpConsentStatus
|
||||||
import dev.dnpm.etl.processor.services.RequestProcessor
|
import dev.dnpm.etl.processor.services.RequestProcessor
|
||||||
import dev.pcvolkmer.mv64e.mtb.Mtb
|
import dev.pcvolkmer.mv64e.mtb.Mtb
|
||||||
@@ -34,9 +34,8 @@ import org.springframework.web.bind.annotation.*
|
|||||||
@RequestMapping(path = ["mtbfile", "mtb"])
|
@RequestMapping(path = ["mtbfile", "mtb"])
|
||||||
class MtbFileRestController(
|
class MtbFileRestController(
|
||||||
private val requestProcessor: RequestProcessor,
|
private val requestProcessor: RequestProcessor,
|
||||||
private val iGetConsent: IGetConsent
|
private val consentEvaluator: ConsentEvaluator
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
private val logger = LoggerFactory.getLogger(MtbFileRestController::class.java)
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -46,8 +45,15 @@ class MtbFileRestController(
|
|||||||
|
|
||||||
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE])
|
@PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, CustomMediaType.APPLICATION_VND_DNPM_V2_MTB_JSON_VALUE])
|
||||||
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
|
fun mtbFile(@RequestBody mtbFile: Mtb): ResponseEntity<Unit> {
|
||||||
logger.debug("Accepted MTB File (DNPM V2) for processing")
|
val consentEvaluation = consentEvaluator.check(mtbFile)
|
||||||
requestProcessor.processMtbFile(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()
|
return ResponseEntity.accepted().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,9 +6,9 @@ import com.fasterxml.jackson.core.type.TypeReference
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
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.ConsentDomain
|
||||||
import dev.dnpm.etl.processor.consent.IGetConsent
|
import dev.dnpm.etl.processor.consent.IConsentService
|
||||||
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
|
import dev.dnpm.etl.processor.pseudonym.ensureMetaDataIsInitialized
|
||||||
import dev.pcvolkmer.mv64e.mtb.*
|
import dev.pcvolkmer.mv64e.mtb.*
|
||||||
import org.apache.commons.lang3.NotImplementedException
|
import org.apache.commons.lang3.NotImplementedException
|
||||||
@@ -31,7 +31,7 @@ class ConsentProcessor(
|
|||||||
private val gIcsConfigProperties: GIcsConfigProperties,
|
private val gIcsConfigProperties: GIcsConfigProperties,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val fhirContext: FhirContext,
|
private val fhirContext: FhirContext,
|
||||||
private val consentService: IGetConsent
|
private val consentService: IConsentService
|
||||||
) {
|
) {
|
||||||
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
|
private var logger: Logger = LoggerFactory.getLogger("ConsentProcessor")
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class ConsentProcessor(
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
|
fun consentGatedCheckAndTryEmbedding(mtbFile: Mtb): Boolean {
|
||||||
if (consentService is ConsentByMtbFile) {
|
if (consentService is MtbFileConsentService) {
|
||||||
// consent check is disabled
|
// consent check is disabled
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ class ConsentProcessor(
|
|||||||
* broad consent
|
* broad consent
|
||||||
*/
|
*/
|
||||||
val broadConsent = consentService.getConsent(
|
val broadConsent = consentService.getConsent(
|
||||||
personIdentifierValue, requestDate, ConsentDomain.BroadConsent
|
personIdentifierValue, requestDate, ConsentDomain.BROAD_CONSENT
|
||||||
)
|
)
|
||||||
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
|
val broadConsentHasBeenAsked = !broadConsent.entry.isEmpty()
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ class ConsentProcessor(
|
|||||||
if (!broadConsentHasBeenAsked) return false
|
if (!broadConsentHasBeenAsked) return false
|
||||||
|
|
||||||
val genomeDeConsent = consentService.getConsent(
|
val genomeDeConsent = consentService.getConsent(
|
||||||
personIdentifierValue, requestDate, ConsentDomain.Modelvorhaben64e
|
personIdentifierValue, requestDate, ConsentDomain.MODELLVORHABEN_64E
|
||||||
)
|
)
|
||||||
|
|
||||||
addGenomeDbProvisions(mtbFile, genomeDeConsent)
|
addGenomeDbProvisions(mtbFile, genomeDeConsent)
|
||||||
@@ -88,11 +88,11 @@ class ConsentProcessor(
|
|||||||
embedBroadConsentResources(mtbFile, broadConsent)
|
embedBroadConsentResources(mtbFile, broadConsent)
|
||||||
|
|
||||||
val broadConsentStatus = getProvisionTypeByPolicyCode(
|
val broadConsentStatus = getProvisionTypeByPolicyCode(
|
||||||
broadConsent, requestDate, ConsentDomain.BroadConsent
|
broadConsent, requestDate, ConsentDomain.BROAD_CONSENT
|
||||||
)
|
)
|
||||||
|
|
||||||
val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
|
val genomDeSequencingStatus = getProvisionTypeByPolicyCode(
|
||||||
genomeDeConsent, requestDate, ConsentDomain.Modelvorhaben64e
|
genomeDeConsent, requestDate, ConsentDomain.MODELLVORHABEN_64E
|
||||||
)
|
)
|
||||||
|
|
||||||
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
|
if (Consent.ConsentProvisionType.NULL == broadConsentStatus) {
|
||||||
@@ -204,10 +204,10 @@ class ConsentProcessor(
|
|||||||
): Consent.ConsentProvisionType {
|
): Consent.ConsentProvisionType {
|
||||||
val code: String?
|
val code: String?
|
||||||
val system: String?
|
val system: String?
|
||||||
if (ConsentDomain.BroadConsent == consentDomain) {
|
if (ConsentDomain.BROAD_CONSENT == consentDomain) {
|
||||||
code = gIcsConfigProperties.broadConsentPolicyCode
|
code = gIcsConfigProperties.broadConsentPolicyCode
|
||||||
system = gIcsConfigProperties.broadConsentPolicySystem
|
system = gIcsConfigProperties.broadConsentPolicySystem
|
||||||
} else if (ConsentDomain.Modelvorhaben64e == consentDomain) {
|
} else if (ConsentDomain.MODELLVORHABEN_64E == consentDomain) {
|
||||||
code = gIcsConfigProperties.genomeDePolicyCode
|
code = gIcsConfigProperties.genomeDePolicyCode
|
||||||
system = gIcsConfigProperties.genomeDePolicySystem
|
system = gIcsConfigProperties.genomeDePolicySystem
|
||||||
} else {
|
} else {
|
||||||
@@ -279,4 +279,4 @@ class ConsentProcessor(
|
|||||||
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
|
return isRequestDateAfterOrEqualStart <= 0 && isRequestDateBeforeOrEqualEnd >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,85 +1,112 @@
|
|||||||
package dev.dnpm.etl.processor.consent;
|
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 com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import dev.dnpm.etl.processor.config.AppConfiguration;
|
import dev.dnpm.etl.processor.config.AppConfiguration;
|
||||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
import dev.dnpm.etl.processor.config.GIcsConfigProperties;
|
||||||
import java.time.Instant;
|
import org.hl7.fhir.r4.model.*;
|
||||||
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.OperationOutcome.IssueSeverity;
|
import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
|
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
|
||||||
import org.hl7.fhir.r4.model.OperationOutcome.OperationOutcomeIssueComponent;
|
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.Parameters.ParametersParameterComponent;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
|
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.retry.support.RetryTemplate;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.springframework.test.web.client.MockRestServiceServer;
|
import org.springframework.test.web.client.MockRestServiceServer;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
|
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
|
||||||
@TestPropertySource(properties = {"app.consent.gics.enabled=true",
|
@TestPropertySource(properties = {
|
||||||
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"})
|
"app.consent.service=gics",
|
||||||
|
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"
|
||||||
|
})
|
||||||
@RestClientTest
|
@RestClientTest
|
||||||
public class GicsConsentServiceTest {
|
class GicsConsentServiceTest {
|
||||||
|
|
||||||
|
static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
|
||||||
|
|
||||||
public static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
|
|
||||||
@Autowired
|
|
||||||
MockRestServiceServer mockRestServiceServer;
|
MockRestServiceServer mockRestServiceServer;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
GicsConsentService gicsConsentService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AppConfiguration appConfiguration;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AppFhirConfig appFhirConfig;
|
AppFhirConfig appFhirConfig;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
GIcsConfigProperties gIcsConfigProperties;
|
GIcsConfigProperties gIcsConfigProperties;
|
||||||
|
|
||||||
|
GicsConsentService gicsConsentService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
void setUp(
|
||||||
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
|
@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
|
@Test
|
||||||
void getTtpBroadConsentStatus() {
|
void shouldReturnTtpBroadConsentStatus() {
|
||||||
final Parameters responseConsented = new Parameters().addParameter(
|
final Parameters consentedResponse = new Parameters()
|
||||||
new ParametersParameterComponent().setName("consented")
|
.addParameter(
|
||||||
.setValue(new BooleanType().setValue(true)));
|
new ParametersParameterComponent()
|
||||||
|
.setName("consented")
|
||||||
|
.setValue(new BooleanType().setValue(true))
|
||||||
|
);
|
||||||
|
|
||||||
mockRestServiceServer.expect(requestTo(
|
mockRestServiceServer
|
||||||
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
|
.expect(
|
||||||
.andRespond(withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
requestTo(
|
||||||
.encodeResourceToString(responseConsented), MediaType.APPLICATION_JSON));
|
"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");
|
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.BROAD_CONSENT_GIVEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void consentRevoced() {
|
void shouldReturnRevokedConsent() {
|
||||||
final Parameters responseRevoced = new Parameters().addParameter(
|
final Parameters revokedResponse = new Parameters()
|
||||||
new ParametersParameterComponent().setName("consented")
|
.addParameter(
|
||||||
.setValue(new BooleanType().setValue(false)));
|
new ParametersParameterComponent()
|
||||||
|
.setName("consented")
|
||||||
|
.setValue(new BooleanType().setValue(false))
|
||||||
|
);
|
||||||
|
|
||||||
mockRestServiceServer.expect(requestTo(
|
mockRestServiceServer
|
||||||
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT))
|
.expect(
|
||||||
.andRespond(withSuccess(
|
requestTo(
|
||||||
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseRevoced),
|
"http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)
|
||||||
MediaType.APPLICATION_JSON));
|
)
|
||||||
|
.andRespond(
|
||||||
|
withSuccess(
|
||||||
|
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(revokedResponse),
|
||||||
|
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);
|
||||||
@@ -87,15 +114,39 @@ public class GicsConsentServiceTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void gicsParameterInvalid() {
|
void shouldReturnInvalidParameterResponse() {
|
||||||
final OperationOutcome responseErrorOutcome = new OperationOutcome().addIssue(
|
final OperationOutcome responseWithErrorOutcome = new OperationOutcome()
|
||||||
new OperationOutcomeIssueComponent().setSeverity(IssueSeverity.ERROR)
|
.addIssue(
|
||||||
.setCode(IssueType.PROCESSING).setDiagnostics("Invalid policy parameter..."));
|
new OperationOutcomeIssueComponent()
|
||||||
|
.setSeverity(IssueSeverity.ERROR)
|
||||||
|
.setCode(IssueType.PROCESSING)
|
||||||
|
.setDiagnostics("Invalid policy parameter...")
|
||||||
|
);
|
||||||
|
|
||||||
mockRestServiceServer.expect(
|
mockRestServiceServer
|
||||||
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
.expect(
|
||||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT)
|
||||||
.encodeResourceToString(responseErrorOutcome), MediaType.APPLICATION_JSON));
|
)
|
||||||
|
.andRespond(
|
||||||
|
withSuccess(
|
||||||
|
appFhirConfig.fhirContext().newJsonParser().encodeResourceToString(responseWithErrorOutcome),
|
||||||
|
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");
|
var consentStatus = gicsConsentService.getTtpBroadConsentStatus("123456");
|
||||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
assertThat(consentStatus).isEqualTo(TtpConsentStatus.FAILED_TO_ASK);
|
||||||
@@ -103,20 +154,27 @@ public class GicsConsentServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildRequestParameterCurrentPolicyStatesForPersonTest() {
|
void buildRequestParameterCurrentPolicyStatesForPersonTest() {
|
||||||
|
|
||||||
String pid = "12345678";
|
String pid = "12345678";
|
||||||
var result = GicsConsentService.buildRequestParameterCurrentPolicyStatesForPerson(
|
var result = gicsConsentService
|
||||||
gIcsConfigProperties, pid, Date.from(Instant.now()),
|
.buildRequestParameterCurrentPolicyStatesForPerson(
|
||||||
gIcsConfigProperties.getGenomDeConsentDomainName());
|
pid,
|
||||||
|
Date.from(Instant.now()),
|
||||||
|
ConsentDomain.MODELLVORHABEN_64E
|
||||||
|
);
|
||||||
|
|
||||||
assertThat(result.getParameter().size()).as("should contain 3 parameter resources")
|
assertThat(result.getParameter())
|
||||||
.isEqualTo(3);
|
.as("should contain 3 parameter resources")
|
||||||
|
.hasSize(3);
|
||||||
|
|
||||||
assertThat(((StringType) result.getParameter("domain").getValue()).getValue()).isEqualTo(
|
assertThat(((StringType) result.getParameter("domain").getValue()).getValue())
|
||||||
gIcsConfigProperties.getGenomDeConsentDomainName());
|
.isEqualTo(
|
||||||
assertThat(
|
gIcsConfigProperties.getGenomDeConsentDomainName()
|
||||||
((Identifier) result.getParameter("personIdentifier").getValue()).getValue()).isEqualTo(
|
);
|
||||||
pid);
|
|
||||||
|
assertThat(((Identifier) result.getParameter("personIdentifier").getValue()).getValue())
|
||||||
|
.isEqualTo(
|
||||||
|
pid
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -17,4 +17,15 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.dnpm.etl.processor
|
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)
|
||||||
|
}
|
||||||
|
@@ -20,8 +20,10 @@
|
|||||||
package dev.dnpm.etl.processor.input
|
package dev.dnpm.etl.processor.input
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import dev.dnpm.etl.processor.consent.TtpConsentStatus
|
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
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.dnpm.etl.processor.services.RequestProcessor
|
||||||
import dev.pcvolkmer.mv64e.mtb.*
|
import dev.pcvolkmer.mv64e.mtb.*
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord
|
import org.apache.kafka.clients.consumer.ConsumerRecord
|
||||||
@@ -40,21 +42,32 @@ import java.util.*
|
|||||||
class KafkaInputListenerTest {
|
class KafkaInputListenerTest {
|
||||||
|
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
private lateinit var requestProcessor: RequestProcessor
|
||||||
|
private lateinit var consentEvaluator: ConsentEvaluator
|
||||||
private lateinit var objectMapper: ObjectMapper
|
private lateinit var objectMapper: ObjectMapper
|
||||||
|
|
||||||
private lateinit var kafkaInputListener: KafkaInputListener
|
private lateinit var kafkaInputListener: KafkaInputListener
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Mock requestProcessor: RequestProcessor,
|
@Mock requestProcessor: RequestProcessor,
|
||||||
|
@Mock consentEvaluator: ConsentEvaluator,
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
|
this.consentEvaluator = consentEvaluator
|
||||||
this.objectMapper = ObjectMapper()
|
this.objectMapper = ObjectMapper()
|
||||||
|
|
||||||
this.kafkaInputListener = KafkaInputListener(requestProcessor, objectMapper)
|
this.kafkaInputListener = KafkaInputListener(requestProcessor, consentEvaluator, objectMapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessMtbFileRequest() {
|
fun shouldProcessMtbFileRequest() {
|
||||||
|
whenever(consentEvaluator.check(any())).thenReturn(
|
||||||
|
ConsentEvaluation(
|
||||||
|
TtpConsentStatus.BROAD_CONSENT_GIVEN,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val mtbFile = Mtb.builder()
|
val mtbFile = Mtb.builder()
|
||||||
.patient(Patient.builder().id("DUMMY_12345678").build())
|
.patient(Patient.builder().id("DUMMY_12345678").build())
|
||||||
.metadata(
|
.metadata(
|
||||||
@@ -64,7 +77,10 @@ class KafkaInputListenerTest {
|
|||||||
ModelProjectConsent
|
ModelProjectConsent
|
||||||
.builder()
|
.builder()
|
||||||
.provisions(
|
.provisions(
|
||||||
listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
listOf(
|
||||||
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING).build()
|
||||||
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@@ -86,6 +102,13 @@ class KafkaInputListenerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessDeleteRequest() {
|
fun shouldProcessDeleteRequest() {
|
||||||
|
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(
|
||||||
@@ -95,7 +118,10 @@ class KafkaInputListenerTest {
|
|||||||
ModelProjectConsent
|
ModelProjectConsent
|
||||||
.builder()
|
.builder()
|
||||||
.provisions(
|
.provisions(
|
||||||
listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
listOf(
|
||||||
|
Provision.builder().type(ConsentProvision.DENY)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING).build()
|
||||||
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@@ -120,6 +146,13 @@ class KafkaInputListenerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessMtbFileRequestWithExistingRequestId() {
|
fun shouldProcessMtbFileRequestWithExistingRequestId() {
|
||||||
|
whenever(consentEvaluator.check(any())).thenReturn(
|
||||||
|
ConsentEvaluation(
|
||||||
|
TtpConsentStatus.BROAD_CONSENT_GIVEN,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val mtbFile = Mtb.builder()
|
val mtbFile = Mtb.builder()
|
||||||
.patient(Patient.builder().id("DUMMY_12345678").build())
|
.patient(Patient.builder().id("DUMMY_12345678").build())
|
||||||
.metadata(
|
.metadata(
|
||||||
@@ -129,7 +162,10 @@ class KafkaInputListenerTest {
|
|||||||
ModelProjectConsent
|
ModelProjectConsent
|
||||||
.builder()
|
.builder()
|
||||||
.provisions(
|
.provisions(
|
||||||
listOf(Provision.builder().type(ConsentProvision.PERMIT).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
listOf(
|
||||||
|
Provision.builder().type(ConsentProvision.PERMIT)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING).build()
|
||||||
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@@ -158,6 +194,13 @@ class KafkaInputListenerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldProcessDeleteRequestWithExistingRequestId() {
|
fun shouldProcessDeleteRequestWithExistingRequestId() {
|
||||||
|
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(
|
||||||
@@ -167,7 +210,10 @@ class KafkaInputListenerTest {
|
|||||||
ModelProjectConsent
|
ModelProjectConsent
|
||||||
.builder()
|
.builder()
|
||||||
.provisions(
|
.provisions(
|
||||||
listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
listOf(
|
||||||
|
Provision.builder().type(ConsentProvision.DENY)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING).build()
|
||||||
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
@@ -208,7 +254,10 @@ class KafkaInputListenerTest {
|
|||||||
ModelProjectConsent
|
ModelProjectConsent
|
||||||
.builder()
|
.builder()
|
||||||
.provisions(
|
.provisions(
|
||||||
listOf(Provision.builder().type(ConsentProvision.DENY).purpose(ModelProjectConsentPurpose.SEQUENCING).build())
|
listOf(
|
||||||
|
Provision.builder().type(ConsentProvision.DENY)
|
||||||
|
.purpose(ModelProjectConsentPurpose.SEQUENCING).build()
|
||||||
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
@@ -20,23 +20,34 @@
|
|||||||
package dev.dnpm.etl.processor.input
|
package dev.dnpm.etl.processor.input
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import dev.dnpm.etl.processor.ArgProvider
|
||||||
import dev.dnpm.etl.processor.CustomMediaType
|
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.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.BeforeEach
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.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.Mock
|
||||||
import org.mockito.Mockito.times
|
import org.mockito.Mockito.times
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.kotlin.anyValueClass
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
import org.springframework.core.io.ClassPathResource
|
import org.springframework.core.io.ClassPathResource
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
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.post
|
||||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
class MtbFileRestControllerTest {
|
class MtbFileRestControllerTest {
|
||||||
@@ -49,22 +60,31 @@ class MtbFileRestControllerTest {
|
|||||||
private lateinit var mockMvc: MockMvc
|
private lateinit var mockMvc: MockMvc
|
||||||
|
|
||||||
private lateinit var requestProcessor: RequestProcessor
|
private lateinit var requestProcessor: RequestProcessor
|
||||||
|
private lateinit var consentEvaluator: ConsentEvaluator
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup(
|
fun setup(
|
||||||
@Mock requestProcessor: RequestProcessor,
|
@Mock requestProcessor: RequestProcessor,
|
||||||
@Mock gicsConsentService: GicsConsentService
|
@Mock consentEvaluator: ConsentEvaluator
|
||||||
) {
|
) {
|
||||||
this.requestProcessor = requestProcessor
|
this.requestProcessor = requestProcessor
|
||||||
|
this.consentEvaluator = consentEvaluator
|
||||||
val controller = MtbFileRestController(
|
val controller = MtbFileRestController(
|
||||||
requestProcessor,
|
requestProcessor,
|
||||||
gicsConsentService
|
consentEvaluator
|
||||||
)
|
)
|
||||||
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldRespondPostRequest() {
|
fun shouldRespondPostRequest() {
|
||||||
|
whenever(consentEvaluator.check(any())).thenReturn(
|
||||||
|
ConsentEvaluation(
|
||||||
|
TtpConsentStatus.BROAD_CONSENT_GIVEN,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val mtbFileContent =
|
val mtbFileContent =
|
||||||
ClassPathResource("mv64e-mtb-fake-patient.json").inputStream.readAllBytes().toString(Charsets.UTF_8)
|
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>())
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||||||
import dev.dnpm.etl.processor.config.AppConfigProperties
|
import dev.dnpm.etl.processor.config.AppConfigProperties
|
||||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
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.ConsentProcessor
|
||||||
import dev.dnpm.etl.processor.services.ConsentProcessorTest
|
import dev.dnpm.etl.processor.services.ConsentProcessorTest
|
||||||
import dev.pcvolkmer.mv64e.mtb.*
|
import dev.pcvolkmer.mv64e.mtb.*
|
||||||
@@ -95,7 +95,7 @@ class ExtensionsTest {
|
|||||||
gIcsConfigProperties,
|
gIcsConfigProperties,
|
||||||
JacksonConfig().objectMapper(),
|
JacksonConfig().objectMapper(),
|
||||||
FhirContext.forR4(),
|
FhirContext.forR4(),
|
||||||
ConsentByMtbFile()
|
MtbFileConsentService()
|
||||||
).embedBroadConsentResources(mtbFile, bundle)
|
).embedBroadConsentResources(mtbFile, bundle)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,8 @@ import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
|||||||
import dev.dnpm.etl.processor.config.JacksonConfig
|
import dev.dnpm.etl.processor.config.JacksonConfig
|
||||||
import dev.dnpm.etl.processor.consent.ConsentDomain
|
import dev.dnpm.etl.processor.consent.ConsentDomain
|
||||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
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.assertj.core.api.Assertions.assertThat
|
||||||
import org.hl7.fhir.r4.model.Bundle
|
import org.hl7.fhir.r4.model.Bundle
|
||||||
import org.hl7.fhir.r4.model.CodeableConcept
|
import org.hl7.fhir.r4.model.CodeableConcept
|
||||||
@@ -46,7 +47,7 @@ class ConsentProcessorTest {
|
|||||||
@Mock gicsConsentService: GicsConsentService,
|
@Mock gicsConsentService: GicsConsentService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.gIcsConfigProperties = GIcsConfigProperties(null, null, null)
|
this.gIcsConfigProperties = GIcsConfigProperties("https://gics.example.com")
|
||||||
val jacksonConfig = JacksonConfig()
|
val jacksonConfig = JacksonConfig()
|
||||||
this.objectMapper = jacksonConfig.objectMapper()
|
this.objectMapper = jacksonConfig.objectMapper()
|
||||||
this.fhirContext = JacksonConfig.fhirContext()
|
this.fhirContext = JacksonConfig.fhirContext()
|
||||||
@@ -67,10 +68,10 @@ class ConsentProcessorTest {
|
|||||||
assertThat(consentProcessor.toString()).isNotNull
|
assertThat(consentProcessor.toString()).isNotNull
|
||||||
// prep gICS response
|
// prep gICS response
|
||||||
doAnswer { getDummyBroadConsentBundle() }.whenever(gicsConsentService)
|
doAnswer { getDummyBroadConsentBundle() }.whenever(gicsConsentService)
|
||||||
.getConsent(any(), any(), eq(ConsentDomain.BroadConsent))
|
.getConsent(any(), any(), eq(ConsentDomain.BROAD_CONSENT))
|
||||||
|
|
||||||
doAnswer { Bundle() }.whenever(gicsConsentService)
|
doAnswer { Bundle() }.whenever(gicsConsentService)
|
||||||
.getConsent(any(), any(), eq(ConsentDomain.Modelvorhaben64e))
|
.getConsent(any(), any(), eq(ConsentDomain.MODELLVORHABEN_64E))
|
||||||
|
|
||||||
val inputMtb = Mtb.builder()
|
val inputMtb = Mtb.builder()
|
||||||
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build()).build()
|
.patient(Patient.builder().id("d611d429-5003-11f0-a144-661e92ac9503").build()).build()
|
||||||
|
Reference in New Issue
Block a user