diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e9ba013..3201d23bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The GP2GP Adaptor now populates the ObservationStatement / confidentialityCode field when the .meta.security field of an Uncategorized Data Observation contains NOPAT * When List.meta.security field contains NOPAT, the GP2GP Adaptor will now populate the CompoundStatement.confidentialityCode * The GP2GP Adaptor now throws an exception when the Access Structure Record is empty, thereby rejecting the transfer +* The GP2GP Adaptor now throws an exception when the XML is not valid, thereby stopping the transfer from going forward ### Fixed * When DiagnosticReport doesn't contain a Specimen or Observation reference, instead of "DUMMY" "NOT-PRESENT" value is used diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/exception/XmlSchemaValidationException.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/exception/XmlSchemaValidationException.java new file mode 100644 index 000000000..b336865d8 --- /dev/null +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/exception/XmlSchemaValidationException.java @@ -0,0 +1,8 @@ +package uk.nhs.adaptors.gp2gp.ehr.exception; + +public class XmlSchemaValidationException extends RuntimeException { + + public XmlSchemaValidationException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java index 5736798a3..cf538dc70 100644 --- a/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java @@ -11,8 +11,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.common.service.TimestampService; +import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException; import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EhrExtractTemplateParameters; import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.SkeletonComponentTemplateParameters; import uk.nhs.adaptors.gp2gp.ehr.utils.DateFormatUtil; @@ -23,6 +25,15 @@ import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import javax.xml.transform.stream.StreamSource; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -31,9 +42,14 @@ @Component @Slf4j public class EhrExtractMapper { + private final RedactionsContext redactionsContext; private static final Mustache EHR_EXTRACT_TEMPLATE = TemplateUtils.loadTemplate("ehr_extract_template.mustache"); private static final Mustache SKELETON_COMPONENT_TEMPLATE = TemplateUtils.loadTemplate("ehr_skeleton_component_template.mustache"); private static final String CONSULTATION_LIST_CODE = "325851000000107"; + private static final String SCHEMA_PATH = "../service/src/test/resources/mim/Schemas/"; + + private static final String RCMR_IN030000UK06_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.NON_REDACTION_INTERACTION_ID + ".xsd"; + private static final String RCMR_IN030000UK07_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.REDACTION_INTERACTION_ID + ".xsd"; private final RandomIdGeneratorService randomIdGeneratorService; private final TimestampService timestampService; @@ -49,6 +65,25 @@ public String mapEhrExtractToXml(EhrExtractTemplateParameters ehrExtractTemplate return TemplateUtils.fillTemplate(EHR_EXTRACT_TEMPLATE, ehrExtractTemplateParameters); } + public void validateXmlAgainstSchema(String xml) { + String interactionId = redactionsContext.ehrExtractInteractionId(); + boolean isRedactionInteraction = RedactionsContext.REDACTION_INTERACTION_ID.equals(interactionId); + + String schemaPath = isRedactionInteraction ? RCMR_IN030000UK07_SCHEMA_PATH : RCMR_IN030000UK06_SCHEMA_PATH; + try { + SchemaFactory factory = SchemaFactory.newDefaultInstance(); + Schema schema = factory.newSchema(new File(schemaPath)); + Validator validator = schema.newValidator(); + + validator.validate(new StreamSource(new StringReader(xml))); + + LOGGER.info("XML successfully validated against schema: {}", schemaPath); + } catch (SAXException | IOException e) { + LOGGER.error("XML validation failed against schema {}: {}", schemaPath, e.getMessage(), e); + throw new XmlSchemaValidationException("XML schema validation failed", e); + } + } + public EhrExtractTemplateParameters mapBundleToEhrFhirExtractParams( GetGpcStructuredTaskDefinition getGpcStructuredTaskDefinition, Bundle bundle) { var ehrExtractTemplateParameters = setSharedExtractParams(getGpcStructuredTaskDefinition); diff --git a/service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java b/service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java index 0a3ec0540..aa794d8c3 100644 --- a/service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java +++ b/service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java @@ -20,6 +20,7 @@ import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.ehr.EhrExtractStatusService; +import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException; import uk.nhs.adaptors.gp2gp.ehr.mapper.EhrExtractMapper; import uk.nhs.adaptors.gp2gp.ehr.mapper.MessageContext; import uk.nhs.adaptors.gp2gp.ehr.mapper.OutputMessageWrapperMapper; @@ -151,6 +152,12 @@ public String mapStructuredRecordToEhrExtractXml(GetGpcStructuredTaskDefinition .mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle); String ehrExtractContent = ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters); + try { + ehrExtractMapper.validateXmlAgainstSchema(ehrExtractContent); + } catch (XmlSchemaValidationException e) { + LOGGER.error("EHR Extract XML validation failed: {}", e.getMessage()); + } + ehrExtractStatusService.saveEhrExtractMessageId(structuredTaskDefinition.getConversationId(), ehrExtractTemplateParameters.getEhrExtractId()); @@ -206,4 +213,4 @@ private static Node removeComponentsFromEhrFolder(Document document) throws XPat return parent; } -} +} \ No newline at end of file diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java index cf33f7003..bdade4435 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java @@ -16,6 +16,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext; import uk.nhs.adaptors.gp2gp.common.service.ConfidentialityService; import uk.nhs.adaptors.gp2gp.common.service.FhirParseService; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; @@ -101,6 +102,8 @@ class EhrExtractMapperComponentTest { private CodeableConceptCdMapper codeableConceptCdMapper; @Mock private ConfidentialityService confidentialityService; + @Mock + private RedactionsContext redactionsContext; private NonConsultationResourceMapper nonConsultationResourceMapper; private EhrExtractMapper ehrExtractMapper; @@ -218,7 +221,7 @@ codeableConceptCdMapper, new ParticipantMapper(), confidentialityService), new BloodPressureValidator() ); - ehrExtractMapper = new EhrExtractMapper(randomIdGeneratorService, + ehrExtractMapper = new EhrExtractMapper(redactionsContext, randomIdGeneratorService, timestampService, new EncounterMapper(messageContext, encounterComponentsMapper, confidentialityService), nonConsultationResourceMapper, diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java index 013cafff5..7b9ce16d5 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java @@ -1,6 +1,7 @@ package uk.nhs.adaptors.gp2gp.ehr.mapper; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -8,12 +9,15 @@ import static org.mockito.Mockito.mock; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; import org.hl7.fhir.dstu3.model.Bundle; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -21,9 +25,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; +import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.common.service.TimestampService; import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException; +import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException; import uk.nhs.adaptors.gp2gp.gpc.GetGpcStructuredTaskDefinition; @ExtendWith(MockitoExtension.class) @@ -52,6 +58,8 @@ class EhrExtractMapperTest { private EhrFolderEffectiveTime ehrFolderEffectiveTime; @InjectMocks private EhrExtractMapper ehrExtractMapper; + @Mock + private RedactionsContext redactionsContext; @Test void When_NhsOverrideNumberProvided_Expect_OverrideToBeUsed() { @@ -155,4 +163,48 @@ void When_BuildEhrCompositionForSkeletonEhrExtract_Expect_ExpectedComponentBuilt assertThat(actual).isEqualTo(expected); } + + @Test + void When_ValidateXmlAgainstSchemaWithInvalidXmlAndAnyId_Expect_XmlSchemaValidationExceptionIsThrown() { + String invalidXml = ""; + + when(redactionsContext.ehrExtractInteractionId()) + .thenReturn(RedactionsContext.REDACTION_INTERACTION_ID); + + XmlSchemaValidationException ex = assertThrows(XmlSchemaValidationException.class, () -> { + ehrExtractMapper.validateXmlAgainstSchema(invalidXml); + }); + + assertThat(ex.getMessage()).contains("XML schema validation failed"); + } + + @Test + void When_ValidateXmlAgainstSchemaWithValidXmlAndRedactionId_Expect_NoExceptionIsThrown() throws Exception { + String basePath = Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath() + + "/../../service/src/test/resources/"; + String xmlFilePath = basePath + "complete-and-validated-xml-test-file-redaction.xml"; + + String validXml = Files.readString(Paths.get(xmlFilePath)); + + when(redactionsContext.ehrExtractInteractionId()) + .thenReturn(RedactionsContext.REDACTION_INTERACTION_ID); + Assertions.assertTrue(validXml.contains("RCMR_IN030000UK07")); + + assertDoesNotThrow(() -> ehrExtractMapper.validateXmlAgainstSchema(validXml)); + } + @Test + void When_ValidateXmlAgainstSchemaWithValidXmlAndNonRedactionId_Expect_NoExceptionIsThrown() throws Exception { + String basePath = Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath() + + "/../../service/src/test/resources/"; + String xmlFilePath = basePath + "complete-and-validated-xml-test-file-non-redaction.xml"; + + String validXml = Files.readString(Paths.get(xmlFilePath)); + + Assertions.assertTrue(validXml.contains("RCMR_IN030000UK06")); + + when(redactionsContext.ehrExtractInteractionId()) + .thenReturn("interaction_id_test"); + + assertDoesNotThrow(() -> ehrExtractMapper.validateXmlAgainstSchema(validXml)); + } } diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingServiceTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingServiceTest.java index 1a990d6ce..d9d2b2d4a 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingServiceTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingServiceTest.java @@ -3,11 +3,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; + import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - import static uk.nhs.adaptors.gp2gp.utils.IdUtil.buildIdType; import java.util.Arrays; @@ -30,6 +31,7 @@ import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration; import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService; import uk.nhs.adaptors.gp2gp.ehr.EhrExtractStatusService; +import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException; import uk.nhs.adaptors.gp2gp.ehr.mapper.EhrExtractMapper; import uk.nhs.adaptors.gp2gp.ehr.mapper.IdMapper; import uk.nhs.adaptors.gp2gp.ehr.mapper.MessageContext; @@ -80,34 +82,34 @@ void setup() { @Test void When_GettingExternalAttachments_Expect_AllDocumentReferenceResourcesAreMapped() { when(randomIdGeneratorService.createNewId()).thenReturn( - NEW_DOC_MANIFEST_ID_1, - NEW_DOC_MANIFEST_ID_2 + NEW_DOC_MANIFEST_ID_1, + NEW_DOC_MANIFEST_ID_2 ); when(randomIdGeneratorService.createNewOrUseExistingUUID(anyString())).thenReturn( - NEW_DOC_MANIFEST_ID_1, - NEW_DOC_MANIFEST_ID_2 + NEW_DOC_MANIFEST_ID_1, + NEW_DOC_MANIFEST_ID_2 ); when(gp2gpConfiguration.getLargeAttachmentThreshold()).thenReturn(LARGE_MESSAGE_THRESHOLD); when(supportedContentTypes.isContentTypeSupported(any())).thenReturn(true); var mappedExternalAttachments = getMappedExternalAttachments( - buildDocumentReference(ID_1, "/" + NEW_DOC_REF_ID_1, null, ATTACHMENT_1_SIZE, "text/plain"), - buildDocumentReference(ID_2, "/" + NEW_DOC_REF_ID_2, null, ATTACHMENT_2_SIZE, "text/html") + buildDocumentReference(ID_1, "/" + NEW_DOC_REF_ID_1, null, ATTACHMENT_1_SIZE, "text/plain"), + buildDocumentReference(ID_2, "/" + NEW_DOC_REF_ID_2, null, ATTACHMENT_2_SIZE, "text/html") ); assertThat(mappedExternalAttachments).usingRecursiveComparison().isEqualTo( - List.of( - buildExternalAttachment( - NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, "/" + NEW_DOC_REF_ID_1, null, - "111_new_doc_manifest_id.txt", "text/plain", - buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) - ), - buildExternalAttachment( - NEW_DOC_MANIFEST_ID_2, NEW_DOC_MANIFEST_ID_2, "/" + NEW_DOC_REF_ID_2, null, - "222_new_doc_manifest_id.html", "text/html", - buildAttachmentDescription(NEW_DOC_MANIFEST_ID_2) + List.of( + buildExternalAttachment( + NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, "/" + NEW_DOC_REF_ID_1, null, + "111_new_doc_manifest_id.txt", "text/plain", + buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) + ), + buildExternalAttachment( + NEW_DOC_MANIFEST_ID_2, NEW_DOC_MANIFEST_ID_2, "/" + NEW_DOC_REF_ID_2, null, + "222_new_doc_manifest_id.html", "text/html", + buildAttachmentDescription(NEW_DOC_MANIFEST_ID_2) + ) ) - ) ); } @@ -118,13 +120,13 @@ void When_GettingExternalAttachment_WithWrongContentType_Expect_AbsentAttachment when(gp2gpConfiguration.getLargeAttachmentThreshold()).thenReturn(LARGE_MESSAGE_THRESHOLD); var mappedExternalAttachments = getMappedAbsentAttachments( - buildDocumentReference(ID_1, "/" + NEW_DOC_REF_ID_1, null, ATTACHMENT_1_SIZE, UNSUPPORTED_CONTENT_TYPE) + buildDocumentReference(ID_1, "/" + NEW_DOC_REF_ID_1, null, ATTACHMENT_1_SIZE, UNSUPPORTED_CONTENT_TYPE) ); assertThat(mappedExternalAttachments).usingRecursiveComparison().isEqualTo(List.of(buildExternalAttachment( - NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, "/" + NEW_DOC_REF_ID_1, null, - "AbsentAttachment111_new_doc_manifest_id.txt", "text/plain", - buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) + NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, "/" + NEW_DOC_REF_ID_1, null, + "AbsentAttachment111_new_doc_manifest_id.txt", "text/plain", + buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) ))); } @@ -135,13 +137,13 @@ void When_GettingExternalAttachment_WithTitleAndNoUrl_Expect_AbsentAttachmentMap when(gp2gpConfiguration.getLargeAttachmentThreshold()).thenReturn(LARGE_MESSAGE_THRESHOLD); var mappedExternalAttachments = getMappedAbsentAttachments( - buildDocumentReference(ID_1, null, "some title", ATTACHMENT_1_SIZE, "text/plain") + buildDocumentReference(ID_1, null, "some title", ATTACHMENT_1_SIZE, "text/plain") ); assertThat(mappedExternalAttachments).usingRecursiveComparison().isEqualTo(List.of(buildExternalAttachment( - NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, null, "some title", - "AbsentAttachment111_new_doc_manifest_id.txt", "text/plain", - buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) + NEW_DOC_MANIFEST_ID_1, NEW_DOC_MANIFEST_ID_1, null, "some title", + "AbsentAttachment111_new_doc_manifest_id.txt", "text/plain", + buildAttachmentDescription(NEW_DOC_MANIFEST_ID_1) ))); } @@ -154,15 +156,16 @@ void When_GettingHL7_Expect_BundleAndStructuredRecordAreMapped() { var ehrExtractTemplateParameters = mock(EhrExtractTemplateParameters.class); when(ehrExtractMapper.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle)) - .thenReturn(ehrExtractTemplateParameters); + .thenReturn(ehrExtractTemplateParameters); when(ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters)).thenReturn(ehrExtractContent); when(outputMessageWrapperMapper.map(structuredTaskDefinition, ehrExtractContent)) - .thenReturn(expectedHL7); + .thenReturn(expectedHL7); var actualHL7 = structuredRecordMappingService.mapStructuredRecordToEhrExtractXml(structuredTaskDefinition, bundle); verify(ehrExtractMapper).mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle); verify(ehrExtractMapper).mapEhrExtractToXml(ehrExtractTemplateParameters); + verify(ehrExtractMapper).mapEhrExtractToXml(ehrExtractTemplateParameters); verify(outputMessageWrapperMapper).map(structuredTaskDefinition, ehrExtractContent); assertThat(actualHL7).isEqualTo(expectedHL7); @@ -196,7 +199,7 @@ private Bundle getBundleWith(DocumentReference... documentReferences) { var bundle = new Bundle().addEntry(new Bundle.BundleEntryComponent().setResource(new Patient())); Arrays.stream(documentReferences).forEach( - documentReference -> bundle.addEntry(new Bundle.BundleEntryComponent().setResource(documentReference)) + documentReference -> bundle.addEntry(new Bundle.BundleEntryComponent().setResource(documentReference)) ); return bundle; } @@ -280,6 +283,40 @@ void When_BuildingSkeletonForEhrExtractWithoutChildComponentNodesToReplace_Expec assertXMLEquals(skeletonEhrExtract, expectedSkeletonEhrExtract); } + @Test + void When_XmlSchemaValidationFails_Expect_ErrorLoggedAndProcessingContinues() { + var structuredTaskDefinition = mock(GetGpcStructuredTaskDefinition.class); + var bundle = mock(Bundle.class); + var ehrExtractTemplateParameters = mock(EhrExtractTemplateParameters.class); + + var ehrExtractContent = "some invalid content"; + var expectedHL7 = "some wrapped hl7 message"; + var conversationId = "conversation-id"; + var ehrExtractId = "ehr-extract-id"; + + when(ehrExtractMapper.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle)) + .thenReturn(ehrExtractTemplateParameters); + when(ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters)).thenReturn(ehrExtractContent); + when(ehrExtractTemplateParameters.getEhrExtractId()).thenReturn(ehrExtractId); + when(structuredTaskDefinition.getConversationId()).thenReturn(conversationId); + when(outputMessageWrapperMapper.map(structuredTaskDefinition, ehrExtractContent)) + .thenReturn(expectedHL7); + + doThrow(new XmlSchemaValidationException("Invalid XML", new RuntimeException("Invalid XML"))) + .when(ehrExtractMapper).validateXmlAgainstSchema(ehrExtractContent); + + var actualHL7 = structuredRecordMappingService.mapStructuredRecordToEhrExtractXml(structuredTaskDefinition, bundle); + + verify(ehrExtractMapper).mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle); + verify(ehrExtractMapper).mapEhrExtractToXml(ehrExtractTemplateParameters); + verify(ehrExtractMapper).validateXmlAgainstSchema(ehrExtractContent); + verify(outputMessageWrapperMapper).map(structuredTaskDefinition, ehrExtractContent); + verify(ehrExtractStatusService).saveEhrExtractMessageId(conversationId, ehrExtractId); + + assertThat(actualHL7).isEqualTo(expectedHL7); + } + + public static void assertXMLEquals(String actualXML, String expectedXML) throws Exception { XMLUnit.setIgnoreWhitespace(true); @@ -293,37 +330,37 @@ private static OutboundMessage.ExternalAttachment buildExternalAttachment(String String filename, String contentType, OutboundMessage.AttachmentDescription description) { return OutboundMessage.ExternalAttachment.builder() - .title(title) - .documentId(documentID) - .messageId(messageID) - .description(description.toString()) - .url(url) - .filename(filename) - .identifier(List.of()) - .contentType(contentType) - .build(); + .title(title) + .documentId(documentID) + .messageId(messageID) + .description(description.toString()) + .url(url) + .filename(filename) + .identifier(List.of()) + .contentType(contentType) + .build(); } private static OutboundMessage.AttachmentDescription buildAttachmentDescription(String documentId) { return OutboundMessage.AttachmentDescription.builder() - .fileName(null) - .contentType(null) - .compressed(false) - .largeAttachment(false) - .originalBase64(false) - .documentId(documentId) - .build(); + .fileName(null) + .contentType(null) + .compressed(false) + .largeAttachment(false) + .originalBase64(false) + .documentId(documentId) + .build(); } private static DocumentReference buildDocumentReference(String id, String attachmentURL, String attachmentTitle, - int size, String contentType) { + int size, String contentType) { var documentReference = new DocumentReference(); documentReference.setId(buildIdType(ResourceType.DocumentReference, id)); documentReference.getContentFirstRep().setAttachment(new Attachment() - .setUrl(attachmentURL) - .setTitle(attachmentTitle) - .setSize(size) - .setContentType(contentType)); + .setUrl(attachmentURL) + .setTitle(attachmentTitle) + .setSize(size) + .setContentType(contentType)); return documentReference; } } diff --git a/service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java b/service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java index 71400dfd5..616f8d0c4 100644 --- a/service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java +++ b/service/src/test/java/uk/nhs/adaptors/gp2gp/uat/EhrExtractUATTest.java @@ -157,7 +157,7 @@ public void setUp() { final NonConsultationResourceMapper nonConsultationResourceMapper = new NonConsultationResourceMapper(messageContext, randomIdGeneratorService, encounterComponentsMapper, new BloodPressureValidator()); - ehrExtractMapper = new EhrExtractMapper(randomIdGeneratorService, timestampService, encounterMapper, + ehrExtractMapper = new EhrExtractMapper(redactionsContext, randomIdGeneratorService, timestampService, encounterMapper, nonConsultationResourceMapper, agentDirectoryMapper, messageContext); lenient().when(confidentialityService.generateConfidentialityCode(any())) .thenReturn(Optional.empty()); diff --git a/service/src/test/resources/complete-and-validated-xml-test-file-non-redaction.xml b/service/src/test/resources/complete-and-validated-xml-test-file-non-redaction.xml new file mode 100644 index 000000000..fcc6db1af --- /dev/null +++ b/service/src/test/resources/complete-and-validated-xml-test-file-non-redaction.xml @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unknown + + + + Spring Hall Group Practice + + + + + + + + + Unknown + + + + Gareth + Roberts + + + + Spring Hall Group Practice + + + 101 Yvonne Falls + West Carey + MK3 7SA + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + Allergy to banana + + + +
+ + + + + + Status: Active Reaction 1 Severity: MILD Manifestation(s): NoInformation Certainty: Unlikely + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + capsule + + + + + Expected Supply Duration: 20 days + + + + + + + Take 2 capsules four times a day - with or after food - oral + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + capsule + + + + + + + + + + Expected Supply Duration: 20 days + + + + + + + Take 2 capsules four times a day - with or after food - oral + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + Checked head + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + Test + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + Implied consent for core Summary Care Record dataset upload + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/service/src/test/resources/complete-and-validated-xml-test-file-redaction.xml b/service/src/test/resources/complete-and-validated-xml-test-file-redaction.xml new file mode 100644 index 000000000..c42848886 --- /dev/null +++ b/service/src/test/resources/complete-and-validated-xml-test-file-redaction.xml @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unknown + + + + Spring Hall Group Practice + + + + + + + + + Unknown + + + + Gareth + Roberts + + + + Spring Hall Group Practice + + + 101 Yvonne Falls + West Carey + MK3 7SA + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + Allergy to banana + + + +
+ + + + + + Status: Active Reaction 1 Severity: MILD Manifestation(s): NoInformation Certainty: Unlikely + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + capsule + + + + + Expected Supply Duration: 20 days + + + + + + + Take 2 capsules four times a day - with or after food - oral + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + capsule + + + + + + + + + + Expected Supply Duration: 20 days + + + + + + + Take 2 capsules four times a day - with or after food - oral + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + Checked head + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + Test + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + Implied consent for core Summary Care Record dataset upload + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file