Skip to content

Commit

Permalink
Add ingestion time and call Delivery (#1011)
Browse files Browse the repository at this point in the history
* Adding baseline method

* Adding tests and providing body for mock RS client

* Adding sendingOrg to tests

* Fixing e2e Tests

* Fixing unit tests

* Set the sender name to PLACER_HOLDER when querying the summary metadata endpoint until story 990 is complete

---------

Co-authored-by: halprin <halprin@users.noreply.github.com>
  • Loading branch information
jcrichlake and halprin authored Apr 11, 2024
1 parent 9e5956c commit f946ec3
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ class ConsolidatedSummaryTest extends Specification {

def orderClient = new EndpointClient("/v1/etor/orders")
def labOrderJsonFileString = Files.readString(Path.of("../examples/Test/Orders/002_ORM_O01.fhir"))
def senderName = "flexion.simulated-hospital"
def senderName = "PLACE_HOLDER" //TODO: when story #990 is implemented, update this to be the sender from the 002_ORM_O01.fhir message

when:
def orderResponse = orderClient.submit(labOrderJsonFileString, inboundSubmissionId, true)

then:
orderResponse.getCode() == expectedStatusCode

when:
def senderNameResponse = ConsolidatedSummaryClient.get(senderName, true)
def jsonBody = JsonParsing.parseContent(senderNameResponse)

then:
jsonBody.get((jsonBody.keySet().toArray())[0]).stale != null
jsonBody.get((jsonBody.keySet().toArray())[0]).failureReason == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ String requestWatersEndpoint(String body, String bearerToken)

String requestHistoryEndpoint(String submissionId, String bearerToken)
throws ReportStreamEndpointClientException;

String requestDeliveryEndpoint(String reportId, String bearerToken)
throws ReportStreamEndpointClientException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -37,39 +38,48 @@ private PartnerMetadataOrchestrator() {}
public void updateMetadataForReceivedMessage(
String receivedSubmissionId, String messageHash, PartnerMetadataMessageType messageType)
throws PartnerMetadataException {
// currently blocked by: https://github.com/CDCgov/prime-reportstream/issues/12624
// once we get the right receivedSubmissionId from RS, this method should work

logger.logInfo(
"Looking up sender name and timeReceived from RS history API for receivedSubmissionId: {}",
"Looking up sender name and timeReceived from RS delivery API for receivedSubmissionId: {}",
receivedSubmissionId);

String sender;
Instant timeReceived;
try {
String bearerToken = rsclient.getRsToken();
String responseBody =
rsclient.requestHistoryEndpoint(receivedSubmissionId, bearerToken);
rsclient.requestDeliveryEndpoint(receivedSubmissionId, bearerToken);
Map<String, Object> responseObject =
formatter.convertJsonToObject(responseBody, new TypeReference<>() {});

sender = responseObject.get("sender").toString();
String timestamp = responseObject.get("timestamp").toString();
List<Map<String, String>> originalIngestion =
(List<Map<String, String>>) responseObject.get("originalIngestion");

if (originalIngestion.size() > 1) {
logger.logWarning(
"More than 1 report ids found in originalIngestion,"
+ " check to make sure batching wasn't turned on for receiver in RS");
}

// We should only have 1 object in originalIngestion, it is a list to support other RS
// use cases
String timestamp = originalIngestion.get(0).get("ingestionTime");
timeReceived = Instant.parse(timestamp);

} catch (Exception e) {
// write the received submission ID so that the rest of the metadata flow works even if
// some data is missing
logger.logWarning(
"Unable to retrieve metadata from RS history API, but writing basic metadata entry anyway for received submission ID {}",
"Unable to retrieve metadata from RS delivery API, but writing basic metadata entry anyway for received submission ID {}",
receivedSubmissionId);
PartnerMetadata partnerMetadata =
new PartnerMetadata(receivedSubmissionId, messageHash, messageType);
partnerMetadataStorage.saveMetadata(partnerMetadata);

throw new PartnerMetadataException(
"Unable to retrieve metadata from RS history API", e);
"Unable to retrieve metadata from RS delivery API", e);
}

String sender = "PLACE_HOLDER";
logger.logInfo(
"Updating metadata with sender: {}, timeReceived: {} and hash",
sender,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,35 @@ public String requestHistoryEndpoint(String submissionId, String bearerToken) {
}]
}""";
}

@Override
public String requestDeliveryEndpoint(String reportId, String bearerToken)
throws ReportStreamEndpointClientException {
return """
{
"deliveryId": 20,
"batchReadyAt": "2024-04-09T18:19:00.431Z",
"expires": "2024-05-09T18:19:00.431Z",
"receiver": "flexion.etor-service-receiver-orders",
"receivingOrgSvcStatus": null,
"reportId": "ddfeb4e2-af58-433e-9297-a4be01957225",
"topic": "etor-ti",
"reportItemCount": 2,
"fileName": "fhir-transform-sample.yml-ddfeb4e2-af58-433e-9297-a4be01957225-20240409181900.fhir",
"fileType": "FHIR",
"originalIngestion": [
{
"reportId": "2f5f17e7-2161-44d9-b091-2d53c10f6e90",
"ingestionTime": "2024-04-09T18:17:56.571Z",
"sendingOrg": "DogCow Associates"
},
{
"reportId": "e18c283e-e2e4-4804-bca3-33afe32e6b69",
"ingestionTime": "2024-04-09T18:18:00.553Z",
"sendingOrg": "DogCow Associates"
}
]
}
""";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class ReportStreamEndpointClient implements RSEndpointClient {
private static final String RS_AUTH_API_URL = RS_URL_PREFIX + "/api/token";
private static final String RS_HISTORY_API_URL =
RS_URL_PREFIX + "/api/waters/report/{id}/history";
private static final String RS_DELIVERY_API_URL = RS_WATERS_API_URL + "/report/{id}/delivery";

private static final String OUR_PRIVATE_KEY_ID =
"trusted-intermediary-private-key-" + ApplicationContext.getEnvironment();
Expand Down Expand Up @@ -123,6 +124,22 @@ public String requestHistoryEndpoint(String submissionId, String bearerToken)
}
}

@Override
public String requestDeliveryEndpoint(String reportId, String bearerToken)
throws ReportStreamEndpointClientException {
logger.logInfo("Requesting delivery API from ReportStream");

Map<String, String> headers = Map.of("Authorization", "Bearer " + bearerToken);

try {
String url = RS_DELIVERY_API_URL.replace("{id}", reportId);
return client.get(url, headers);
} catch (HttpClientException e) {
throw new ReportStreamEndpointClientException(
"Error GETting deliveries from ReportStream", e);
}
}

protected String requestToken() throws ReportStreamEndpointClientException {
logger.logInfo("Requesting token from ReportStream");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,31 +35,56 @@ class PartnerMetadataOrchestratorTest extends Specification {
TestApplicationContext.injectRegisteredImplementations()
}

def "updateMetadataForReceivedOrder updates metadata successfully"() {
def "updateMetadataForReceivedMessage updates metadata successfully"() {
given:

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

def receivedSubmissionId = "receivedSubmissionId"
def sender = "senderName"
def timestamp = "2020-01-01T00:00:00.000Z"
def timeDelivered = "2020-01-02T00:00:00.000Z"
def hashCode = "123"
def bearerToken = "token"
def messageType = PartnerMetadataMessageType.RESULT
def rsHistoryApiResponse = "{\"actualCompletionAt\": \"2023-10-24T19:48:26.921Z\",\"sender\": \"${sender}\", \"timestamp\": \"${timestamp}\"}"
def deliveryStatus = PartnerMetadataStatus.PENDING


mockFormatter.convertJsonToObject(rsHistoryApiResponse, _ as TypeReference) >> [sender: sender, timestamp: timestamp, actualCompletionAt: timeDelivered]
def rsDeliveryApiResponse = """
{
"deliveryId": 20,
"batchReadyAt": "2024-04-09T18:19:00.431Z",
"expires": "2024-05-09T18:19:00.431Z",
"receiver": "flexion.etor-service-receiver-orders",
"receivingOrgSvcStatus": null,
"reportId": "ddfeb4e2-af58-433e-9297-a4be01957225",
"topic": "etor-ti",
"reportItemCount": 2,
"fileName": "fhir-transform-sample.yml-ddfeb4e2-af58-433e-9297-a4be01957225-20240409181900.fhir",
"fileType": "FHIR",
"originalIngestion": [
{
"reportId": "2f5f17e7-2161-44d9-b091-2d53c10f6e90",
"ingestionTime": "${timestamp}",
"sendingOrg": "Clarus Doctors"
},
{
"reportId": "e18c283e-e2e4-4804-bca3-33afe32e6b69",
"ingestionTime": "2024-04-09T18:18:00.553Z",
"sendingOrg": "DogCow Associates"
}
]
}
"""

when:
PartnerMetadataOrchestrator.getInstance().updateMetadataForReceivedMessage(receivedSubmissionId, hashCode, messageType)

then:
1 * mockClient.getRsToken() >> bearerToken
1 * mockClient.requestHistoryEndpoint(receivedSubmissionId, bearerToken) >> rsHistoryApiResponse
1 * mockPartnerMetadataStorage.saveMetadata(new PartnerMetadata(receivedSubmissionId, sender, Instant.parse(timestamp), null, hashCode, deliveryStatus, messageType))
1 * mockClient.requestDeliveryEndpoint(receivedSubmissionId, bearerToken) >> rsDeliveryApiResponse
1 * mockPartnerMetadataStorage.saveMetadata(new PartnerMetadata(receivedSubmissionId, "PLACE_HOLDER", Instant.parse(timestamp), null, hashCode, deliveryStatus, messageType))
}

def "updateMetadataForSentOrder test case when sentSubmissionId is null"() {
def "updateMetadataForSentMessage test case when sentSubmissionId is null"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def sentSubmissionId = null
Expand All @@ -71,7 +96,7 @@ class PartnerMetadataOrchestratorTest extends Specification {
0 * mockPartnerMetadataStorage.readMetadata(receivedSubmissionId)
}

def "updateMetadataForSentOrder test case when PartnerMetadata returns no data"() {
def "updateMetadataForSentMessage test case when PartnerMetadata returns no data"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def sentSubmissionId = "sentSubmissionId"
Expand All @@ -98,13 +123,13 @@ class PartnerMetadataOrchestratorTest extends Specification {
1 * mockPartnerMetadataStorage.readMetadata(receivedSubmissionId) >> mockMetadata
}

def "updateMetadataForReceivedOrder throws PartnerMetadataException on client error"() {
def "updateMetadataForReceivedMessage throws PartnerMetadataException on client error"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def messageType = PartnerMetadataMessageType.RESULT

mockClient.getRsToken() >> "token"
mockClient.requestHistoryEndpoint(_ as String, _ as String) >> { throw new ReportStreamEndpointClientException("Client error", new Exception()) }
mockClient.requestDeliveryEndpoint(_ as String, _ as String) >> { throw new ReportStreamEndpointClientException("Client error", new Exception()) }

when:
PartnerMetadataOrchestrator.getInstance().updateMetadataForReceivedMessage(receivedSubmissionId, "hash", messageType)
Expand All @@ -116,15 +141,15 @@ class PartnerMetadataOrchestratorTest extends Specification {
thrown(PartnerMetadataException)
}

def "updateMetadataForReceivedOrder throws PartnerMetadataException on formatter error"() {
def "updateMetadataForReceivedMessage throws PartnerMetadataException on formatter error"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def messageType = PartnerMetadataMessageType.RESULT
def rsHistoryApiResponse = "{\"sender\": \"responseName\", \"timestamp\": \"2020-01-01T00:00:00.000Z\"}"
def rsDeliveryApiResponse = "{ASDF}"

mockClient.getRsToken() >> "token"
mockClient.requestHistoryEndpoint(_ as String, _ as String) >> rsHistoryApiResponse
mockFormatter.convertJsonToObject(rsHistoryApiResponse, _ as TypeReference) >> { throw new FormatterProcessingException("Formatter error", new Exception()) }
mockClient.requestDeliveryEndpoint(_ as String, _ as String) >> rsDeliveryApiResponse
mockFormatter.convertJsonToObject(rsDeliveryApiResponse, _ as TypeReference) >> { throw new FormatterProcessingException("Formatter error", new Exception()) }

when:
PartnerMetadataOrchestrator.getInstance().updateMetadataForReceivedMessage(receivedSubmissionId, "hash", messageType)
Expand All @@ -133,14 +158,14 @@ class PartnerMetadataOrchestratorTest extends Specification {
thrown(PartnerMetadataException)
}

def "updateMetadataForReceivedOrder throws PartnerMetadataException on formatter error due to unexpected response format"() {
def "updateMetadataForReceivedMessage throws PartnerMetadataException on formatter error due to unexpected response format"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def wrongFormatResponse = "{\"someotherkey\": \"value\"}"
def messageType = PartnerMetadataMessageType.RESULT

mockClient.getRsToken() >> "token"
mockClient.requestHistoryEndpoint(_ as String, _ as String) >> wrongFormatResponse
mockClient.requestDeliveryEndpoint(_ as String, _ as String) >> wrongFormatResponse
mockFormatter.convertJsonToObject(wrongFormatResponse, _ as TypeReference) >> [someotherkey: "value"]

when:
Expand All @@ -150,7 +175,24 @@ class PartnerMetadataOrchestratorTest extends Specification {
thrown(PartnerMetadataException)
}

def "updateMetadataForSentOrder updates metadata successfully"() {
def "updateMetadataForReceivedMessage throws PartnerMetadataException due to 0 originalIngestions"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def wrongFormatResponse = "{\"originalIngestion\": []}"
def messageType = PartnerMetadataMessageType.RESULT

mockClient.getRsToken() >> "token"
mockClient.requestDeliveryEndpoint(_ as String, _ as String) >> wrongFormatResponse
mockFormatter.convertJsonToObject(wrongFormatResponse, _ as TypeReference) >> [originalIngestion: []]

when:
PartnerMetadataOrchestrator.getInstance().updateMetadataForReceivedMessage(receivedSubmissionId, "hash", messageType)

then:
thrown(PartnerMetadataException)
}

def "updateMetadataForSentMessage updates metadata successfully"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def sentSubmissionId = "sentSubmissionId"
Expand All @@ -165,7 +207,7 @@ class PartnerMetadataOrchestratorTest extends Specification {
1 * mockPartnerMetadataStorage.saveMetadata(updatedPartnerMetadata)
}

def "updateMetadataForSentOrder test case when sentSubmissionId is null"() {
def "updateMetadataForSentMessage test case when sentSubmissionId is null"() {
given:
def receivedSubmissionId = "receivedSubmissionId"
def sentSubmissionId = null
Expand All @@ -186,7 +228,7 @@ class PartnerMetadataOrchestratorTest extends Specification {

mockPartnerMetadataStorage.readMetadata(receivedSubmissionId) >> Optional.of(partnerMetadata)
mockClient.getRsToken() >> "token"
mockClient.requestHistoryEndpoint(_ as String, _ as String) >> { throw new ReportStreamEndpointClientException("Client error", new Exception()) }
mockClient.requestDeliveryEndpoint(_ as String, _ as String) >> { throw new ReportStreamEndpointClientException("Client error", new Exception()) }

when:
PartnerMetadataOrchestrator.getInstance().getMetadata(receivedSubmissionId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,17 @@ class MockRSEndpointClientTest extends Specification {
cleanup:
readonlyLocalFile.toFile().delete()
}

def "requestDeliveryEndpoint happy path"() {
when:
def token = MockRSEndpointClient.getInstance().getRsToken()
def response = MockRSEndpointClient.getInstance().requestDeliveryEndpoint("delivery", token)
def responseObject =
Jackson.getInstance().convertJsonToObject(response, new TypeReference<Map<String, Object>>() {})

then:
responseObject.originalIngestion != null
responseObject.originalIngestion[0] != null
responseObject.originalIngestion[0].ingestionTime != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ class ReportStreamEndpointClientTest extends Specification {
thrown(ReportStreamEndpointClientException)
}

def "requestDeliveryEndpoint works"() {
given:
def mockClient = Mock(HttpClient)
TestApplicationContext.register(HttpClient, mockClient)
TestApplicationContext.injectRegisteredImplementations()
when:
ReportStreamEndpointClient.getInstance().requestDeliveryEndpoint("report_id_1", "fake token")
ReportStreamEndpointClient.getInstance().requestDeliveryEndpoint("report_id_2", "fake token")

then:
2 * mockClient.get(_ as String, _ as Map<String, String>) >> "200"
}

def "requestDeliveryEndpoint fails due to HttpClientException"() {
given:
def mockClient = Mock(HttpClient)
mockClient.get(_ as String, _ as Map<String,String>) >> { throw new HttpClientException("404", new Exception()) }
TestApplicationContext.register(HttpClient, mockClient)
TestApplicationContext.injectRegisteredImplementations()

when:
ReportStreamEndpointClient.getInstance().requestDeliveryEndpoint("report_id", "fake token")

then:
thrown(ReportStreamEndpointClientException)
}

def "requestToken works"() {
given:
def expected = "rs fake token"
Expand Down

0 comments on commit f946ec3

Please sign in to comment.