Skip to content

Commit bee0fd5

Browse files
NIAD-3142: Validate XML before sending (#1375)
* Fixed the import for MOCK * Test * Tried fixing the pitest * Tried fixing the pitest * Added test to fix pitest * Trying to add assertion to fix pitest * Removed importy * Tweaked test * removed unused import * Added changelog line * * Fix whitespace issues. --------- Co-authored-by: MartinWheelerMT <[email protected]>
1 parent 20233ae commit bee0fd5

File tree

10 files changed

+1087
-54
lines changed

10 files changed

+1087
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
* The GP2GP Adaptor now populates the ObservationStatement / confidentialityCode field when the .meta.security field of an Uncategorized Data Observation contains NOPAT
1717
* When List.meta.security field contains NOPAT, the GP2GP Adaptor will now populate the CompoundStatement.confidentialityCode
1818
* The GP2GP Adaptor now throws an exception when the Access Structure Record is empty, thereby rejecting the transfer
19+
* The GP2GP Adaptor now throws an exception when the XML is not valid, thereby stopping the transfer from going forward
1920

2021
### Fixed
2122
* When DiagnosticReport doesn't contain a Specimen or Observation reference, instead of "DUMMY" "NOT-PRESENT" value is used
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package uk.nhs.adaptors.gp2gp.ehr.exception;
2+
3+
public class XmlSchemaValidationException extends RuntimeException {
4+
5+
public XmlSchemaValidationException(String message, Throwable cause) {
6+
super(message, cause);
7+
}
8+
}

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapper.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
import org.springframework.beans.factory.annotation.Value;
1212
import org.springframework.stereotype.Component;
1313

14+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
1415
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
1516
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
17+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
1618
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EhrExtractTemplateParameters;
1719
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.SkeletonComponentTemplateParameters;
1820
import uk.nhs.adaptors.gp2gp.ehr.utils.DateFormatUtil;
@@ -23,6 +25,15 @@
2325

2426
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException;
2527

28+
import javax.xml.validation.Schema;
29+
import javax.xml.validation.SchemaFactory;
30+
import javax.xml.validation.Validator;
31+
import javax.xml.transform.stream.StreamSource;
32+
import org.xml.sax.SAXException;
33+
34+
import java.io.File;
35+
import java.io.IOException;
36+
import java.io.StringReader;
2637
import java.util.List;
2738
import java.util.Optional;
2839
import java.util.stream.Collectors;
@@ -31,9 +42,14 @@
3142
@Component
3243
@Slf4j
3344
public class EhrExtractMapper {
45+
private final RedactionsContext redactionsContext;
3446
private static final Mustache EHR_EXTRACT_TEMPLATE = TemplateUtils.loadTemplate("ehr_extract_template.mustache");
3547
private static final Mustache SKELETON_COMPONENT_TEMPLATE = TemplateUtils.loadTemplate("ehr_skeleton_component_template.mustache");
3648
private static final String CONSULTATION_LIST_CODE = "325851000000107";
49+
private static final String SCHEMA_PATH = "../service/src/test/resources/mim/Schemas/";
50+
51+
private static final String RCMR_IN030000UK06_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.NON_REDACTION_INTERACTION_ID + ".xsd";
52+
private static final String RCMR_IN030000UK07_SCHEMA_PATH = SCHEMA_PATH + RedactionsContext.REDACTION_INTERACTION_ID + ".xsd";
3753

3854
private final RandomIdGeneratorService randomIdGeneratorService;
3955
private final TimestampService timestampService;
@@ -49,6 +65,25 @@ public String mapEhrExtractToXml(EhrExtractTemplateParameters ehrExtractTemplate
4965
return TemplateUtils.fillTemplate(EHR_EXTRACT_TEMPLATE, ehrExtractTemplateParameters);
5066
}
5167

68+
public void validateXmlAgainstSchema(String xml) {
69+
String interactionId = redactionsContext.ehrExtractInteractionId();
70+
boolean isRedactionInteraction = RedactionsContext.REDACTION_INTERACTION_ID.equals(interactionId);
71+
72+
String schemaPath = isRedactionInteraction ? RCMR_IN030000UK07_SCHEMA_PATH : RCMR_IN030000UK06_SCHEMA_PATH;
73+
try {
74+
SchemaFactory factory = SchemaFactory.newDefaultInstance();
75+
Schema schema = factory.newSchema(new File(schemaPath));
76+
Validator validator = schema.newValidator();
77+
78+
validator.validate(new StreamSource(new StringReader(xml)));
79+
80+
LOGGER.info("XML successfully validated against schema: {}", schemaPath);
81+
} catch (SAXException | IOException e) {
82+
LOGGER.error("XML validation failed against schema {}: {}", schemaPath, e.getMessage(), e);
83+
throw new XmlSchemaValidationException("XML schema validation failed", e);
84+
}
85+
}
86+
5287
public EhrExtractTemplateParameters mapBundleToEhrFhirExtractParams(
5388
GetGpcStructuredTaskDefinition getGpcStructuredTaskDefinition, Bundle bundle) {
5489
var ehrExtractTemplateParameters = setSharedExtractParams(getGpcStructuredTaskDefinition);

service/src/main/java/uk/nhs/adaptors/gp2gp/gpc/StructuredRecordMappingService.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import uk.nhs.adaptors.gp2gp.common.configuration.Gp2gpConfiguration;
2121
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
2222
import uk.nhs.adaptors.gp2gp.ehr.EhrExtractStatusService;
23+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
2324
import uk.nhs.adaptors.gp2gp.ehr.mapper.EhrExtractMapper;
2425
import uk.nhs.adaptors.gp2gp.ehr.mapper.MessageContext;
2526
import uk.nhs.adaptors.gp2gp.ehr.mapper.OutputMessageWrapperMapper;
@@ -151,6 +152,12 @@ public String mapStructuredRecordToEhrExtractXml(GetGpcStructuredTaskDefinition
151152
.mapBundleToEhrFhirExtractParams(structuredTaskDefinition, bundle);
152153
String ehrExtractContent = ehrExtractMapper.mapEhrExtractToXml(ehrExtractTemplateParameters);
153154

155+
try {
156+
ehrExtractMapper.validateXmlAgainstSchema(ehrExtractContent);
157+
} catch (XmlSchemaValidationException e) {
158+
LOGGER.error("EHR Extract XML validation failed: {}", e.getMessage());
159+
}
160+
154161
ehrExtractStatusService.saveEhrExtractMessageId(structuredTaskDefinition.getConversationId(),
155162
ehrExtractTemplateParameters.getEhrExtractId());
156163

@@ -206,4 +213,4 @@ private static Node removeComponentsFromEhrFolder(Document document) throws XPat
206213

207214
return parent;
208215
}
209-
}
216+
}

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperComponentTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.mockito.junit.jupiter.MockitoExtension;
1717
import org.mockito.junit.jupiter.MockitoSettings;
1818
import org.mockito.quality.Strictness;
19+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
1920
import uk.nhs.adaptors.gp2gp.common.service.ConfidentialityService;
2021
import uk.nhs.adaptors.gp2gp.common.service.FhirParseService;
2122
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
@@ -101,6 +102,8 @@ class EhrExtractMapperComponentTest {
101102
private CodeableConceptCdMapper codeableConceptCdMapper;
102103
@Mock
103104
private ConfidentialityService confidentialityService;
105+
@Mock
106+
private RedactionsContext redactionsContext;
104107

105108
private NonConsultationResourceMapper nonConsultationResourceMapper;
106109
private EhrExtractMapper ehrExtractMapper;
@@ -218,7 +221,7 @@ codeableConceptCdMapper, new ParticipantMapper(), confidentialityService),
218221
new BloodPressureValidator()
219222
);
220223

