Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ingestion time and call Delivery #1011

Merged
merged 6 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading