mirror of
https://github.com/pcvolkmer/etl-processor.git
synced 2025-07-01 14:12:55 +00:00
fix: added missing gIcs connection status to configuration view * also small refactoring
This commit is contained in:
@ -96,7 +96,7 @@ Falls in diesem Fall die Statusprüfung fehlschlägt, wird Status **abgelehnt**
|
||||
Ist die Prüfung über gIcs deaktiviert, wird der eingetragene Einwilligungsstatus der übermittelten MTB Datei geprüft.
|
||||
|
||||
* `APP_CONSENT_GICS_ENABLED`: Aktiviert oder deaktiviert `true` oder `false`, `false` wenn nicht gesetzt.
|
||||
* `APP_CONSENT_GICS_GICSBASEURI`: URI der gICS-Instanz (z.B. `http://localhost:8090/ttp-fhir/fhir/gics`)
|
||||
* `APP_CONSENT_GICS_URI`: URI der gICS-Instanz (z.B. `http://localhost:8090/ttp-fhir/fhir/gics`)
|
||||
* `APP_CONSENT_GICS_USERNAME`: gIcs Basic-Auth Benutzername
|
||||
* `APP_CONSENT_GICS_PASSWORD`: gIcs Basic-Auth Passwort
|
||||
* `APP_CONSENT_GICS_PERSONIDENTIFIERSYSTEM`: Derzeit wird nur die PID unterstützt. wenn leer wird `https://ths-greifswald.de/fhir/gics/identifiers/Patienten-ID` angenommen
|
||||
@ -423,4 +423,4 @@ Die Datei `application-dev.yml` enthält hierzu die Konfiguration für das Profi
|
||||
Beim Ausführen der Integrationstests wird eine Testdatenbank in einem Docker-Container gestartet.
|
||||
Siehe hier auch die Klasse `AbstractTestcontainerTest` unter `src/integrationTest`.
|
||||
|
||||
Ein einfaches Entwickler-Setup inklusive DNPM:DIP ist mit Hilfe von https://github.com/pcvolkmer/dnpmdip-devenv realisierbar.
|
||||
Ein einfaches Entwickler-Setup inklusive DNPM:DIP ist mit Hilfe von https://github.com/pcvolkmer/dnpmdip-devenv realisierbar.
|
||||
|
@ -22,6 +22,7 @@ package dev.dnpm.etl.processor.web
|
||||
import dev.dnpm.etl.processor.config.AppConfiguration
|
||||
import dev.dnpm.etl.processor.config.AppSecurityConfiguration
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||
import dev.dnpm.etl.processor.monitoring.GIcsConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.RestConnectionCheckService
|
||||
import dev.dnpm.etl.processor.output.MtbFileSender
|
||||
@ -89,7 +90,8 @@ abstract class MockSink : Sinks.Many<Boolean>
|
||||
RequestProcessor::class,
|
||||
TransformationService::class,
|
||||
GPasConnectionCheckService::class,
|
||||
RestConnectionCheckService::class
|
||||
RestConnectionCheckService::class,
|
||||
GIcsConnectionCheckService::class
|
||||
]
|
||||
)
|
||||
class ConfigControllerTest {
|
||||
@ -182,7 +184,13 @@ class ConfigControllerTest {
|
||||
|
||||
@Test
|
||||
fun testShouldNotSaveTokenWithExstingName() {
|
||||
whenever(tokenService.addToken(anyString())).thenReturn(Result.failure(RuntimeException("Testfailure")))
|
||||
whenever(tokenService.addToken(anyString())).thenReturn(
|
||||
Result.failure(
|
||||
RuntimeException(
|
||||
"Testfailure"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
mockMvc.post("/configs/tokens") {
|
||||
with(user("admin").roles("ADMIN"))
|
||||
@ -303,7 +311,10 @@ class ConfigControllerTest {
|
||||
|
||||
val idCaptor = argumentCaptor<Long>()
|
||||
val roleCaptor = argumentCaptor<Role>()
|
||||
verify(userRoleService, times(1)).updateUserRole(idCaptor.capture(), roleCaptor.capture())
|
||||
verify(userRoleService, times(1)).updateUserRole(
|
||||
idCaptor.capture(),
|
||||
roleCaptor.capture()
|
||||
)
|
||||
|
||||
assertThat(idCaptor.firstValue).isEqualTo(42)
|
||||
assertThat(roleCaptor.firstValue).isEqualTo(Role.ADMIN)
|
||||
@ -341,23 +352,26 @@ class ConfigControllerTest {
|
||||
|
||||
@BeforeEach
|
||||
fun setup(
|
||||
applicationContext: WebApplicationContext,
|
||||
applicationContext: WebApplicationContext
|
||||
) {
|
||||
this.webClient = MockMvcWebTestClient
|
||||
.bindToApplicationContext(applicationContext).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShouldRequestSSE() {
|
||||
val expectedEvent = ConnectionCheckResult.GPasConnectionCheckResult(true, Instant.now(), Instant.now())
|
||||
fun testShouldRequestGPasSSE() {
|
||||
val expectedEvent =
|
||||
ConnectionCheckResult.GPasConnectionCheckResult(true, Instant.now(), Instant.now())
|
||||
|
||||
connectionCheckUpdateProducer.tryEmitNext(expectedEvent)
|
||||
connectionCheckUpdateProducer.emitComplete { _, _ -> true }
|
||||
|
||||
val result = webClient.get().uri("http://localhost/configs/events").accept(TEXT_EVENT_STREAM).exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(TEXT_EVENT_STREAM)
|
||||
.returnResult(ConnectionCheckResult.GPasConnectionCheckResult::class.java)
|
||||
val result =
|
||||
webClient.get().uri("http://localhost/configs/events").accept(TEXT_EVENT_STREAM)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(TEXT_EVENT_STREAM)
|
||||
.returnResult(ConnectionCheckResult.GPasConnectionCheckResult::class.java)
|
||||
|
||||
StepVerifier.create(result.responseBody)
|
||||
.expectNext(expectedEvent)
|
||||
|
@ -1,7 +1,16 @@
|
||||
package dev.dnpm.etl.processor.consent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConsentCheckFileBased implements ICheckConsent{
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ConsentCheckFileBased.class);
|
||||
|
||||
public ConsentCheckFileBased() {
|
||||
log.info("ConsentCheckFileBased initialized...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TtpConsentStatus getTtpConsentStatus(String personIdentifierValue) {
|
||||
return TtpConsentStatus.UNKNOWN_CHECK_FILE;
|
||||
|
@ -49,11 +49,12 @@ public class GicsConsentService implements ICheckConsent {
|
||||
this.fhirContext = appFhirConfig.fhirContext();
|
||||
httpHeader = buildHeader(gIcsConfigProperties.getUsername(),
|
||||
gIcsConfigProperties.getPassword());
|
||||
log.info("GicsConsentService initialized...");
|
||||
}
|
||||
|
||||
public String getGicsUri() {
|
||||
if (url == null) {
|
||||
final String gIcsBaseUri = gIcsConfigProperties.getGIcsBaseUri();
|
||||
final String gIcsBaseUri = gIcsConfigProperties.getUri();
|
||||
if (StringUtils.isBlank(gIcsBaseUri)) {
|
||||
throw new IllegalArgumentException(
|
||||
"gICS base URL is empty - should call gICS with false configuration.");
|
||||
|
@ -62,7 +62,7 @@ data class GIcsConfigProperties(
|
||||
* Base URL to gICS System
|
||||
*
|
||||
*/
|
||||
val gIcsBaseUri: String?,
|
||||
val uri: String?,
|
||||
val username: String?,
|
||||
val password: String?,
|
||||
|
||||
|
@ -23,10 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import dev.dnpm.etl.processor.consent.ConsentCheckFileBased
|
||||
import dev.dnpm.etl.processor.consent.ICheckConsent
|
||||
import dev.dnpm.etl.processor.consent.GicsConsentService
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.ReportService
|
||||
import dev.dnpm.etl.processor.monitoring.*
|
||||
import dev.dnpm.etl.processor.pseudonym.AnonymizingGenerator
|
||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||
import dev.dnpm.etl.processor.pseudonym.GpasPseudonymGenerator
|
||||
@ -180,15 +177,9 @@ class AppConfiguration {
|
||||
return AppJdbcConfiguration()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun constService(): ICheckConsent {
|
||||
return ConsentCheckFileBased()
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
||||
fun gicsAccessConsent( gIcsConfigProperties: GIcsConfigProperties,
|
||||
fun gicsConsentService( gIcsConfigProperties: GIcsConfigProperties,
|
||||
retryTemplate: RetryTemplate, restTemplate: RestTemplate, appFhirConfig: AppFhirConfig): ICheckConsent {
|
||||
return GicsConsentService(
|
||||
gIcsConfigProperties,
|
||||
@ -197,5 +188,21 @@ class AppConfiguration {
|
||||
appFhirConfig
|
||||
)
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = ["app.consent.gics.enabled"], havingValue = "true")
|
||||
@Bean
|
||||
fun gIcsConnectionCheckService(
|
||||
restTemplate: RestTemplate,
|
||||
gIcsConfigProperties: GIcsConfigProperties,
|
||||
connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
||||
): ConnectionCheckService {
|
||||
return GIcsConnectionCheckService(restTemplate, gIcsConfigProperties, connectionCheckUpdateProducer)
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
fun constService(): ICheckConsent {
|
||||
return ConsentCheckFileBased()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
package dev.dnpm.etl.processor.monitoring
|
||||
|
||||
import dev.dnpm.etl.processor.config.GIcsConfigProperties
|
||||
import dev.dnpm.etl.processor.config.GPasConfigProperties
|
||||
import dev.dnpm.etl.processor.config.RestTargetProperties
|
||||
import jakarta.annotation.PostConstruct
|
||||
@ -68,6 +69,12 @@ sealed class ConnectionCheckResult {
|
||||
override val timestamp: Instant,
|
||||
override val lastChange: Instant
|
||||
) : ConnectionCheckResult()
|
||||
|
||||
data class GIcsConnectionCheckResult(
|
||||
override val available: Boolean,
|
||||
override val timestamp: Instant,
|
||||
override val lastChange: Instant
|
||||
) : ConnectionCheckResult()
|
||||
}
|
||||
|
||||
class KafkaConnectionCheckService(
|
||||
@ -207,4 +214,57 @@ class GPasConnectionCheckService(
|
||||
override fun connectionAvailable(): ConnectionCheckResult.GPasConnectionCheckResult {
|
||||
return this.result
|
||||
}
|
||||
}
|
||||
|
||||
class GIcsConnectionCheckService(
|
||||
private val restTemplate: RestTemplate,
|
||||
private val gIcsConfigProperties: GIcsConfigProperties,
|
||||
@Qualifier("connectionCheckUpdateProducer")
|
||||
private val connectionCheckUpdateProducer: Sinks.Many<ConnectionCheckResult>
|
||||
) : ConnectionCheckService {
|
||||
|
||||
private var result = ConnectionCheckResult.GIcsConnectionCheckResult(false, Instant.now(), Instant.now())
|
||||
|
||||
@PostConstruct
|
||||
@Scheduled(cron = "0 * * * * *")
|
||||
fun check() {
|
||||
result = try {
|
||||
|
||||
val uri = UriComponentsBuilder.fromUriString(
|
||||
gIcsConfigProperties.uri.toString()).path("/metadata").build().toUri()
|
||||
|
||||
val headers = HttpHeaders()
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
if (!gIcsConfigProperties.username.isNullOrBlank() && !gIcsConfigProperties.password.isNullOrBlank()) {
|
||||
headers.setBasicAuth(gIcsConfigProperties.username, gIcsConfigProperties.password)
|
||||
}
|
||||
|
||||
val available = restTemplate.exchange(
|
||||
uri,
|
||||
HttpMethod.GET,
|
||||
HttpEntity<Void>(headers),
|
||||
Void::class.java
|
||||
).statusCode == HttpStatus.OK
|
||||
|
||||
ConnectionCheckResult.GIcsConnectionCheckResult(
|
||||
available,
|
||||
Instant.now(),
|
||||
if (result.available == available) { result.lastChange } else { Instant.now() }
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
ConnectionCheckResult.GIcsConnectionCheckResult(
|
||||
false,
|
||||
Instant.now(),
|
||||
if (!result.available) { result.lastChange } else { Instant.now() }
|
||||
)
|
||||
}
|
||||
connectionCheckUpdateProducer.emitNext(
|
||||
result,
|
||||
Sinks.EmitFailureHandler.FAIL_FAST
|
||||
)
|
||||
}
|
||||
|
||||
override fun connectionAvailable(): ConnectionCheckResult.GIcsConnectionCheckResult {
|
||||
return this.result
|
||||
}
|
||||
}
|
@ -19,10 +19,7 @@
|
||||
|
||||
package dev.dnpm.etl.processor.web
|
||||
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckResult
|
||||
import dev.dnpm.etl.processor.monitoring.ConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.GPasConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.OutputConnectionCheckService
|
||||
import dev.dnpm.etl.processor.monitoring.*
|
||||
import dev.dnpm.etl.processor.output.MtbFileSender
|
||||
import dev.dnpm.etl.processor.pseudonym.Generator
|
||||
import dev.dnpm.etl.processor.security.Role
|
||||
@ -61,11 +58,15 @@ class ConfigController(
|
||||
val gPasConnectionAvailable =
|
||||
connectionCheckServices.filterIsInstance<GPasConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
||||
|
||||
val gIcsConnectionAvailable =
|
||||
connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
||||
|
||||
model.addAttribute("pseudonymGenerator", pseudonymGenerator.javaClass.simpleName)
|
||||
model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
|
||||
model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
|
||||
model.addAttribute("outputConnectionAvailable", outputConnectionAvailable)
|
||||
model.addAttribute("gPasConnectionAvailable", gPasConnectionAvailable)
|
||||
model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable)
|
||||
model.addAttribute("tokensEnabled", tokenService != null)
|
||||
if (tokenService != null) {
|
||||
model.addAttribute("tokens", tokenService.findAll())
|
||||
@ -119,6 +120,24 @@ class ConfigController(
|
||||
return "configs/gPasConnectionAvailable"
|
||||
}
|
||||
|
||||
@GetMapping(params = ["gIcsConnectionAvailable"])
|
||||
fun gIcsConnectionAvailable(model: Model): String {
|
||||
val gIcsConnectionAvailable =
|
||||
connectionCheckServices.filterIsInstance<GIcsConnectionCheckService>().firstOrNull()?.connectionAvailable()
|
||||
|
||||
model.addAttribute("mtbFileSender", mtbFileSender.javaClass.simpleName)
|
||||
model.addAttribute("mtbFileEndpoint", mtbFileSender.endpoint())
|
||||
model.addAttribute("gIcsConnectionAvailable", gIcsConnectionAvailable)
|
||||
if (tokenService != null) {
|
||||
model.addAttribute("tokensEnabled", true)
|
||||
model.addAttribute("tokens", tokenService.findAll())
|
||||
} else {
|
||||
model.addAttribute("tokens", listOf<Token>())
|
||||
}
|
||||
|
||||
return "configs/gIcsConnectionAvailable"
|
||||
}
|
||||
|
||||
@PostMapping(path = ["tokens"])
|
||||
fun addToken(@ModelAttribute("name") name: String, model: Model): String {
|
||||
if (tokenService == null) {
|
||||
@ -190,6 +209,7 @@ class ConfigController(
|
||||
is ConnectionCheckResult.KafkaConnectionCheckResult -> "output-connection-check"
|
||||
is ConnectionCheckResult.RestConnectionCheckResult -> "output-connection-check"
|
||||
is ConnectionCheckResult.GPasConnectionCheckResult -> "gpas-connection-check"
|
||||
is ConnectionCheckResult.GIcsConnectionCheckResult -> "gics-connection-check"
|
||||
}
|
||||
|
||||
ServerSentEvent.builder<Any>()
|
||||
|
@ -49,6 +49,11 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
||||
<div th:insert="~{configs/gIcsConnectionAvailable.html}" th:hx-get="@{/configs?gIcsConnectionAvailable}" hx-trigger="sse:gics-connection-check">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section hx-ext="sse" th:sse-connect="@{/configs/events}">
|
||||
<div th:insert="~{configs/outputConnectionAvailable.html}" th:hx-get="@{/configs?outputConnectionAvailable}" hx-trigger="sse:output-connection-check">
|
||||
</div>
|
||||
|
@ -0,0 +1,24 @@
|
||||
<th:block th:if="${gIcsConnectionAvailable == null}">
|
||||
<h2><span>🟦</span> gICS nicht konfiguriert - Einwilligung wird über Dateiinhalt geprüft</h2>
|
||||
</th:block>
|
||||
<th:block th:if="${gIcsConnectionAvailable != null}">
|
||||
<h2><span th:if="${gIcsConnectionAvailable.available}">✅</span><span th:if="${not(gIcsConnectionAvailable.available)}">⚡</span> Verbindung zu gICS</h2>
|
||||
<div>
|
||||
Stand: <time style="font-weight: bold" th:datetime="${#temporals.formatISO(gIcsConnectionAvailable.timestamp)}" th:text="${#temporals.formatISO(gIcsConnectionAvailable.timestamp)}"></time>
|
||||
|
|
||||
Letzte Änderung: <time style="font-weight: bold" th:datetime="${#temporals.formatISO(gIcsConnectionAvailable.lastChange)}" th:text="${#temporals.formatISO(gIcsConnectionAvailable.lastChange)}"></time>
|
||||
</div>
|
||||
<div>
|
||||
<span>Die Verbindung ist aktuell</span>
|
||||
<strong th:if="${gIcsConnectionAvailable.available}" style="color: green">verfügbar.</strong>
|
||||
<strong th:if="${not(gIcsConnectionAvailable.available)}" style="color: red">nicht verfügbar.</strong>
|
||||
</div>
|
||||
<div class="connection-display border">
|
||||
<img th:src="@{/server.png}" alt="ETL-Processor" />
|
||||
<span class="connection" th:classappend="${gIcsConnectionAvailable.available ? 'available' : ''}"></span>
|
||||
<img th:src="@{/server.png}" alt="gICS" />
|
||||
<span>ETL-Processor</span>
|
||||
<span></span>
|
||||
<span>gICS</span>
|
||||
</div>
|
||||
</th:block>
|
@ -3,6 +3,7 @@ package dev.dnpm.etl.processor.consent;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dev.dnpm.etl.processor.config.AppConfiguration;
|
||||
import dev.dnpm.etl.processor.config.AppFhirConfig;
|
||||
@ -23,19 +24,19 @@ import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
|
||||
|
||||
|
||||
@ContextConfiguration(classes = {GicsConsentService.class,
|
||||
AppConfiguration.class, ObjectMapper.class})
|
||||
@ContextConfiguration(classes = {AppConfiguration.class, ObjectMapper.class})
|
||||
@TestPropertySource(properties = {"app.consent.gics.enabled=true",
|
||||
"app.consent.gics.gIcsBaseUri=http://localhost:8090/ttp-fhir/fhir/gics"})
|
||||
"app.consent.gics.uri=http://localhost:8090/ttp-fhir/fhir/gics"})
|
||||
@RestClientTest
|
||||
public class GicsConsentServiceTest {
|
||||
|
||||
public static final String GICS_BASE_URI = "http://localhost:8090/ttp-fhir/fhir/gics";
|
||||
@Autowired
|
||||
MockRestServiceServer mockRestServiceServer;
|
||||
|
||||
@Autowired
|
||||
GicsConsentService gicsConsentService;
|
||||
|
||||
@Autowired
|
||||
AppConfiguration appConfiguration;
|
||||
|
||||
@ -45,6 +46,7 @@ public class GicsConsentServiceTest {
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
mockRestServiceServer = MockRestServiceServer.createServer(appConfiguration.restTemplate());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -54,7 +56,8 @@ public class GicsConsentServiceTest {
|
||||
.setValue(new BooleanType().setValue(true)));
|
||||
|
||||
mockRestServiceServer.expect(
|
||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics"
|
||||
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
||||
.encodeResourceToString(responseConsented),
|
||||
MediaType.APPLICATION_JSON));
|
||||
@ -63,7 +66,6 @@ public class GicsConsentServiceTest {
|
||||
assertThat(consentStatus).isEqualTo(TtpConsentStatus.CONSENTED);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void consentRevoced() {
|
||||
final Parameters responseRevoced = new Parameters().addParameter(
|
||||
@ -71,7 +73,8 @@ public class GicsConsentServiceTest {
|
||||
.setValue(new BooleanType().setValue(false)));
|
||||
|
||||
mockRestServiceServer.expect(
|
||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
||||
requestTo("http://localhost:8090/ttp-fhir/fhir/gics"
|
||||
+ GicsConsentService.IS_CONSENTED_ENDPOINT)).andRespond(
|
||||
withSuccess(appFhirConfig.fhirContext().newJsonParser()
|
||||
.encodeResourceToString(responseRevoced),
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
Reference in New Issue
Block a user