diff --git a/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/SdsClient.java b/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/SdsClient.java index 91437d2..0b87db0 100644 --- a/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/SdsClient.java +++ b/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/SdsClient.java @@ -36,41 +36,49 @@ public class SdsClient { private String supplierOdsCode; public Mono callForGetAsid(String interactionId, String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for consumer ASID (fromOdsCode={}, interactionId={}, correlationId={})", + fromOdsCode, interactionId, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildAsDeviceAsidRequest(fromOdsCode, supplierOdsCode, interactionId, correlationId); return retrieveAsDeviceNhsSpineAsid(sdsDeviceRequest, LOOKUP_CONTEXT_CONSUMER_ASID); } public Mono callForGetStructuredRecord(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for GetStructuredRecord (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildGetStructuredRecordAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildGetStructuredRecordEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); } public Mono callForMigrateStructuredRecord(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for MigrateStructuredRecord (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildMigrateStructuredRecordAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildMigrateStructuredRecordEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); } public Mono callForPatientSearchAccessDocument(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for PatientSearchAccessDocument (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildPatientSearchAccessDocumentAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildPatientSearchAccessDocumentEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); } public Mono callForSearchForDocumentRecord(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for SearchForDocument (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildSearchForDocumentAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildSearchForDocumentEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); } public Mono callForRetrieveDocumentRecord(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for RetrieveDocument (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildRetrieveDocumentAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildRetrieveDocumentEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); } public Mono callForMigrateDocumentRecord(String fromOdsCode, String correlationId) { + LOGGER.info("SDS lookup for MigrateDocument (fromOdsCode={}, correlationId={})", fromOdsCode, correlationId); var sdsDeviceRequest = sdsRequestBuilder.buildMigrateDocumentAsDeviceRequest(fromOdsCode, correlationId); var sdsEndpointRequest = sdsRequestBuilder.buildMigrateDocumentEndpointRequest(fromOdsCode, correlationId); return retrieveData(sdsDeviceRequest, sdsEndpointRequest); @@ -86,6 +94,11 @@ private Mono retrieveData(RequestHeadersSpec { doBundleEntryCheck(bundle, LOOKUP_CONTEXT_PROVIDER_ENDPOINT); var endpoint = (Endpoint) bundle.getEntryFirstRep().getResource(); + var nhsMhsId = getNhsMhsId(endpoint); + var address = getAddressFromEndpoint(endpoint); + + LOGGER.info("SDS provider details retrieved (nhsMhsId={}, nhsSpineAsid={}, address={})", + nhsMhsId, nhsSpineAsid, address); return SdsResponseData.builder() .address(getAddressFromEndpoint(endpoint)) @@ -165,14 +178,13 @@ private String getAddressFromEndpoint(Endpoint endpoint) { LOGGER.error("SDS Endpoint response contained an empty address"); throw new RuntimeException("SDS returned a result but with an empty address"); } - LOGGER.info("Found GPC provider endpoint in SDS: {}", address); return address; } private Mono performRequest(RequestHeadersSpec> request) { return request.retrieve() .bodyToMono(String.class) - .doOnError(e -> LOGGER.error("SDS request failed: {}", e)); + .doOnError(e -> LOGGER.error("SDS request failed", e)); } @Builder diff --git a/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilder.java b/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilder.java index 134951a..ae63ab7 100644 --- a/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilder.java +++ b/service/src/main/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilder.java @@ -24,6 +24,7 @@ @RequiredArgsConstructor(onConstructor = @__(@Autowired)) @Slf4j public class SdsRequestBuilder { + private static final String PIPE = "|"; private static final String ORG_CODE_PARAMETER = "organization"; private static final String ORG_CODE_IDENTIFIER = "https://fhir.nhs.uk/Id/ods-organization-code"; @@ -121,9 +122,13 @@ private RequestHeadersSpec> buildAsidClientFor(S String interaction, String correlationId) { if (StringUtils.isEmpty(supplierOdsCode)) { + LOGGER.error("Supplier ODS code is not configured — cannot build ASID lookup request"); throw new GpConnectException("Supplier ODS code variable must be defined"); } + LOGGER.debug("Building ASID Device request (consumerOdsCode={}, supplierOdsCode={}, interaction={})", + consumerOrgOdsCode, supplierOdsCode, interaction); + var httpClient = getHttpClient(); return buildWebClient(httpClient) @@ -141,6 +146,7 @@ private RequestHeadersSpec> buildAsidClientFor(S @NotNull private RequestHeadersSpec> buildClientFor(String odsCode, String interaction, String correlationId, String path) { + LOGGER.debug("Building SDS request (odsCode={}, interaction={}, path={})", odsCode, interaction, path); var httpClient = getHttpClient(); return buildWebClient(httpClient) diff --git a/service/src/test/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilderTest.java b/service/src/test/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilderTest.java new file mode 100644 index 0000000..b8a0591 --- /dev/null +++ b/service/src/test/java/uk/nhs/adaptors/gpc/consumer/sds/builder/SdsRequestBuilderTest.java @@ -0,0 +1,142 @@ +package uk.nhs.adaptors.gpc.consumer.sds.builder; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeStrategies; + +import io.netty.handler.ssl.SslContext; +import reactor.core.publisher.Mono; +import uk.nhs.adaptors.gpc.consumer.gpc.exception.GpConnectException; +import uk.nhs.adaptors.gpc.consumer.sds.configuration.SdsConfiguration; +import uk.nhs.adaptors.gpc.consumer.web.RequestBuilderService; +import uk.nhs.adaptors.gpc.consumer.web.WebClientFilterService; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class SdsRequestBuilderTest { + + private static final String ODS_CODE = "A12345"; + private static final String SUPPLIER_ODS_CODE = "SUPPLIER01"; + private static final String CORRELATION_ID = "corr-id-001"; + private static final String INTERACTION_ID = "urn:nhs:names:services:gpconnect:fhir:operation:gpc.getstructuredrecord-1"; + private static final String SDS_URL = "https://sds.example.com"; + private static final String API_KEY = "test-api-key"; + + @Mock + private SdsConfiguration sdsConfiguration; + + @Mock + private RequestBuilderService requestBuilderService; + + @Mock + private WebClientFilterService webClientFilterService; + + @Mock + private SslContext sslContext; + + private SdsRequestBuilder sdsRequestBuilder; + + @BeforeEach + void setUp() { + sdsRequestBuilder = new SdsRequestBuilder(sdsConfiguration, requestBuilderService, webClientFilterService); + when(requestBuilderService.buildStandardSslContext()).thenReturn(sslContext); + when(requestBuilderService.buildExchangeStrategies()).thenReturn(ExchangeStrategies.withDefaults()); + when(sdsConfiguration.getUrl()).thenReturn(SDS_URL); + when(sdsConfiguration.getApiKey()).thenReturn(API_KEY); + when(webClientFilterService.logRequest()) + .thenReturn((request, next) -> next.exchange(request)); + when(webClientFilterService.logResponse()) + .thenReturn(ExchangeFilterFunction.ofResponseProcessor(Mono::just)); + when(webClientFilterService.errorHandlingFilter(any(), any())) + .thenReturn(ExchangeFilterFunction.ofResponseProcessor(Mono::just)); + } + + @ParameterizedTest + @NullAndEmptySource + void When_SupplierOdsCodeIsBlank_Expect_GpConnectException(String blankSupplierOdsCode) { + assertThatThrownBy(() -> sdsRequestBuilder.buildAsDeviceAsidRequest(ODS_CODE, blankSupplierOdsCode, INTERACTION_ID, CORRELATION_ID)) + .isInstanceOf(GpConnectException.class) + .hasMessageContaining("Supplier ODS code variable must be defined"); + } + + @Test + void When_SupplierOdsCodeIsPresent_Expect_BuildAsDeviceAsidRequestReturnsRequest() { + + var result = sdsRequestBuilder.buildAsDeviceAsidRequest(ODS_CODE, SUPPLIER_ODS_CODE, INTERACTION_ID, CORRELATION_ID); + + assertNotNull(result); + } + + @Test + void When_Called_Expect_BuildGetStructuredRecordAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildGetStructuredRecordAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildGetStructuredRecordEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildGetStructuredRecordEndpointRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildMigrateStructuredRecordAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildMigrateStructuredRecordAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildMigrateStructuredRecordEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildMigrateStructuredRecordEndpointRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildPatientSearchAccessDocumentAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildPatientSearchAccessDocumentAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildPatientSearchAccessDocumentEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildPatientSearchAccessDocumentEndpointRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildSearchForDocumentAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildSearchForDocumentAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildSearchForDocumentEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildSearchForDocumentEndpointRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildRetrieveDocumentAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildRetrieveDocumentAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildRetrieveDocumentEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildRetrieveDocumentEndpointRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildMigrateDocumentAsDeviceRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildMigrateDocumentAsDeviceRequest(ODS_CODE, CORRELATION_ID)); + } + + @Test + void When_Called_Expect_BuildMigrateDocumentEndpointRequestReturnsRequest() { + assertNotNull(sdsRequestBuilder.buildMigrateDocumentEndpointRequest(ODS_CODE, CORRELATION_ID)); + } +}