diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java index da4caed482..0babf0d2e0 100644 --- a/service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java @@ -2,7 +2,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.stereotype.Service; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; @@ -10,6 +9,7 @@ @Service public class FhirParseService { + private final IParser jsonParser = prepareParser(); public T parseResource(String body, Class fhirClass) { @@ -20,6 +20,13 @@ public T parseResource(String body, Class fhirClass } } + public String encodeToJson(IBaseResource resource) { + IParser jsonParser = prepareParser(); + return jsonParser + .setPrettyPrint(true) + .encodeResourceToString(resource); + } + private IParser prepareParser() { FhirContext ctx = FhirContext.forDstu3(); ctx.newJsonParser(); diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendController.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendController.java index 2a42649f43..0c5c627f44 100644 --- a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendController.java +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import uk.nhs.adaptors.gp2gp.common.service.FhirParseService; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.common.service.TimestampService; import uk.nhs.adaptors.gp2gp.common.task.TaskDispatcher; @@ -30,37 +31,42 @@ public class EhrResendController { private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1"; + public static final String INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"; + public static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE"; private EhrExtractStatusRepository ehrExtractStatusRepository; private TaskDispatcher taskDispatcher; private RandomIdGeneratorService randomIdGeneratorService; private final TimestampService timestampService; + private final FhirParseService fhirParseService; @PostMapping("/{conversationId}") - public ResponseEntity scheduleEhrExtractResend(@PathVariable String conversationId) { + public ResponseEntity scheduleEhrExtractResend(@PathVariable String conversationId) { EhrExtractStatus ehrExtractStatus = ehrExtractStatusRepository.findByConversationId(conversationId).orElseGet(() -> null); if (ehrExtractStatus == null) { - var details = getCodeableConcept("INVALID_IDENTIFIER_VALUE"); + var details = getCodeableConcept(INVALID_IDENTIFIER_VALUE); var diagnostics = "Provide a conversationId that exists and retry the operation"; var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.VALUE, OperationOutcome.IssueSeverity.ERROR, details, diagnostics); + var errorBody = fhirParseService.encodeToJson(operationOutcome); - return new ResponseEntity<>(operationOutcome, HttpStatus.NOT_FOUND); + return new ResponseEntity<>(errorBody, HttpStatus.NOT_FOUND); } if (noErrorsInEhrReceivedAcknowledgement(ehrExtractStatus) && ehrExtractStatus.getError() == null) { - var details = getCodeableConcept("INTERNAL_SERVER_ERROR"); + var details = getCodeableConcept(INTERNAL_SERVER_ERROR); var diagnostics = "The current resend operation is still in progress. Please wait for it to complete before retrying"; var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.BUSINESSRULE, OperationOutcome.IssueSeverity.ERROR, details, diagnostics); - return new ResponseEntity<>(operationOutcome, HttpStatus.FORBIDDEN); + var errorBody = fhirParseService.encodeToJson(operationOutcome); + return new ResponseEntity<>(errorBody, HttpStatus.FORBIDDEN); } LOGGER.info("Creating tasks to start the EHR Extract process resend"); diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseServiceTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseServiceTest.java new file mode 100644 index 0000000000..b344dfcdb8 --- /dev/null +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseServiceTest.java @@ -0,0 +1,64 @@ +package uk.nhs.adaptors.gp2gp.common.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.OperationOutcome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import uk.nhs.adaptors.gp2gp.common.configuration.ObjectMapperBean; +import uk.nhs.adaptors.gp2gp.ehr.EhrResendController; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@ExtendWith(MockitoExtension.class) +class FhirParseServiceTest { + + public static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE"; + private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1"; + private OperationOutcome operationOutcome; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + ObjectMapperBean objectMapperBean = new ObjectMapperBean(); + objectMapper = objectMapperBean.objectMapper(new Jackson2ObjectMapperBuilder()); + + var details = getCodeableConcept(INVALID_IDENTIFIER_VALUE); + var diagnostics = "Provide a conversationId that exists and retry the operation"; + operationOutcome = EhrResendController.createOperationOutcome(OperationOutcome.IssueType.VALUE, + OperationOutcome.IssueSeverity.ERROR, + details, + diagnostics); + } + + @Test + void parseOperationOutcomeTest() throws JsonProcessingException { + FhirParseService fhirParseService = new FhirParseService(); + + String convertedToJsonOperationOutcome = fhirParseService.encodeToJson(operationOutcome); + + JsonNode rootNode = objectMapper.readTree(convertedToJsonOperationOutcome); + String code = + rootNode.path("issue").get(0).path("details").path("coding").get(0).path("code").asText(); + String operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText(); + + assertEquals(INVALID_IDENTIFIER_VALUE, code); + assertEquals(OPERATION_OUTCOME_URL, operationOutcomeUrl); + } + + private static CodeableConcept getCodeableConcept(String codeableConceptCode) { + var details = new CodeableConcept(); + var codeableConceptCoding = new Coding(); + codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1"); + codeableConceptCoding.setCode(codeableConceptCode); + details.setCoding(List.of(codeableConceptCoding)); + return details; + } +} \ No newline at end of file diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendControllerTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendControllerTest.java index e33b72c07e..672595be8a 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendControllerTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/EhrResendControllerTest.java @@ -1,16 +1,22 @@ package uk.nhs.adaptors.gp2gp.ehr; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.UriType; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import uk.nhs.adaptors.gp2gp.common.configuration.ObjectMapperBean; +import uk.nhs.adaptors.gp2gp.common.service.FhirParseService; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.common.service.TimestampService; import uk.nhs.adaptors.gp2gp.common.task.TaskDispatcher; @@ -44,6 +50,11 @@ public class EhrResendControllerTest { private static final String FROM_ASID_CODE = "test-from-asid"; public static final String INCUMBENT_NACK_CODE = "99"; public static final String INCUMBENT_NACK_DISPLAY = "Unexpected condition."; + public static final String INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"; + public static final String GPCONNECT_ERROR_OR_WARNING_CODE = "http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1"; + public static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE"; + + private ObjectMapper objectMapper; @Mock private EhrExtractStatusRepository ehrExtractStatusRepository; @@ -57,11 +68,23 @@ public class EhrResendControllerTest { @Mock private TaskDispatcher taskDispatcher; - @InjectMocks private EhrResendController ehrResendController; + + @BeforeEach + void setUp() { + ObjectMapperBean objectMapperBean = new ObjectMapperBean(); + objectMapper = objectMapperBean.objectMapper(new Jackson2ObjectMapperBuilder()); + FhirParseService fhirParseService = new FhirParseService(); + ehrResendController = new EhrResendController(ehrExtractStatusRepository, + taskDispatcher, + randomIdGeneratorService, + timestampService, + fhirParseService); + } + @Test - public void When_AnEhrExtractHasFailed_Expect_GetGpcStructuredTaskScheduled() { + void When_AnEhrExtractHasFailed_Expect_GetGpcStructuredTaskScheduled() { String ehrMessageRef = generateRandomUppercaseUUID(); var ehrExtractStatus = new EhrExtractStatus(); @@ -105,14 +128,13 @@ public void When_AnEhrExtractHasFailed_Expect_GetGpcStructuredTaskScheduled() { } @Test - public void When_AnEhrExtractHasNotFailedAndAnotherResendRequestArrives_Expect_FailedOperationOutcome() { + void When_AnEhrExtractHasNotFailedAndAnotherResendRequestArrives_Expect_FailedOperationOutcome() throws JsonProcessingException { var details = new CodeableConcept(); var codeableConceptCoding = new Coding(); - codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1"); - codeableConceptCoding.setCode("INTERNAL_SERVER_ERROR"); + codeableConceptCoding.setSystem(GPCONNECT_ERROR_OR_WARNING_CODE); + codeableConceptCoding.setCode(INTERNAL_SERVER_ERROR); details.setCoding(List.of(codeableConceptCoding)); - var diagnostics = "The current resend operation is still in progress. Please wait for it to complete before retrying"; final EhrExtractStatus IN_PROGRESS_EXTRACT_STATUS = EhrExtractStatus.builder() .conversationId(CONVERSATION_ID) @@ -123,20 +145,23 @@ public void When_AnEhrExtractHasNotFailedAndAnotherResendRequestArrives_Expect_F doReturn(Optional.of(IN_PROGRESS_EXTRACT_STATUS)).when(ehrExtractStatusRepository).findByConversationId(CONVERSATION_ID); - var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.BUSINESSRULE, - OperationOutcome.IssueSeverity.ERROR, - details, - diagnostics); var response = ehrResendController.scheduleEhrExtractResend(CONVERSATION_ID); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); + JsonNode rootNode = objectMapper.readTree(response.getBody()); + JsonNode jsonCodingSection = rootNode.path("issue").get(0).path("details").path("coding").get(0); + var code = jsonCodingSection.path("code").asText(); + var system = jsonCodingSection.path("system").asText(); + var operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText(); - assertThat(response.getBody()).usingRecursiveComparison().isEqualTo(operationOutcome); + assertEquals(INTERNAL_SERVER_ERROR, code); + assertEquals(GPCONNECT_ERROR_OR_WARNING_CODE, system); + assertEquals(URI_TYPE, operationOutcomeUrl); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } @Test - public void When_AnEhrExtractHasFailed_Expect_RespondsWith202() { + void When_AnEhrExtractHasFailed_Expect_RespondsWith202() { String ehrMessageRef = generateRandomUppercaseUUID(); var ehrExtractStatus = new EhrExtractStatus(); @@ -164,19 +189,13 @@ public void When_AnEhrExtractHasFailed_Expect_RespondsWith202() { } @Test - public void When_AnEhrExtractHasNotFailed_Expect_RespondsWith403() { + void When_AnEhrExtractHasNotFailed_Expect_RespondsWith403() throws JsonProcessingException { var details = new CodeableConcept(); var codeableConceptCoding = new Coding(); - codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1"); - codeableConceptCoding.setCode("INTERNAL_SERVER_ERROR"); + codeableConceptCoding.setSystem(GPCONNECT_ERROR_OR_WARNING_CODE); + codeableConceptCoding.setCode(INTERNAL_SERVER_ERROR); details.setCoding(List.of(codeableConceptCoding)); - var diagnostics = "The current resend operation is still in progress. Please wait for it to complete before retrying"; - - var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.BUSINESSRULE, - OperationOutcome.IssueSeverity.ERROR, - details, - diagnostics); String ehrMessageRef = generateRandomUppercaseUUID(); var ehrExtractStatus = new EhrExtractStatus(); @@ -195,32 +214,41 @@ public void When_AnEhrExtractHasNotFailed_Expect_RespondsWith403() { var response = ehrResendController.scheduleEhrExtractResend(CONVERSATION_ID); + JsonNode rootNode = objectMapper.readTree(response.getBody()); + JsonNode jsonCodingSection = rootNode.path("issue").get(0).path("details").path("coding").get(0); + var code = jsonCodingSection.path("code").asText(); + var system = jsonCodingSection.path("system").asText(); + var operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText(); + + assertEquals(INTERNAL_SERVER_ERROR, code); + assertEquals(GPCONNECT_ERROR_OR_WARNING_CODE, system); + assertEquals(URI_TYPE, operationOutcomeUrl); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(response.getBody()).usingRecursiveComparison().isEqualTo(operationOutcome); } @Test - public void When_AnEhrExtractDoesNotExist_Expect_RespondsWith404() { + void When_AnEhrExtractDoesNotExist_Expect_RespondsWith404() throws JsonProcessingException { var details = new CodeableConcept(); var codeableConceptCoding = new Coding(); - codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1"); + codeableConceptCoding.setSystem(GPCONNECT_ERROR_OR_WARNING_CODE); codeableConceptCoding.setCode("INVALID_IDENTIFIER_VALUE"); details.setCoding(List.of(codeableConceptCoding)); - var diagnostics = "Provide a conversationId that exists and retry the operation"; - var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.VALUE, - OperationOutcome.IssueSeverity.ERROR, - details, - diagnostics); doReturn(Optional.empty()).when(ehrExtractStatusRepository).findByConversationId(CONVERSATION_ID); - var response = ehrResendController.scheduleEhrExtractResend(CONVERSATION_ID); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + JsonNode rootNode = objectMapper.readTree(response.getBody()); + JsonNode jsonCodingSection = rootNode.path("issue").get(0).path("details").path("coding").get(0); + var code = jsonCodingSection.path("code").asText(); + var system = jsonCodingSection.path("system").asText(); + var operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText(); - assertThat(response.getBody()).usingRecursiveComparison().isEqualTo(operationOutcome); + assertEquals(INVALID_IDENTIFIER_VALUE, code); + assertEquals(GPCONNECT_ERROR_OR_WARNING_CODE, system); + assertEquals(URI_TYPE, operationOutcomeUrl); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); } private String generateRandomUppercaseUUID() {