Skip to content

Commit

Permalink
converting operationOutcome into json before sending it in ResponseEn…
Browse files Browse the repository at this point in the history
…tity
  • Loading branch information
ole4ryb committed Nov 21, 2024
1 parent 5838168 commit af7598e
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

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;
import uk.nhs.adaptors.gp2gp.common.exception.FhirValidationException;

@Service
public class FhirParseService {

private final IParser jsonParser = prepareParser();

public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass) {
Expand All @@ -20,6 +20,13 @@ public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass
}
}

public String encodeToJson(IBaseResource resource) {
IParser jsonParser = prepareParser();
return jsonParser
.setPrettyPrint(true)

Check warning on line 26 in service/src/main/java/uk/nhs/adaptors/gp2gp/common/service/FhirParseService.java

View workflow job for this annotation

GitHub Actions / pitest

A change can be made to line 26 without causing a test to fail

removed call to setPrettyPrint (covered by 4 tests RemoveChainedCallsMutator)
.encodeResourceToString(resource);
}

private IParser prepareParser() {
FhirContext ctx = FhirContext.forDstu3();
ctx.newJsonParser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<OperationOutcome> scheduleEhrExtractResend(@PathVariable String conversationId) {
public ResponseEntity<String> 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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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() {
Expand Down

0 comments on commit af7598e

Please sign in to comment.