Skip to content

Commit

Permalink
Implementation to send the FHIR result to RS Waters endpoint (#859)
Browse files Browse the repository at this point in the history
* Refactored to extract common code into helper function and implemented send method for results

* Added methods for results and order sender

* Fixed typo

* Updated tests to reflect new changes

* Moved ReportStreamSenderHelper resgistration

* Added java docs

* Added test coverage

* Improved assertions for test
  • Loading branch information
basiliskus authored Feb 12, 2024
1 parent f283b48 commit 8533fb8
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamEndpointClient;
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamOrderSender;
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamResultSender;
import gov.hhs.cdc.trustedintermediary.external.reportstream.ReportStreamSenderHelper;
import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException;
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
Expand Down Expand Up @@ -104,6 +105,8 @@ public Map<HttpEndpoint, Function<DomainRequest, DomainResponse>> domainRegistra
ApplicationContext.register(ResultSender.class, ReportStreamResultSender.getInstance());
ApplicationContext.register(ResultController.class, ResultController.getInstance());
ApplicationContext.register(SendResultUseCase.class, SendResultUseCase.getInstance());
ApplicationContext.register(
ReportStreamSenderHelper.class, ReportStreamSenderHelper.getInstance());

if (ApplicationContext.getProperty("DB_URL") != null) {
ApplicationContext.register(DbDao.class, PostgresDao.getInstance());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
package gov.hhs.cdc.trustedintermediary.external.reportstream;

import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep;
import gov.hhs.cdc.trustedintermediary.etor.orders.Order;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender;
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;

Expand All @@ -20,11 +13,9 @@ public class ReportStreamOrderSender implements OrderSender {

private static final ReportStreamOrderSender INSTANCE = new ReportStreamOrderSender();

@Inject private RSEndpointClient rsclient;
@Inject private Formatter formatter;
@Inject private HapiFhir fhir;
@Inject private Logger logger;
@Inject MetricMetadata metadata;
@Inject ReportStreamSenderHelper sender;
@Inject HapiFhir fhir;
@Inject Logger logger;

public static ReportStreamOrderSender getInstance() {
return INSTANCE;
Expand All @@ -35,40 +26,7 @@ private ReportStreamOrderSender() {}
@Override
public Optional<String> send(final Order<?> order) throws UnableToSendMessageException {
logger.logInfo("Sending the order to ReportStream");

String json = fhir.encodeResourceToJson(order.getUnderlyingOrder());
String bearerToken;
String rsResponseBody;

try {
bearerToken = rsclient.getRsToken();
rsResponseBody = rsclient.requestWatersEndpoint(json, bearerToken);
} catch (ReportStreamEndpointClientException e) {
throw new UnableToSendMessageException("Unable to send order to ReportStream", e);
}

logger.logInfo("Order successfully sent to ReportStream");
metadata.put(order.getFhirResourceId(), EtorMetadataStep.SENT_TO_REPORT_STREAM);

Optional<String> sentSubmissionId = getSubmissionId(rsResponseBody);
if (sentSubmissionId.isEmpty()) {
logger.logError("Unable to retrieve sentSubmissionId from ReportStream response");
} else {
logger.logInfo("ReportStream response's sentSubmissionId={}", sentSubmissionId);
}

return sentSubmissionId;
}

protected Optional<String> getSubmissionId(String rsResponseBody) {
try {
Map<String, Object> rsResponse =
formatter.convertJsonToObject(rsResponseBody, new TypeReference<>() {});
return Optional.ofNullable(rsResponse.get("submissionId").toString());
} catch (FormatterProcessingException e) {
logger.logError("Unable to get the submissionId", e);
}

return Optional.empty();
return sender.sendOrderToReportStream(json, order.getFhirResourceId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.results.Result;
import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender;
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import java.util.Optional;
import javax.inject.Inject;

/**
* This class is responsible for sending results to the ReportStream service and receiving a
Expand All @@ -13,6 +16,10 @@ public class ReportStreamResultSender implements ResultSender {

private static final ReportStreamResultSender INSTANCE = new ReportStreamResultSender();

@Inject ReportStreamSenderHelper sender;
@Inject HapiFhir fhir;
@Inject Logger logger;

public static ReportStreamResultSender getInstance() {
return INSTANCE;
}
Expand All @@ -21,7 +28,8 @@ private ReportStreamResultSender() {}

@Override
public Optional<String> send(Result<?> result) throws UnableToSendMessageException {
// todo: implement in #616
return Optional.empty();
logger.logInfo("Sending results to ReportStream");
String json = fhir.encodeResourceToJson(result.getUnderlyingResult());
return sender.sendResultToReportStream(json, result.getFhirResourceId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package gov.hhs.cdc.trustedintermediary.external.reportstream;

import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;

/** Helper class for sending messages to ReportStream */
public class ReportStreamSenderHelper {
private static final ReportStreamSenderHelper INSTANCE = new ReportStreamSenderHelper();

@Inject RSEndpointClient rsclient;
@Inject Formatter formatter;
@Inject Logger logger;
@Inject MetricMetadata metadata;

private ReportStreamSenderHelper() {}

public static ReportStreamSenderHelper getInstance() {
return INSTANCE;
}

public Optional<String> sendOrderToReportStream(String body, String fhirResourceId)
throws UnableToSendMessageException {
return sendToReportStream(body, fhirResourceId, "order");
}

public Optional<String> sendResultToReportStream(String body, String fhirResourceId)
throws UnableToSendMessageException {
return sendToReportStream(body, fhirResourceId, "result");
}

protected Optional<String> sendToReportStream(
String body, String fhirResourceId, String messageType)
throws UnableToSendMessageException {
String bearerToken;
String rsResponseBody;

try {
bearerToken = rsclient.getRsToken();
rsResponseBody = rsclient.requestWatersEndpoint(body, bearerToken);
} catch (ReportStreamEndpointClientException e) {
throw new UnableToSendMessageException(
"Unable to send " + messageType + " to ReportStream", e);
}

logger.logInfo("{} successfully sent to ReportStream", messageType);
metadata.put(fhirResourceId, EtorMetadataStep.SENT_TO_REPORT_STREAM);

Optional<String> sentSubmissionId = getSubmissionId(rsResponseBody);
if (sentSubmissionId.isEmpty()) {
logger.logError("Unable to retrieve sentSubmissionId from ReportStream response");
} else {
logger.logInfo("ReportStream response's sentSubmissionId={}", sentSubmissionId);
}

return sentSubmissionId;
}

protected Optional<String> getSubmissionId(String rsResponseBody) {
try {
Map<String, Object> rsResponse =
formatter.convertJsonToObject(rsResponseBody, new TypeReference<>() {});
return Optional.ofNullable(rsResponse.get("submissionId").toString());
} catch (FormatterProcessingException e) {
logger.logError("Unable to get the submissionId", e);
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ class ResultMock<T> implements Result<T> {
private String fhirResourceId
private T underlyingResult

ResultMock(String fhirResourceId, T underlyingOrders) {
ResultMock(String fhirResourceId, T underlyingResult) {
this.fhirResourceId = fhirResourceId
this.underlyingResult = underlyingOrders
this.underlyingResult = underlyingResult
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,10 @@ package gov.hhs.cdc.trustedintermediary.external.reportstream
import gov.hhs.cdc.trustedintermediary.OrderMock
import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext
import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient
import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender
import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson
import gov.hhs.cdc.trustedintermediary.external.localfile.MockRSEndpointClient
import gov.hhs.cdc.trustedintermediary.wrappers.AuthEngine
import gov.hhs.cdc.trustedintermediary.wrappers.Cache
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir
import gov.hhs.cdc.trustedintermediary.wrappers.HttpClient
import gov.hhs.cdc.trustedintermediary.wrappers.Logger
import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata
import gov.hhs.cdc.trustedintermediary.wrappers.Secrets
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference
import spock.lang.Specification

class ReportStreamOrderSenderTest extends Specification {
Expand All @@ -31,106 +21,24 @@ class ReportStreamOrderSenderTest extends Specification {

def "send order works"() {
given:
def mockAuthEngine = Mock(AuthEngine)
TestApplicationContext.register(AuthEngine, mockAuthEngine)
def fhirResourceId = null
def underlyingOrder = "Mock order"
def mockOrder = new OrderMock(fhirResourceId, "patient-id", underlyingOrder)

def mockSecrets = Mock(Secrets)
TestApplicationContext.register(Secrets, mockSecrets)

def mockClient = Mock(HttpClient)
mockClient.post(_ as String, _ as Map, _ as String) >> """{"submissionId": "fake-id", "key": "value"}"""
TestApplicationContext.register(HttpClient, mockClient)
def senderHelper = Mock(ReportStreamSenderHelper)
senderHelper.sendOrderToReportStream(underlyingOrder, fhirResourceId) >> Optional.of("fake-id")
TestApplicationContext.register(ReportStreamSenderHelper, senderHelper)

def mockFhir = Mock(HapiFhir)
mockFhir.encodeResourceToJson(_ as String) >> "Mock order"
mockFhir.encodeResourceToJson(_ as String) >> underlyingOrder
TestApplicationContext.register(HapiFhir, mockFhir)

def mockFormatter = Mock(Formatter)
mockFormatter.convertJsonToObject(_ as String, _ as TypeReference) >> Map.of("submissionId", "fake-id")
TestApplicationContext.register(Formatter, mockFormatter)

def mockCache = Mock(Cache)
TestApplicationContext.register(Cache, mockCache)

TestApplicationContext.injectRegisteredImplementations()

when:
ReportStreamOrderSender.getInstance().send(new OrderMock(null, null, "Mock order"))
ReportStreamOrderSender.getInstance().send(mockOrder)

then:
noExceptionThrown()
}

def "log the step to metadata when send order is called"() {
given:

def mockAuthEngine = Mock(AuthEngine)
TestApplicationContext.register(AuthEngine, mockAuthEngine)

def mockSecrets = Mock(Secrets)
TestApplicationContext.register(Secrets, mockSecrets)

def mockClient = Mock(HttpClient)
mockClient.post(_ as String, _ as Map, _ as String) >> """{"submissionId": "fake-id", "key": "value"}"""
TestApplicationContext.register(HttpClient, mockClient)

def mockFhir = Mock(HapiFhir)
mockFhir.encodeResourceToJson(_ as String) >> "Mock order"
TestApplicationContext.register(HapiFhir, mockFhir)

def mockFormatter = Mock(Formatter)
mockFormatter.convertJsonToObject(_ as String, _ as TypeReference) >> Map.of("submissionId", "fake-id")
TestApplicationContext.register(Formatter, mockFormatter)

def mockCache = Mock(Cache)
TestApplicationContext.register(Cache, mockCache)

TestApplicationContext.injectRegisteredImplementations()

when:
ReportStreamOrderSender.getInstance().send(new OrderMock(null, null, "Mock order"))

then:
1 * ReportStreamOrderSender.getInstance().metadata.put(_, EtorMetadataStep.SENT_TO_REPORT_STREAM)
}

def "getSubmissionId logs submissionId if convertJsonToObject is successful"() {
given:
def mockSubmissionId = "fake-id"
def mockResponseBody = """{"submissionId": "${mockSubmissionId}", "key": "value"}"""

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

def mockLogger = Mock(Logger)
TestApplicationContext.register(Logger, mockLogger)

TestApplicationContext.injectRegisteredImplementations()

when:
def submissionId = ReportStreamOrderSender.getInstance().getSubmissionId(mockResponseBody)

then:
submissionId.get() == mockSubmissionId
}

def "getSubmissionId logs error if convertJsonToObject fails"() {
given:
def mockResponseBody = '{"submissionId": "fake-id", "key": "value"}'
def exception = new FormatterProcessingException("couldn't convert json", new Exception())

def mockFormatter = Mock(Formatter)
mockFormatter.convertJsonToObject(_ as String, _ as TypeReference) >> { throw exception }
TestApplicationContext.register(Formatter, mockFormatter)

def mockLogger = Mock(Logger)
TestApplicationContext.register(Logger, mockLogger)

TestApplicationContext.injectRegisteredImplementations()

when:
ReportStreamOrderSender.getInstance().getSubmissionId(mockResponseBody)

then:
1 * mockLogger.logError(_ as String, exception)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gov.hhs.cdc.trustedintermediary.external.reportstream
import gov.hhs.cdc.trustedintermediary.ResultMock
import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext
import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir
import spock.lang.Specification

class ReportStreamResultSenderTest extends Specification {
Expand All @@ -14,8 +15,23 @@ class ReportStreamResultSenderTest extends Specification {
}

def "send results works"() {
given:
def fhirResourceId = null
def underlyingResult = "Mock result"
def mockResult = new ResultMock(fhirResourceId, underlyingResult)

def senderHelper = Mock(ReportStreamSenderHelper)
senderHelper.sendResultToReportStream(underlyingResult, fhirResourceId) >> Optional.of("fake-id")
TestApplicationContext.register(ReportStreamSenderHelper, senderHelper)

def mockFhir = Mock(HapiFhir)
mockFhir.encodeResourceToJson(_ as String) >> underlyingResult
TestApplicationContext.register(HapiFhir, mockFhir)

TestApplicationContext.injectRegisteredImplementations()

when:
ReportStreamResultSender.getInstance().send(new ResultMock(null, "Mock result"))
ReportStreamResultSender.getInstance().send(mockResult)

then:
noExceptionThrown()
Expand Down
Loading

0 comments on commit 8533fb8

Please sign in to comment.