221-
ehrExtractMapper = new EhrExtractMapper(randomIdGeneratorService,
224+
ehrExtractMapper = new EhrExtractMapper(redactionsContext, randomIdGeneratorService,
222225
timestampService,
223226
new EncounterMapper(messageContext, encounterComponentsMapper, confidentialityService),
224227
nonConsultationResourceMapper,

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/EhrExtractMapperTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
package uk.nhs.adaptors.gp2gp.ehr.mapper;
22

33
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
45
import static org.mockito.ArgumentMatchers.any;
56
import static org.mockito.ArgumentMatchers.eq;
67
import static org.junit.jupiter.api.Assertions.assertThrows;
78
import static org.mockito.Mockito.when;
89
import static org.mockito.Mockito.mock;
910

1011

12+
import java.nio.file.Files;
13+
import java.nio.file.Paths;
1114
import java.time.Instant;
1215
import java.time.temporal.ChronoUnit;
1316
import java.util.Arrays;
1417
import java.util.Collections;
1518

1619
import org.hl7.fhir.dstu3.model.Bundle;
20+
import org.junit.jupiter.api.Assertions;
1721
import org.junit.jupiter.api.Test;
1822
import org.junit.jupiter.api.extension.ExtendWith;
1923
import org.mockito.InjectMocks;
2024
import org.mockito.Mock;
2125
import org.mockito.junit.jupiter.MockitoExtension;
2226
import org.springframework.test.util.ReflectionTestUtils;
2327

28+
import uk.nhs.adaptors.gp2gp.common.configuration.RedactionsContext;
2429
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
2530
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
2631
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrValidationException;
32+
import uk.nhs.adaptors.gp2gp.ehr.exception.XmlSchemaValidationException;
2733
import uk.nhs.adaptors.gp2gp.gpc.GetGpcStructuredTaskDefinition;
2834

2935
@ExtendWith(MockitoExtension.class)
@@ -52,6 +58,8 @@ class EhrExtractMapperTest {
5258
private EhrFolderEffectiveTime ehrFolderEffectiveTime;
5359
@InjectMocks
5460
private EhrExtractMapper ehrExtractMapper;
61+
@Mock
62+
private RedactionsContext redactionsContext;
5563

5664
@Test
5765
void When_NhsOverrideNumberProvided_Expect_OverrideToBeUsed() {
@@ -155,4 +163,48 @@ void When_BuildEhrCompositionForSkeletonEhrExtract_Expect_ExpectedComponentBuilt
155163

156164
assertThat(actual).isEqualTo(expected);
157165
}
166+
167+
@Test
168+
void When_ValidateXmlAgainstSchemaWithInvalidXmlAndAnyId_Expect_XmlSchemaValidationExceptionIsThrown() {
169+
String invalidXml = "<invalid><xml>";
170+
171+
when(redactionsContext.ehrExtractInteractionId())
172+
.thenReturn(RedactionsContext.REDACTION_INTERACTION_ID);
173+
174+
XmlSchemaValidationException ex = assertThrows(XmlSchemaValidationException.class, () -> {
175+
ehrExtractMapper.validateXmlAgainstSchema(invalidXml);
176+
});
177+
178+
assertThat(ex.getMessage()).contains("XML schema validation failed");
179+
}
180+
181+
@Test
182+
void When_ValidateXmlAgainstSchemaWithValidXmlAndRedactionId_Expect_NoExceptionIsThrown() throws Exception {
183+
String basePath = Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath()
184+
+ "/../../service/src/test/resources/";
185+
String xmlFilePath = basePath + "complete-and-validated-xml-test-file-redaction.xml";
186+
187+
String validXml = Files.readString(Paths.get(xmlFilePath));
188+
189+
when(redactionsContext.ehrExtractInteractionId())
190+
.thenReturn(RedactionsContext.REDACTION_INTERACTION_ID);
191+
Assertions.assertTrue(validXml.contains("RCMR_IN030000UK07"));
192+
193+
assertDoesNotThrow(() -> ehrExtractMapper.validateXmlAgainstSchema(validXml));
194+
}
195+
@Test
196+
void When_ValidateXmlAgainstSchemaWithValidXmlAndNonRedactionId_Expect_NoExceptionIsThrown() throws Exception {
197+
String basePath = Paths.get("src/").toFile().getAbsoluteFile().getAbsolutePath()
198+
+ "/../../service/src/test/resources/";
199+
String xmlFilePath = basePath + "complete-and-validated-xml-test-file-non-redaction.xml";
200+
201+
String validXml = Files.readString(Paths.get(xmlFilePath));
202+
203+
Assertions.assertTrue(validXml.contains("RCMR_IN030000UK06"));
204+
205+
when(redactionsContext.ehrExtractInteractionId())
206+
.thenReturn("interaction_id_test");
207+
208+
assertDoesNotThrow(() -> ehrExtractMapper.validateXmlAgainstSchema(validXml));
209+
}
158210
}

0 commit comments

Comments
 (0)