Skip to content

Commit

Permalink
Adding failure reason updates (#789)
Browse files Browse the repository at this point in the history
* Adding failure reason updates

* Addressing PR comments

* Re-adding test
  • Loading branch information
jcrichlake authored Jan 18, 2024
1 parent ee6c930 commit 8472171
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataStatus;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.etor.operationoutcomes.FhirMetadata;
import gov.hhs.cdc.trustedintermediary.etor.orders.Order;
Expand Down Expand Up @@ -157,22 +156,25 @@ DomainResponse handleOrders(DomainRequest request) {
}

var markMetadataAsFailed = false;
String errorMessage = "";
try {
orders = orderController.parseOrders(request);
sendOrderUseCase.convertAndSend(orders, receivedSubmissionId);
} catch (FhirParseException e) {
logger.logError("Unable to parse order request", e);
errorMessage = "Unable to parse order request";
logger.logError(errorMessage, e);
markMetadataAsFailed = true;
return domainResponseHelper.constructErrorResponse(400, e);
} catch (UnableToSendOrderException e) {
logger.logError("Unable to send order", e);
errorMessage = "Unable to send order";
logger.logError(errorMessage, e);
markMetadataAsFailed = true;
return domainResponseHelper.constructErrorResponse(400, e);
} finally {
if (markMetadataAsFailed) {
try {
partnerMetadataOrchestrator.setMetadataStatus(
receivedSubmissionId, PartnerMetadataStatus.FAILED);
partnerMetadataOrchestrator.setMetadataStatusToFailed(
receivedSubmissionId, errorMessage);
} catch (PartnerMetadataException innerE) {
logger.logError("Unable to update metadata status", innerE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,16 @@ public PartnerMetadata withDeliveryStatus(PartnerMetadataStatus deliveryStatus)
deliveryStatus,
this.failureReason);
}

public PartnerMetadata withFailureMessage(String failureMessage) {
return new PartnerMetadata(
this.receivedSubmissionId,
this.sentSubmissionId,
this.sender,
this.receiver,
this.timeReceived,
this.hash,
this.deliveryStatus,
failureMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,15 @@ public Optional<PartnerMetadata> getMetadata(String receivedSubmissionId)

String receiver;
String rsStatus;
String rsMessage = "";
try {
String bearerToken = rsclient.getRsToken();
String responseBody =
rsclient.requestHistoryEndpoint(sentSubmissionId, bearerToken);
var parsedResponseBody = getReceiverAndStatus(responseBody);
var parsedResponseBody = getDataFromReportStream(responseBody);
receiver = parsedResponseBody[0];
rsStatus = parsedResponseBody[1];
rsMessage = parsedResponseBody[2];
} catch (ReportStreamEndpointClientException | FormatterProcessingException e) {
throw new PartnerMetadataException(
"Unable to retrieve metadata from RS history API", e);
Expand All @@ -142,14 +144,18 @@ public Optional<PartnerMetadata> getMetadata(String receivedSubmissionId)
var ourStatus = ourStatusFromReportStreamStatus(rsStatus);

logger.logInfo("Updating metadata with receiver {} and status {}", receiver, ourStatus);
partnerMetadata = partnerMetadata.withReceiver(receiver).withDeliveryStatus(ourStatus);
partnerMetadata =
partnerMetadata
.withReceiver(receiver)
.withDeliveryStatus(ourStatus)
.withFailureMessage(rsMessage);
partnerMetadataStorage.saveMetadata(partnerMetadata);
}

return Optional.of(partnerMetadata);
}

public void setMetadataStatus(String submissionId, PartnerMetadataStatus metadataStatus)
public void setMetadataStatusToFailed(String submissionId, String errorMessage)
throws PartnerMetadataException {
if (submissionId == null) {
return;
Expand All @@ -160,23 +166,26 @@ public void setMetadataStatus(String submissionId, PartnerMetadataStatus metadat
PartnerMetadata partnerMetadata;
if (optionalPartnerMetadata.isEmpty()) {
// there wasn't any metadata given the submission ID, so make one with the status
partnerMetadata = new PartnerMetadata(submissionId, metadataStatus);
partnerMetadata = new PartnerMetadata(submissionId, PartnerMetadataStatus.FAILED);
} else {
partnerMetadata = optionalPartnerMetadata.get();
if (partnerMetadata.deliveryStatus().equals(metadataStatus)) {
if (partnerMetadata.deliveryStatus().equals(PartnerMetadataStatus.FAILED)) {
return;
}
}

logger.logInfo(
"Updating metadata delivery status {} with submissionId: {}",
metadataStatus,
PartnerMetadataStatus.FAILED,
submissionId);
partnerMetadata = partnerMetadata.withDeliveryStatus(metadataStatus);
partnerMetadata =
partnerMetadata
.withDeliveryStatus(PartnerMetadataStatus.FAILED)
.withFailureMessage(errorMessage);
partnerMetadataStorage.saveMetadata(partnerMetadata);
}

String[] getReceiverAndStatus(String responseBody) throws FormatterProcessingException {
String[] getDataFromReportStream(String responseBody) throws FormatterProcessingException {
// the expected json structure for the response is:
// {
// ...
Expand Down Expand Up @@ -217,7 +226,21 @@ String[] getReceiverAndStatus(String responseBody) throws FormatterProcessingExc
"Unable to extract overallStatus from response due to unexpected format", e);
}

return new String[] {receiver, overallStatus};
StringBuilder errorMessages = new StringBuilder();
if ("Error".equalsIgnoreCase(overallStatus)) {
try {
ArrayList<?> errors = (ArrayList<?>) responseObject.get("errors");
for (Object error : errors) {
Map<?, ?> x = (Map<?, ?>) error;
errorMessages.append(x.get("message").toString()).append(" / ");
}
} catch (Exception e) {
throw new FormatterProcessingException(
"Unable to extract failure reason due to unexpected format", e);
}
}

return new String[] {receiver, overallStatus, errorMessages.toString()};
}

PartnerMetadataStatus ourStatusFromReportStreamStatus(String rsStatus) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ public FhirMetadata<?> extractPublicMetadataToOperationOutcome(PartnerMetadata m
createInformationIssueComponent(
"delivery status", metadata.deliveryStatus().toString()));

operation
.getIssue()
.add(createInformationIssueComponent("status message", metadata.failureReason()));

return new HapiFhirMetadata(operation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class EtorDomainRegistrationTest extends Specification {
TestApplicationContext.register(DomainResponseHelper, mockResponseHelper)

def mockPartnerMetadataOrchestrator = Mock(PartnerMetadataOrchestrator)
mockPartnerMetadataOrchestrator.setMetadataStatus(_, PartnerMetadataStatus.FAILED) >> {
mockPartnerMetadataOrchestrator.setMetadataStatusToFailed(_, PartnerMetadataStatus.FAILED) >> {
throw new PartnerMetadataException("error")
}
TestApplicationContext.register(PartnerMetadataOrchestrator,mockPartnerMetadataOrchestrator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ package gov.hhs.cdc.trustedintermediary.etor.metadata.partner

import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext
import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataOrchestrator
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataStatus
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataStorage
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter
import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson
Expand Down Expand Up @@ -251,8 +246,8 @@ class PartnerMetadataOrchestratorTest extends Specification {
def hashCode = "123"
def bearerToken = "token"
def rsHistoryApiResponse = "{\"destinations\": [{\"organization_id\": \"org\", \"service\": \"service\"}]}"
def missingReceiverMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, null, timestamp, hashCode, PartnerMetadataStatus.DELIVERED, null)
def expectedMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, "org.service", timestamp, hashCode, PartnerMetadataStatus.DELIVERED, null)
def missingReceiverMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, null, timestamp, hashCode, PartnerMetadataStatus.DELIVERED, "")
def expectedMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, "org.service", timestamp, hashCode, PartnerMetadataStatus.DELIVERED, "")

mockClient.getRsToken() >> bearerToken
mockClient.requestHistoryEndpoint(sentSubmissionId, bearerToken) >> rsHistoryApiResponse
Expand Down Expand Up @@ -283,7 +278,7 @@ class PartnerMetadataOrchestratorTest extends Specification {
def bearerToken = "token"
def rsHistoryApiResponse = "{\"destinations\": [{\"organization_id\": \"org\", \"service\": \"service\"}]}"
def missingReceiverMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, receiver, timestamp, hashCode, PartnerMetadataStatus.PENDING, null)
def expectedMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, receiver, timestamp, hashCode, PartnerMetadataStatus.FAILED, null)
def expectedMetadata = new PartnerMetadata(receivedSubmissionId, sentSubmissionId, sender, receiver, timestamp, hashCode, PartnerMetadataStatus.FAILED, "")

mockClient.getRsToken() >> bearerToken
mockClient.requestHistoryEndpoint(sentSubmissionId, bearerToken) >> rsHistoryApiResponse
Expand All @@ -303,39 +298,31 @@ class PartnerMetadataOrchestratorTest extends Specification {
1 * mockPartnerMetadataStorage.saveMetadata(expectedMetadata)
}

def "setMetadataStatus sets status to Pending"(){
def "setMetadataStatusToFailed sets status to Failed"(){
given:
def submissionId = "13425"
def metadataStatus = PartnerMetadataStatus.PENDING
def optional = Optional.of(new PartnerMetadata("","","","",Instant.now(),"",PartnerMetadataStatus.FAILED, null))
def metadataStatus = PartnerMetadataStatus.FAILED
def optional = Optional.of(new PartnerMetadata("","","","",Instant.now(),"",PartnerMetadataStatus.PENDING, "Bad Message"))
mockPartnerMetadataStorage.readMetadata(submissionId) >> optional

when:
PartnerMetadataOrchestrator.getInstance().setMetadataStatus(submissionId,metadataStatus)
PartnerMetadataOrchestrator.getInstance().setMetadataStatusToFailed(submissionId, "Bad Message")

then:
1 * mockPartnerMetadataStorage.saveMetadata(_ as PartnerMetadata) >> { PartnerMetadata partnerMetadata ->
assert partnerMetadata.deliveryStatus() == metadataStatus
}
}

def "setMetadataStatus doesn't update status if status is the same"(){
def "setMetadataStatusToFailed doesn't update status if status is the same"(){
given:
def submissionId = "13425"
def metadataStatus = PartnerMetadataStatus.PENDING
def metadataStatus = PartnerMetadataStatus.FAILED
def optional = Optional.of(new PartnerMetadata("","","","",Instant.now(),"",metadataStatus, null))
mockPartnerMetadataStorage.readMetadata(submissionId) >> optional

when:
PartnerMetadataOrchestrator.getInstance().setMetadataStatus(submissionId,metadataStatus)

then:
0 * mockPartnerMetadataStorage.saveMetadata(_ as PartnerMetadata)
}

def "setMetadataStatus doesn't update when submissionId is null"(){
when:
PartnerMetadataOrchestrator.getInstance().setMetadataStatus(null,PartnerMetadataStatus.DELIVERED)
PartnerMetadataOrchestrator.getInstance().setMetadataStatusToFailed(submissionId, null)

then:
0 * mockPartnerMetadataStorage.saveMetadata(_ as PartnerMetadata)
Expand All @@ -344,90 +331,107 @@ class PartnerMetadataOrchestratorTest extends Specification {
def "setMetadataStatus sets status to Pending when there is no metadata"(){
given:
def submissionId = "13425"
def metadataStatus = PartnerMetadataStatus.DELIVERED
def optional = Optional.empty()
mockPartnerMetadataStorage.readMetadata(submissionId) >> optional

when:
PartnerMetadataOrchestrator.getInstance().setMetadataStatus(submissionId,metadataStatus)
PartnerMetadataOrchestrator.getInstance().setMetadataStatusToFailed(submissionId, "Failure")

then:
1 * mockPartnerMetadataStorage.saveMetadata(_ as PartnerMetadata) >> { PartnerMetadata partnerMetadata ->
assert partnerMetadata.deliveryStatus() == metadataStatus
assert partnerMetadata.deliveryStatus() == PartnerMetadataStatus.FAILED
assert partnerMetadata.receivedSubmissionId() == submissionId
}
}

def "getReceiverAndStatus returns correct status name and receiver name from valid JSON response"() {
def "setMetadataStatus doesn't update when submissionId is null"(){
when:
PartnerMetadataOrchestrator.getInstance().setMetadataStatusToFailed(null, null)

then:
0 * mockPartnerMetadataStorage.saveMetadata(_ as PartnerMetadata)
}


def "getDataFromReportStream returns correct status name and receiver name from valid JSON response"() {
given:
def organization = "org_id"
def sender = "service_name"
def status = "Not Delivering"
def validJson = """{"overallStatus": "${status}", "destinations": [{"organization_id": "${organization}", "service": "${sender}"}]}"""
def status = "Error"
def errorMessage = "Bad message"
def validJson = """{"overallStatus": "${status}", "destinations": [{"organization_id": "${organization}", "service": "${sender}"}], "errors": [{"message": "${errorMessage}" }]}"""

TestApplicationContext.register(Formatter, Jackson.getInstance())
TestApplicationContext.injectRegisteredImplementations()

when:
def parsedResponse = PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(validJson)
def parsedResponse = PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(validJson)

then:
parsedResponse[0] == "${organization}.${sender}"
parsedResponse[1] == status
parsedResponse[2].contains(errorMessage)
}

def "getReceiverAndStatus throws FormatterProcessingException or returns null for unexpected format response"() {
def "getDataFromReportStream throws FormatterProcessingException or returns null for unexpected format response"() {
given:
TestApplicationContext.register(Formatter, Jackson.getInstance())
TestApplicationContext.injectRegisteredImplementations()

when:
def invalidJson = "invalid JSON"
PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(invalidJson)
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(invalidJson)

then:
thrown(FormatterProcessingException)

when:
def emptyJson = "{}"
PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(emptyJson)
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(emptyJson)

then:
thrown(FormatterProcessingException)

when:
def jsonWithoutDestinations = "{\"someotherkey\": \"value\"}"
PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(jsonWithoutDestinations)
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithoutDestinations)

then:
thrown(FormatterProcessingException)

when:

def jsonWithEmptyDestinations = "{\"destinations\": []}"
def parsedData = PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(jsonWithEmptyDestinations)
def parsedData = PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithEmptyDestinations)

then:
parsedData[0] == null

when:

def jsonWithNoStatus = "{\"destinations\": []}"
parsedData = PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(jsonWithNoStatus)
parsedData = PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithNoStatus)

then:
parsedData[1] == null

when:
def jsonWithoutOrgId = "{\"destinations\":[{\"service\":\"service\"}]}"
PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(jsonWithoutOrgId)
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithoutOrgId)

then:
thrown(FormatterProcessingException)

when:
def jsonWithoutService = "{\"destinations\":[{\"organization_id\":\"org_id\"}]}"
PartnerMetadataOrchestrator.getInstance().getReceiverAndStatus(jsonWithoutService)
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithoutService)

then:
thrown(FormatterProcessingException)

when:
def jsonWithoutErrorMessageSubString = "{\"destinations\":[{\"organization_id\":\"org_id\", \"service\":\"service\"}], \"overallStatus\": \"Error\"}"
PartnerMetadataOrchestrator.getInstance().getDataFromReportStream(jsonWithoutErrorMessageSubString)

then:
thrown(FormatterProcessingException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@ class HapiOrderConverterTest extends Specification {
def receiver = "receiver"
def time = Instant.now()
def hash = "hash"
def failureReason = "timed_out"
PartnerMetadata metadata = new PartnerMetadata(
"receivedSubmissionId", "sentSubmissionId", sender, receiver, time, hash, PartnerMetadataStatus.DELIVERED, null)
"receivedSubmissionId", "sentSubmissionId", sender, receiver, time, hash, PartnerMetadataStatus.DELIVERED, failureReason)

when:
def result = HapiOrderConverter.getInstance().extractPublicMetadataToOperationOutcome(metadata).getUnderlyingOutcome() as OperationOutcome
Expand All @@ -319,5 +320,6 @@ class HapiOrderConverterTest extends Specification {
result.getIssue().get(2).diagnostics == time.toString()
result.getIssue().get(3).diagnostics == hash
result.getIssue().get(4).diagnostics == PartnerMetadataStatus.DELIVERED.toString()
result.getIssue().get(5).diagnostics == failureReason
}
}

0 comments on commit 8472171

Please sign in to comment.