Skip to content

Commit 12f8e25

Browse files
NIAD-3393: Prevent a duplicate medication request to be pulled as a non-consultation item (#1319)
* small test cleanup * covering code with tests * fixing a test * tests fix * tests fix * refactoring and test fix * checkstyle * refactoring code and tests * changelog * * Update arcmutate plugin * * Downgradle plugin for arc mutate as was updated by dependabot * * Downgradle plugin for arc mutate as was updated by dependabot * * Downgradle plugin for git-plugin as was updated by dependabot * pitest * pitest * pitest * code refactoring * pitest * adding another pitest * indentation --------- Co-authored-by: MartinWheelerMT <[email protected]> Co-authored-by: MartinWheelerMT <[email protected]>
1 parent 75f491d commit 12f8e25

File tree

9 files changed

+8038
-16065
lines changed

9 files changed

+8038
-16065
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
* Prevent duplicate processing: after a medication request is processed, the system no longer pulls a subsequent non‑consultation item based on the same request.
12+
913
### Added
1014
* The GP2GP Adaptor now validates references inside condition objects to test whether they actually exist. Otherwise, the bundle is rejected.
1115
* The GP2GP Adaptor now adds the EhrComposition / confidentialityCode field when Encounter.meta.security contains NOPAT security entry

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hl7.fhir.dstu3.model.DiagnosticReport;
1313
import org.hl7.fhir.dstu3.model.IdType;
1414
import org.hl7.fhir.dstu3.model.ListResource;
15+
import org.hl7.fhir.dstu3.model.MedicationRequest;
1516
import org.hl7.fhir.dstu3.model.Observation;
1617
import org.hl7.fhir.dstu3.model.Reference;
1718
import org.hl7.fhir.dstu3.model.Resource;
@@ -25,6 +26,7 @@
2526
import lombok.RequiredArgsConstructor;
2627
import lombok.extern.slf4j.Slf4j;
2728
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
29+
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrMapperException;
2830
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EncounterTemplateParameters;
2931
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EncounterTemplateParameters.EncounterTemplateParametersBuilder;
3032
import uk.nhs.adaptors.gp2gp.ehr.utils.BloodPressureValidator;
@@ -72,12 +74,12 @@ public class NonConsultationResourceMapper {
7274
);
7375

7476
public List<String> mapRemainingResourcesToEhrCompositions(Bundle bundle) {
75-
var mappedResources = bundle.getEntry()
76-
.stream()
77+
78+
var mappedResources = bundle.getEntry().stream()
7779
.map(Bundle.BundleEntryComponent::getResource)
7880
.filter(this::isMappableNonConsultationResource)
7981
.sorted(this::compareProcessingOrder)
80-
.filter(resource -> !hasIdBeenMapped(resource) && !isIgnoredResource(resource))
82+
.filter(this::shouldMapResource)
8183
.map(this::mapResourceToEhrComposition)
8284
.flatMap(Optional::stream)
8385
.collect(Collectors.toList());
@@ -101,6 +103,32 @@ public List<String> mapRemainingResourcesToEhrCompositions(Bundle bundle) {
101103
return mappedResources;
102104
}
103105

106+
boolean shouldMapResource(Resource resource) {
107+
if (hasIdBeenMapped(resource) || isIgnoredResource(resource)) {
108+
return false;
109+
}
110+
if (resource instanceof MedicationRequest medicationRequest) {
111+
return shouldMapMedicationRequest(medicationRequest);
112+
}
113+
return true;
114+
}
115+
116+
boolean shouldMapMedicationRequest(MedicationRequest medicationRequest) {
117+
if (!medicationRequest.hasBasedOn()) {
118+
return true;
119+
}
120+
121+
String referenceId = medicationRequest.getBasedOn().getFirst().getReference();
122+
123+
try {
124+
Optional<Resource> referencedResource = messageContext.getInputBundleHolder().getResource(new IdType(referenceId));
125+
return referencedResource.isEmpty() || !hasIdBeenMapped(referencedResource.get());
126+
} catch (EhrMapperException e) {
127+
LOGGER.info("MedicationRequest {} cannot be mapped", referenceId);
128+
return true;
129+
}
130+
}
131+
104132
private Resource replaceId(Resource resource) {
105133
resource.setIdElement(new IdType(resource.getResourceType().name(), randomIdGeneratorService.createNewId()));
106134
return resource;

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,23 @@
4141

4242
@ExtendWith(MockitoExtension.class)
4343
@MockitoSettings(strictness = Strictness.LENIENT)
44-
public class EhrExtractMapperComponentTest {
44+
class EhrExtractMapperComponentTest {
45+
4546
private static final String TEST_FILE_DIRECTORY = "/ehr/request/fhir/";
4647
private static final String INPUT_DIRECTORY = "input/";
4748
private static final String OUTPUT_DIRECTORY = "output/";
4849
private static final String INPUT_PATH = TEST_FILE_DIRECTORY + INPUT_DIRECTORY;
4950
private static final String OUTPUT_PATH = TEST_FILE_DIRECTORY + OUTPUT_DIRECTORY;
50-
5151
private static final String JSON_INPUT_FILE = "gpc-access-structured.json";
5252
private static final String JSON_INPUT_FILE_WITH_NOPAT = "gpc-access-structured-with-nopat.json";
5353
private static final String DUPLICATE_RESOURCE_BUNDLE = INPUT_PATH + "duplicated-resource-bundle.json";
5454
private static final String ONE_CONSULTATION_RESOURCE_BUNDLE = INPUT_PATH + "1-consultation-resource.json";
55+
private static final String FHIR_BUNDLE_WITH_DUPLICATED_MEDICATION_REQUESTS = "fhir_bundle_with_duplicated_medication_requests.json";
56+
private static final String EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE = "ExpectedResponseFrom1ConsultationResponse.xml";
57+
58+
private static final String EXPECTED_XML_TO_JSON_FILE = "expected-ehr-extract-response-from-json.xml";
59+
private static final String EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT = "expected-ehr-extract-response-from-json-with-nopat.xml";
60+
5561
private static final String FHIR_BUNDLE_WITHOUT_EFFECTIVE_TIME = "fhir-bundle-without-effective-time.json";
5662
private static final String FHIR_BUNDLE_WITHOUT_HIGH_EFFECTIVE_TIME = "fhir-bundle-without-high-effective-time.json";
5763
private static final String FHIR_BUNDLE_WITH_EFFECTIVE_TIME = "fhir-bundle-with-effective-time.json";
@@ -63,12 +69,7 @@ public class EhrExtractMapperComponentTest {
6369
private static final String FHIR_BUNDLE_WITH_OBSERVATIONS_UNRELATED_TO_DIAGNOSTIC_REPORT =
6470
"fhir-bundle-observations-unrelated-to-diagnostic-report.json";
6571
private static final String FHIR_BUNDLE_WITH_OBSERVATIONS_WITH_RELATED_OBSERVATIONS =
66-
"fhir-bundle-observations-with-related-observations.json";
67-
68-
private static final String EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE = "ExpectedResponseFrom1ConsultationResponse.xml";
69-
70-
private static final String EXPECTED_XML_TO_JSON_FILE = "expected-ehr-extract-response-from-json.xml";
71-
private static final String EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT = "expected-ehr-extract-response-from-json-with-nopat.xml";
72+
"fhir-bundle-observations-with-related-observations.json";
7273
private static final String EXPECTED_XML_WITHOUT_EFFECTIVE_TIME = "expected-xml-without-effective-time.xml";
7374
private static final String EXPECTED_XML_WITHOUT_HIGH_EFFECTIVE_TIME = "expected-xml-without-high-effective-time.xml";
7475
private static final String EXPECTED_XML_WITH_EFFECTIVE_TIME = "expected-xml-with-effective-time.xml";
@@ -231,7 +232,7 @@ public void tearDown() {
231232
}
232233

233234
@Test
234-
public void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStatementWithConfidentialityCode() {
235+
void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStatementWithConfidentialityCode() {
235236

236237
String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT);
237238
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(INPUT_PATH + JSON_INPUT_FILE_WITH_NOPAT);
@@ -259,7 +260,7 @@ public void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStat
259260

260261
@ParameterizedTest
261262
@MethodSource("testData")
262-
public void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String input, String expected) {
263+
void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String input, String expected) {
263264
String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + expected);
264265
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(INPUT_PATH + input);
265266
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
@@ -274,6 +275,20 @@ public void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String inpu
274275
assertThat(output).isEqualToIgnoringWhitespace(expectedJsonToXmlContent);
275276
}
276277

278+
@Test
279+
void When_MappingProperJsonRequestBody_Expect_NonDuplicatedMedicationRequestRemainingResources() {
280+
String inputJsonFileContent =
281+
ResourceTestFileUtils.getFileContent(INPUT_PATH + FHIR_BUNDLE_WITH_DUPLICATED_MEDICATION_REQUESTS);
282+
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
283+
messageContext.initialize(bundle);
284+
285+
EhrExtractTemplateParameters ehrExtractTemplateParameters = ehrExtractMapper.mapBundleToEhrFhirExtractParams(
286+
getGpcStructuredTaskDefinition,
287+
bundle);
288+
289+
assertThat(ehrExtractTemplateParameters.getComponents()).hasSize(2);
290+
}
291+
277292
private static Stream<Arguments> testData() {
278293
return Stream.of(
279294
Arguments.of(JSON_INPUT_FILE, EXPECTED_XML_TO_JSON_FILE),
@@ -288,7 +303,7 @@ private static Stream<Arguments> testData() {
288303
}
289304

290305
@Test
291-
public void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
306+
void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
292307
String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE);
293308
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(ONE_CONSULTATION_RESOURCE_BUNDLE);
294309
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
@@ -302,7 +317,7 @@ public void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
302317
}
303318

304319
@Test
305-
public void When_TransformingResourceToEhrComp_Expect_NoDuplicateMappings() {
320+
void When_TransformingResourceToEhrComp_Expect_NoDuplicateMappings() {
306321
String bundle = ResourceTestFileUtils.getFileContent(DUPLICATE_RESOURCE_BUNDLE);
307322
Bundle parsedBundle = new FhirParseService().parseResource(bundle, Bundle.class);
308323
messageContext.initialize(parsedBundle);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package uk.nhs.adaptors.gp2gp.ehr.mapper;
2+
3+
import org.hl7.fhir.dstu3.model.IdType;
4+
import org.hl7.fhir.dstu3.model.MedicationRequest;
5+
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
6+
import org.hl7.fhir.dstu3.model.Reference;
7+
import org.junit.jupiter.api.AfterEach;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.junit.jupiter.api.extension.ExtendWith;
11+
import org.mockito.InjectMocks;
12+
import org.mockito.Mock;
13+
import org.mockito.MockedStatic;
14+
import org.mockito.Mockito;
15+
import org.mockito.junit.jupiter.MockitoExtension;
16+
import org.mockito.junit.jupiter.MockitoSettings;
17+
import org.mockito.quality.Strictness;
18+
import uk.nhs.adaptors.gp2gp.ehr.utils.IgnoredResourcesUtils;
19+
import org.hl7.fhir.dstu3.model.ResourceType;
20+
import java.util.Optional;
21+
22+
import static org.junit.Assert.assertFalse;
23+
import static org.junit.Assert.assertTrue;
24+
import static org.mockito.ArgumentMatchers.any;
25+
import static org.mockito.Mockito.when;
26+
27+
@ExtendWith(MockitoExtension.class)
28+
@MockitoSettings(strictness = Strictness.LENIENT)
29+
class EhrExtractResourceMapperTest {
30+
31+
@Mock
32+
private MessageContext messageContext;
33+
34+
@Mock
35+
private InputBundle inputBundleHolder;
36+
37+
@Mock
38+
private IdMapper idMapper;
39+
40+
@InjectMocks
41+
private NonConsultationResourceMapper resourceMapper;
42+
43+
@BeforeEach
44+
public void setUp() {
45+
when(messageContext.getInputBundleHolder()).thenReturn(inputBundleHolder);
46+
when(messageContext.getIdMapper()).thenReturn(idMapper);
47+
when(idMapper.hasIdBeenMapped(any(), any())).thenReturn(false);
48+
}
49+
50+
@AfterEach
51+
public void tearDown() {
52+
messageContext.resetMessageContext();
53+
}
54+
55+
@Test
56+
void When_ReferencedResourceIsEmpty_Expect_MapMedicationRequest() {
57+
58+
MedicationRequest medRequest = new MedicationRequest();
59+
medRequest.setId("MedicationRequest/1");
60+
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
61+
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(Optional.empty());
62+
63+
boolean result = resourceMapper.shouldMapResource(medRequest);
64+
65+
assertTrue(result);
66+
}
67+
68+
@Test
69+
void When_ReferencedResourceHasNotBeenMapped_Expect_MapMedicationRequest() {
70+
71+
MedicationRequest medRequest = new MedicationRequest();
72+
medRequest.setId("MedicationRequest/1");
73+
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
74+
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(
75+
Optional.ofNullable(new MedicationRequest().setId("111")));
76+
77+
boolean result = resourceMapper.shouldMapResource(medRequest);
78+
79+
assertTrue(result);
80+
}
81+
82+
@Test
83+
void When_ReferencedResourceHasBeenMapped_Expect_MedicationRequestIsNotMapped() {
84+
85+
MedicationRequest medRequest = new MedicationRequest();
86+
medRequest.setId("MedicationRequest/1");
87+
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
88+
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(Optional.empty());
89+
when(idMapper.hasIdBeenMapped(any(), any())).thenReturn(true);
90+
91+
boolean result = resourceMapper.shouldMapResource(medRequest);
92+
93+
assertFalse(result);
94+
}
95+
96+
@Test
97+
void When_ReferencedResourceHasNoBasedOn_Expect_MedicationRequestShouldBeMapped() {
98+
99+
MedicationRequest medRequest = new MedicationRequest();
100+
medRequest.setId("MedicationRequest/1");
101+
102+
boolean result = resourceMapper.shouldMapResource(medRequest);
103+
104+
assertTrue(result);
105+
}
106+
107+
@Test
108+
void When_ReferencedResourceHasBeenIgnored_Expect_MedicationRequestShouldNotBeMapped() {
109+
QuestionnaireResponse questionnaireResp = new QuestionnaireResponse();
110+
questionnaireResp.setId("MedicationRequest/1");
111+
112+
try (MockedStatic<IgnoredResourcesUtils> ignoredMock = Mockito.mockStatic(IgnoredResourcesUtils.class)) {
113+
114+
ignoredMock.when(() -> IgnoredResourcesUtils.isIgnoredResourceType(ResourceType.QuestionnaireResponse)).thenReturn(true);
115+
116+
boolean result = resourceMapper.shouldMapResource(questionnaireResp);
117+
118+
assertFalse(result);
119+
}
120+
}
121+
122+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,15 @@ public void When_TransformingResourceToEhrComp_Expect_CorrectValuesToBeExtracted
115115
String bundle = ResourceTestFileUtils.getFileContent(inputBundle);
116116
String expectedOutput = ResourceTestFileUtils.getFileContent(output);
117117
Bundle parsedBundle = fhirParseService.parseResource(bundle, Bundle.class);
118+
messageContext.initialize(parsedBundle);
118119

119120
var translatedOutput = nonConsultationResourceMapper.mapRemainingResourcesToEhrCompositions(parsedBundle).get(0);
120121
assertThat(translatedOutput).isEqualToIgnoringWhitespace(expectedOutput);
121122
}
122123

123124
@ParameterizedTest
124125
@MethodSource("endedAllergiesArgs")
125-
public void When_TransformingEndedAllergyListToEhrComp_Expect_CorrectValuesToBeExtracted(String inputBundle, String output) {
126+
void When_TransformingEndedAllergyListToEhrComp_Expect_CorrectValuesToBeExtracted(String inputBundle, String output) {
126127
setupMock(ResourceTestFileUtils.getFileContent(ALLERGY_INTOLERANCE_XML));
127128
String bundle = ResourceTestFileUtils.getFileContent(inputBundle);
128129
String expectedOutput = ResourceTestFileUtils.getFileContent(output);

0 commit comments

Comments
 (0)