Skip to content

Commit

Permalink
Merge pull request #2 from nhsconnect/PRMT-3252
Browse files Browse the repository at this point in the history
PRMT-3252 Ensure health record End to End XML is unchanged for Small EHRs
  • Loading branch information
MohammadIqbalAD-NHS authored May 16, 2023
2 parents 45e51b8 + 292a320 commit 93183a2
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 27 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dependencies {
testImplementation 'org.jfree:jfreechart:1.5.0'
testImplementation 'org.bouncycastle:bcpkix-jdk15on:1.68'
testImplementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'org.xmlunit:xmlunit-core:2.9.1'
testImplementation 'org.xmlunit:xmlunit-matchers:2.9.1'

testCompileOnly 'org.projectlombok:lombok:1.18.24'
testAnnotationProcessor 'org.projectlombok:lombok'
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/uk/nhs/prm/deduction/e2e/TestConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ public String largeEhrQueueUri() {
return getQueueUri("ehr-transfer-service-large-ehr-observability");
}

public String gp2gpMessengerQueueUri() { return getQueueUri("hl7-message-sent-observability"); }

public String getMqUserName() {
return awsConfigurationClient.getParamValue(String.format("/repo/%s/user-input/mq-app-username", getEnvironmentName()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package uk.nhs.prm.deduction.e2e.ehr_transfer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import uk.nhs.prm.deduction.e2e.TestConfiguration;
import uk.nhs.prm.deduction.e2e.queue.QueueMessageHelper;
import uk.nhs.prm.deduction.e2e.queue.ThinlyWrappedSqsClient;

@Component
public class Gp2gpMessengerQueue extends QueueMessageHelper {

@Autowired
public Gp2gpMessengerQueue(ThinlyWrappedSqsClient thinlyWrappedSqsClient, TestConfiguration configuration) {
super(thinlyWrappedSqsClient, configuration.gp2gpMessengerQueueUri());
}
}
180 changes: 156 additions & 24 deletions src/test/java/uk/nhs/prm/deduction/e2e/tests/RepositoryE2ETests.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package uk.nhs.prm.deduction.e2e.tests;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
Expand All @@ -11,6 +14,8 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.*;
import uk.nhs.prm.deduction.e2e.TestConfiguration;
import uk.nhs.prm.deduction.e2e.ehr_transfer.*;
import uk.nhs.prm.deduction.e2e.end_of_transfer_service.EndOfTransferMofUpdatedMessageQueue;
Expand All @@ -20,22 +25,31 @@
import uk.nhs.prm.deduction.e2e.performance.awsauth.AssumeRoleCredentialsProviderFactory;
import uk.nhs.prm.deduction.e2e.performance.awsauth.AutoRefreshingRoleAssumingSqsClient;
import uk.nhs.prm.deduction.e2e.queue.BasicSqsClient;
import uk.nhs.prm.deduction.e2e.queue.SqsMessage;
import uk.nhs.prm.deduction.e2e.queue.ThinlyWrappedSqsClient;
import uk.nhs.prm.deduction.e2e.queue.activemq.ForceXercesParserSoLogbackDoesNotBlowUpWhenUsingSwiftMqClient;
import uk.nhs.prm.deduction.e2e.queue.activemq.SimpleAmqpQueue;
import uk.nhs.prm.deduction.e2e.transfer_tracker_db.TransferTrackerDbClient;
import uk.nhs.prm.deduction.e2e.transfer_tracker_db.TrackerDb;
import uk.nhs.prm.deduction.e2e.transfer_tracker_db.TransferTrackerDbMessage;
import uk.nhs.prm.deduction.e2e.utility.Resources;
import uk.nhs.prm.deduction.e2e.utility.TestUtils;

import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.util.AssertionErrors.assertFalse;
import static uk.nhs.prm.deduction.e2e.nhs.NhsIdentityGenerator.randomNemsMessageId;
import static uk.nhs.prm.deduction.e2e.utility.TestUtils.*;

@SpringBootTest(classes = {
RepositoryE2ETests.class,
Expand All @@ -48,6 +62,7 @@
TrackerDb.class,
SmallEhrQueue.class,
LargeEhrQueue.class,
Gp2gpMessengerQueue.class,
AttachmentQueue.class,
EhrParsingDLQ.class,
TransferTrackerDbClient.class,
Expand All @@ -60,29 +75,50 @@
@ExtendWith(ForceXercesParserSoLogbackDoesNotBlowUpWhenUsingSwiftMqClient.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class RepositoryE2ETests {
private static final Logger LOGGER = LogManager.getLogger(RepositoryE2ETests.class);

private final RepoIncomingQueue repoIncomingQueue;
private final TrackerDb trackerDb;
private final SmallEhrQueue smallEhrQueue;
private final LargeEhrQueue largeEhrQueue;
private final AttachmentQueue attachmentQueue;
private final EhrParsingDLQ parsingDLQ;
private final EhrCompleteQueue ehrCompleteQueue;
private final TransferCompleteQueue transferCompleteQueue;
private final EhrInUnhandledQueue ehrInUnhandledQueue;
private final NegativeAcknowledgementQueue negativeAcknowledgementObservabilityQueue;
private final Gp2gpMessengerQueue gp2gpMessengerQueue;
private final TestConfiguration config;

@Autowired
RepoIncomingQueue repoIncomingQueue;
@Autowired
TrackerDb trackerDb;
@Autowired
SmallEhrQueue smallEhrQueue;
@Autowired
LargeEhrQueue largeEhrQueue;
@Autowired
AttachmentQueue attachmentQueue;
@Autowired
EhrParsingDLQ parsingDLQ;
@Autowired
EhrCompleteQueue ehrCompleteQueue;
@Autowired
TransferCompleteQueue transferCompleteQueue;
@Autowired
EhrInUnhandledQueue ehrInUnhandledQueue;
@Autowired
NegativeAcknowledgementQueue negativeAcknowledgementObservabilityQueue;
@Autowired
TestConfiguration config;
public RepositoryE2ETests(
RepoIncomingQueue repoIncomingQueue,
TrackerDb trackerDb,
SmallEhrQueue smallEhrQueue,
LargeEhrQueue largeEhrQueue,
AttachmentQueue attachmentQueue,
EhrParsingDLQ parsingDLQ,
EhrCompleteQueue ehrCompleteQueue,
TransferCompleteQueue transferCompleteQueue,
EhrInUnhandledQueue ehrInUnhandledQueue,
NegativeAcknowledgementQueue negativeAcknowledgementObservabilityQueue,
Gp2gpMessengerQueue gp2gpMessengerQueue,
TestConfiguration config

) {
this.repoIncomingQueue = repoIncomingQueue;
this.trackerDb = trackerDb;
this.smallEhrQueue = smallEhrQueue;
this.largeEhrQueue = largeEhrQueue;
this.attachmentQueue = attachmentQueue;
this.parsingDLQ = parsingDLQ;
this.ehrCompleteQueue = ehrCompleteQueue;
this.transferCompleteQueue = transferCompleteQueue;
this.ehrInUnhandledQueue = ehrInUnhandledQueue;
this.negativeAcknowledgementObservabilityQueue = negativeAcknowledgementObservabilityQueue;
this.gp2gpMessengerQueue = gp2gpMessengerQueue;
this.config = config;
}

PdsAdaptorClient pdsAdaptorClient;

Expand All @@ -101,7 +137,7 @@ void init() {
// The following test should eventually test that we can send a small EHR - until we have an EHR in repo/test patient ready to send,
// we are temporarily doing a smaller test to cover from amqp -> ehr out queue
@Test
@EnabledIfEnvironmentVariable(named = "NHS_ENVIRONMENT", matches = "dev" )
@EnabledIfEnvironmentVariable(named = "NHS_ENVIRONMENT", matches = "dev")
void shouldIdentifyEhrRequestAsEhrOutMessage() {
var ehrRequest = Resources.readTestResourceFile("RCMR_IN010000UK05");
var inboundQueueFromMhs = new SimpleAmqpQueue(config);
Expand All @@ -112,6 +148,101 @@ void shouldIdentifyEhrRequestAsEhrOutMessage() {
assertThat(ehrInUnhandledQueue.getMessageContaining(ehrRequest)).isNotNull();
}

@Test
void shouldVerifyThatASmallEhrXMLIsUnchanged() {
// Given
String inboundConversationId = UUID.randomUUID().toString();
String smallEhrMessageId = UUID.randomUUID().toString();
String outboundConversationId = UUID.randomUUID().toString();
String nhsNumberForTestPatient = "9727018440";
String sourceGpForTestPatient = "M85019";
String asidCodeForTestPatient = "200000000149";
String timeNow = ZonedDateTime.now(ZoneOffset.ofHours(0)).toString();

SimpleAmqpQueue inboundQueueFromMhs = new SimpleAmqpQueue(config);

String smallEhr = getSmallEhrWithoutLinebreaks(inboundConversationId.toUpperCase(), smallEhrMessageId);

String ehrRequest = getEhrRequest(nhsNumberForTestPatient, sourceGpForTestPatient, asidCodeForTestPatient, outboundConversationId);

// When
// change transfer db status to ACTION:EHR_REQUEST_SENT before putting on inbound queue
// Put the patient into inboundQueueFromMhs as a UK05 message

trackerDb.save(new TransferTrackerDbMessage(
inboundConversationId,
"",
randomNemsMessageId(),
nhsNumberForTestPatient,
sourceGpForTestPatient,
"ACTION:EHR_REQUEST_SENT",
timeNow,
timeNow,
timeNow
));

inboundQueueFromMhs.sendMessage(smallEhr, inboundConversationId);
LOGGER.info("conversationIdExists: {}",trackerDb.conversationIdExists(inboundConversationId));
String status = trackerDb.waitForStatusMatching(inboundConversationId, "ACTION:EHR_TRANSFER_TO_REPO_COMPLETE");
LOGGER.info("tracker db status: {}", status);

// Put a EHR request to inboundQueueFromMhs
inboundQueueFromMhs.sendMessage(ehrRequest, outboundConversationId);

// Then
// assert gp2gpMessenger queue got a message of UK06
SqsMessage gp2gpMessage = gp2gpMessengerQueue.getMessageContaining(outboundConversationId);

assertThat(gp2gpMessage).isNotNull();
assertThat(gp2gpMessage.contains("RCMR_IN030000UK06")).isTrue();

try {
String gp2gpMessengerPayload = getPayload(gp2gpMessage.body());
String smallEhrPayload = getPayload(smallEhr);

LOGGER.info("Payload from gp2gpMessenger: {}", gp2gpMessengerPayload);
LOGGER.info("Payload from smallEhr: {}", smallEhrPayload);
Diff myDiff = DiffBuilder.compare(gp2gpMessengerPayload).withTest(smallEhrPayload)
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName))
.withNodeFilter(TestUtils::excludeComparisons)
.withDifferenceEvaluator(DifferenceEvaluators.chain(DifferenceEvaluators.Default,
DifferenceEvaluators.downgradeDifferencesToEqual(ComparisonType.XML_STANDALONE)))
.checkForSimilar().build();

assertFalse(myDiff.toString(), myDiff.hasDifferences());

} catch (JSONException exception) {
LOGGER.error(exception);
throw new Error(exception);
}
}

@Test
public void given2XMLsWithDifferences_whenTestsSimilarWithDifferenceEvaluator_thenCorrect() {
// helper test case for building the `excludeComparisons` function
// final String control = "<agentOrganizationSDS classCode=\"ORG\" determinerCode=\"INSTANCE\"><id root=\"1.2.826.0.1285.0.1.10\" extension=\"B85002\"/></agentOrganizationSDS>";
// final String test = "<agentOrganizationSDS classCode=\"ORG\" determinerCode=\"INSTANCE\"><id root=\"1.2.826.0.1285.0.1.10\" extension=\"M85019\"/></agentOrganizationSDS>";
// final String control = "<device classCode=\"DEV\" determinerCode=\"INSTANCE\"><id root=\"1.2.826.0.1285.0.2.0.107\" extension=\"200000001613\"/></device>";
// final String test = "<device classCode=\"DEV\" determinerCode=\"INSTANCE\"><id root=\"1.2.826.0.1285.0.2.0.107\" extension=\"200000000149\"/></device>";
// final String control = "<id root=\"DF91D420-DDC7-11ED-808B-AC162D1F16F0\"/>";
// final String test = "<id root=\"DFBA6AC0-DDC7-11ED-808B-AC162D1F16F0\"/>";
final String control = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><RCMR_IN030000UK06 xmlns=\"urn:hl7-org:v3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:hl7-org:v3 ..\\Schemas\\RCMR_IN030000UK06.xsd\"></RCMR_IN030000UK06>";
final String test = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><RCMR_IN030000UK06 xmlns=\"urn:hl7-org:v3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:hl7-org:v3 ..\\Schemas\\RCMR_IN030000UK06.xsd\"></RCMR_IN030000UK06>";


Diff myDiff = DiffBuilder.compare(control).withTest(test)
.withNodeFilter(TestUtils::excludeComparisons)
.withDifferenceEvaluator(DifferenceEvaluators.chain(DifferenceEvaluators.Default,
DifferenceEvaluators.downgradeDifferencesToEqual(ComparisonType.XML_STANDALONE)))
.checkForSimilar().build();

assertFalse(myDiff.toString(), myDiff.hasDifferences());
}

@Test
void shouldVerifyThatALargeEhrXMLIsUnchanged() {
}

@Test
void shouldReceivingAndTrackAllLargeEhrFragments_DevAndTest() {
var largeEhrAtEmisWithRepoMof = Patient.largeEhrAtEmisWithRepoMof(config);
Expand All @@ -125,7 +256,7 @@ void shouldReceivingAndTrackAllLargeEhrFragments_DevAndTest() {
.build();

repoIncomingQueue.send(triggerMessage);
assertThat(ehrCompleteQueue.getMessageContaining(triggerMessage.conversationId()));
assertThat(ehrCompleteQueue.getMessageContaining(triggerMessage.conversationId())).isNotNull();
assertTrue(trackerDb.statusForConversationIdIs(triggerMessage.conversationId(), "ACTION:EHR_TRANSFER_TO_REPO_COMPLETE"));
}

Expand Down Expand Up @@ -265,7 +396,7 @@ private void checkThatTransfersHaveCompletedSuccessfully(ArrayList<String> conve
long timeElapsed = Duration.between(timeLastRequestSent, finishedAt).toSeconds();
System.out.println("Total time taken for: " + conversationId + " in seconds was no more than : " + timeElapsed);

assertTrue(trackerDb.statusForConversationIdIs(conversationId.toString(), "ACTION:EHR_TRANSFER_TO_REPO_COMPLETE"));
assertTrue(trackerDb.statusForConversationIdIs(conversationId, "ACTION:EHR_TRANSFER_TO_REPO_COMPLETE"));
}
}

Expand Down Expand Up @@ -306,6 +437,7 @@ void shouldUpdateDbStatusAndPublishToTransferCompleteQueueWhenReceivedNackFromGp
assertThat(negativeAcknowledgementObservabilityQueue.getMessageContaining(triggerMessage.conversationId()));
assertThat(transferCompleteQueue.getMessageContainingAttribute("conversationId", triggerMessage.conversationId()));


var status = trackerDb.waitForStatusMatching(triggerMessage.conversationId(), "ACTION:EHR_TRANSFER_FAILED");
assertThat(status).isEqualTo("ACTION:EHR_TRANSFER_FAILED:" + REQUESTER_NOT_REGISTERED_PRACTICE_FOR_PATIENT_CODE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,8 @@ public String waitForStatusMatching(String conversationId, String partialStatus)
.pollInterval(2, TimeUnit.SECONDS)
.until(() -> transferTrackerDbClient.queryDbWithConversationId(conversationId).item().get("state").s(), containsString(partialStatus));
}

public void save(TransferTrackerDbMessage message) {
transferTrackerDbClient.save(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.*;
import uk.nhs.prm.deduction.e2e.TestConfiguration;

import java.util.HashMap;
Expand All @@ -28,4 +26,26 @@ public GetItemResponse queryDbWithConversationId(String conversationId) {
System.out.println("query response is: " + getItemResponse);
return getItemResponse;
}

public void save(TransferTrackerDbMessage transferTrackerDbMessage) {
Map<String, AttributeValue> item = new HashMap<>();

item.put("conversation_id", AttributeValue.builder().s(transferTrackerDbMessage.getConversationId()).build());
item.put("large_ehr_core_message_id", AttributeValue.builder().s(transferTrackerDbMessage.getLargeEhrCoreMessageId()).build());
item.put("nems_message_id", AttributeValue.builder().s(transferTrackerDbMessage.getNemsMessageId()).build());
item.put("nhs_number", AttributeValue.builder().s(transferTrackerDbMessage.getNhsNumber()).build());
item.put("source_gp", AttributeValue.builder().s(transferTrackerDbMessage.getSourceGp()).build());
item.put("state", AttributeValue.builder().s(transferTrackerDbMessage.getState()).build());
item.put("nems_event_last_updated", AttributeValue.builder().s(transferTrackerDbMessage.getNemsEventLastUpdated()).build());
item.put("created_at", AttributeValue.builder().s(transferTrackerDbMessage.getCreatedAt()).build());
item.put("last_updated_at", AttributeValue.builder().s(transferTrackerDbMessage.getLastUpdatedAt()).build());


DynamoDbClient.builder().build().putItem(PutItemRequest.builder()
.tableName(testConfiguration.getTransferTrackerDb())
.item(item)
.build()
);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package uk.nhs.prm.deduction.e2e.transfer_tracker_db;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.GsonBuilder;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Builder(toBuilder = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class TransferTrackerDbMessage {

private String conversationId;
private String largeEhrCoreMessageId;
private String nemsMessageId;
private String nhsNumber;
private String sourceGp;
private String state;
private String nemsEventLastUpdated;
private String createdAt;
private String lastUpdatedAt;

public TransferTrackerDbMessage(String conversationId,
String largeEhrCoreMessageId,
String nemsMessageId,
String nhsNumber,
String sourceGp,
String state,
String nemsEventLastUpdated,
String createdAt,
String lastUpdatedAt){
this.conversationId = conversationId;
this.largeEhrCoreMessageId = largeEhrCoreMessageId;
this.nemsMessageId = nemsMessageId;
this.nhsNumber = nhsNumber;
this.sourceGp = sourceGp;
this.state = state;
this.nemsEventLastUpdated = nemsEventLastUpdated;
this.createdAt = createdAt;
this.lastUpdatedAt = lastUpdatedAt;
}

public String toJsonString() {
return new GsonBuilder().disableHtmlEscaping().create().toJson(this);
}
}
Loading

0 comments on commit 93183a2

Please sign in to comment.