Skip to content

Commit

Permalink
Results controller implementation (#807)
Browse files Browse the repository at this point in the history
* results endpoint skeleton

* Base of Result Controller with Hapi Result

* Starting tests

* Results Controller Update with base unit tests

* Update EtorDomain with Results conrtroller

* hapiResult initial logic (#806)

* Update EtorDomain with a portion of result handling
- Start the ResultsResponse object
- Update EtorDomainRegistration to handle parsing results

* Base refactor message typing interface

* Remove missed merge conflicts

* Fix spacing

* Remove unnecessary field (patient id)

* Fix ResultResponse to remove PatientId

* Remove patient id from ResultMock

* Update test coverage to fix missing/broken tests for the following:
- EtorDomainRegistrationTest with result handling
- SendResultUseCaseTest with Result Mocking extra non-existent Patient Id
- ReportStreamResultSenderTest with Result Mocking extra non-existent Patient Id

* Register Results controller in etor DomainRegistration

* Fix CI tabbing

* ignore java version file

* Remove java version file

* Add ResultResponse test coverage and fix CI spotless issues

* Add EtorDomainRegistrationTest code coverage for handleResults

* Add SendResultUseCase to EtorDomainRegistration and add test cases

* Add MessageSender registration for ReportStreamResultSender

* Spotless CI check failures

* Fixed build

* Fixed test

* Reverted refactoring OrderSender => MessageSender and added ResultSender

* Remove unnecessary variable for error logs

* Add Result e2e testing base

* ResultTest e2e base

* Add base e2e tests for Result API Endpoint

* Remove unused variable

* Fixed argument name

* Another missed fix

---------

Co-authored-by: jorge Lopez <[email protected]>
Co-authored-by: saquino0827 <[email protected]>
Co-authored-by: Jorge Lopez <[email protected]>
Co-authored-by: Samuel Aquino <[email protected]>
  • Loading branch information
5 people authored Jan 30, 2024
1 parent 2f56178 commit 52d7aeb
Show file tree
Hide file tree
Showing 25 changed files with 451 additions and 65 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Thumbs.db
# Ignore python version file
.python-version

# Ignore java version file
.java-version

# Ignore terraform state (as it is persisted via Azure Storage)
terraform.tfstate*
.terraform*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package gov.hhs.cdc.trustedintermediary.e2e

import spock.lang.Specification

import java.nio.file.Files
import java.nio.file.Path

class ResultTest extends Specification {
def resultClient = new EndpointClient("/v1/etor/results")
def labResultJsonFileString = Files.readString(Path.of("../examples/MN/004_MN_ORU_R01_NBS_translation_from_initial_hl7_ingestion.fhir"))
def submissionId = "submissionId"

def setup() {
SentPayloadReader.delete()
}

def "a result response is returned from the ETOR order endpoint"() {
given:
def expectedFhirResourceId = "Bundle/1705511861639940150.e2c69100-24af-4bbd-86bf-2f29be816edf"

when:
def response = resultClient.submit(labResultJsonFileString, submissionId, true)
def parsedJsonBody = JsonParsing.parseContent(response)

then:
response.getCode() == 200
parsedJsonBody.fhirResourceId == expectedFhirResourceId
}

// Will progress on these tests when sending data to RS
// def "check that the rest of the message is unchanged except the parts we changed"() {
// when:
// resultClient.submit(labResultJsonFileString, submissionId, true)
// def sentPayload = SentPayloadReader.read()
// def parsedSentPayload = JsonParsing.parse(sentPayload)
// def parsedLabResultJsonFile = JsonParsing.parse(labResultJsonFileString)
//
// then:
//
// parsedSentPayload == parsedLabResultJsonFile
// }
//
// def "check that message type is converted to ORU_R01"() {
// when:
// resultClient.submit(labResultJsonFileString, submissionId, true)
// def sentPayload = SentPayloadReader.read()
// def parsedSentPayload = JsonParsing.parse(sentPayload)
//
// then:
// //test that the MessageHeader's event is now an OML_O21
// parsedSentPayload.entry[0].resource.resourceType == "MessageHeader"
// parsedSentPayload.entry[0].resource.eventCoding.code == "R01"
// parsedSentPayload.entry[0].resource.eventCoding.display.contains("ORU")
// }

def "return a 400 response when request has unexpected format"() {
given:
def invalidJsonRequest = labResultJsonFileString.substring(1)

when:
def response = resultClient.submit(invalidJsonRequest, submissionId, true)
def parsedJsonBody = JsonParsing.parseContent(response)

then:
response.getCode() == 400
!(parsedJsonBody.error as String).isEmpty()
}

def "return a 401 response when making an unauthenticated request"() {
when:
def response = resultClient.submit(labResultJsonFileString, submissionId, false)
def parsedJsonBody = JsonParsing.parseContent(response)

then:
response.getCode() == 401
!(parsedJsonBody.error as String).isEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics;
import gov.hhs.cdc.trustedintermediary.etor.demographics.PatientDemographicsController;
import gov.hhs.cdc.trustedintermediary.etor.demographics.PatientDemographicsResponse;
import gov.hhs.cdc.trustedintermediary.etor.messages.MessageSender;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException;
Expand All @@ -22,7 +21,13 @@
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderController;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderResponse;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender;
import gov.hhs.cdc.trustedintermediary.etor.orders.SendOrderUseCase;
import gov.hhs.cdc.trustedintermediary.etor.results.Result;
import gov.hhs.cdc.trustedintermediary.etor.results.ResultController;
import gov.hhs.cdc.trustedintermediary.etor.results.ResultResponse;
import gov.hhs.cdc.trustedintermediary.etor.results.ResultSender;
import gov.hhs.cdc.trustedintermediary.etor.results.SendResultUseCase;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureClient;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureDatabaseCredentialsProvider;
import gov.hhs.cdc.trustedintermediary.external.azure.AzureStorageAccountPartnerMetadataStorage;
Expand All @@ -37,6 +42,7 @@
import gov.hhs.cdc.trustedintermediary.external.localfile.MockRSEndpointClient;
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.wrappers.FhirParseException;
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
Expand Down Expand Up @@ -67,9 +73,9 @@ public class EtorDomainRegistration implements DomainConnector {
@Inject ConvertAndSendDemographicsUsecase convertAndSendDemographicsUsecase;
@Inject SendOrderUseCase sendOrderUseCase;

// @Inject ResultController resultController
@Inject ResultController resultController;

// @Inject SendResultUseCase sendResultUseCase
@Inject SendResultUseCase sendResultUseCase;

@Inject Logger logger;
@Inject DomainResponseHelper domainResponseHelper;
Expand All @@ -85,6 +91,7 @@ public class EtorDomainRegistration implements DomainConnector {
this::handleDemographics,
new HttpEndpoint("POST", ORDERS_API_ENDPOINT, true), this::handleOrders,
new HttpEndpoint("GET", METADATA_API_ENDPOINT, true), this::handleMetadata,
new HttpEndpoint("POST", RESULTS_API_ENDPOINT, true), this::handleResults,
new HttpEndpoint("GET", CONSOLIDATED_SUMMARY_API_ENDPOINT, true),
this::handleConsolidatedSummary);

Expand All @@ -98,9 +105,12 @@ public Map<HttpEndpoint, Function<DomainRequest, DomainResponse>> domainRegistra
ApplicationContext.register(OrderConverter.class, HapiOrderConverter.getInstance());
ApplicationContext.register(OrderController.class, OrderController.getInstance());
ApplicationContext.register(SendOrderUseCase.class, SendOrderUseCase.getInstance());
ApplicationContext.register(MessageSender.class, ReportStreamOrderSender.getInstance());
ApplicationContext.register(OrderSender.class, ReportStreamOrderSender.getInstance());
ApplicationContext.register(
PartnerMetadataOrchestrator.class, PartnerMetadataOrchestrator.getInstance());
ApplicationContext.register(ResultSender.class, ReportStreamResultSender.getInstance());
ApplicationContext.register(ResultController.class, ResultController.getInstance());
ApplicationContext.register(SendResultUseCase.class, SendResultUseCase.getInstance());

if (ApplicationContext.getProperty("DB_URL") != null) {
ApplicationContext.register(SqlDriverManager.class, EtorSqlDriverManager.getInstance());
Expand Down Expand Up @@ -231,6 +241,31 @@ DomainResponse handleMetadata(DomainRequest request) {
}
}

DomainResponse handleResults(DomainRequest request) {
Result<?> results;

String receivedSubmissionId = request.getHeaders().get("recordid");
if (receivedSubmissionId == null || receivedSubmissionId.isEmpty()) {
receivedSubmissionId = null;
logger.logError("Missing required header or empty: RecordId");
}

try {
results = resultController.parseResults(request);
sendResultUseCase.convertAndSend(results);
} catch (FhirParseException e) {
logger.logError("Unable to parse result request", e);
return domainResponseHelper.constructErrorResponse(400, e);
} catch (UnableToSendMessageException e) {
logger.logError("Unable to send result", e);
return domainResponseHelper.constructErrorResponse(400, e);
}

ResultResponse resultResponse = new ResultResponse(results);
logger.logInfo(request.getHeaders().toString());
return domainResponseHelper.constructOkResponse(resultResponse);
}

DomainResponse handleConsolidatedSummary(DomainRequest request) {

Map<String, Map<String, Object>> metadata;
Expand All @@ -247,18 +282,4 @@ DomainResponse handleConsolidatedSummary(DomainRequest request) {

return domainResponseHelper.constructOkResponse(metadata);
}

public DomainResponse handleResults(DomainRequest request) {

// Get the result
// Result<?> result
// resultController.parseResults(request)
// sendResultUseCase/ sendOrderUseCase? change name for reuse?

// ResultResponse resultResponse = new ResultResponse(results)
// return domainResponseHelper.constructOkResponse(resultResponse)

logger.logInfo(request.getHeaders().toString());
return new DomainResponse(200);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package gov.hhs.cdc.trustedintermediary.etor.demographics;

import gov.hhs.cdc.trustedintermediary.etor.messages.MessageSender;
import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageUseCase;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.orders.Order;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter;
import gov.hhs.cdc.trustedintermediary.etor.orders.OrderSender;
import javax.inject.Inject;

/**
Expand All @@ -18,7 +18,7 @@ public class ConvertAndSendDemographicsUsecase implements SendMessageUseCase<Dem

@Inject OrderConverter converter;

@Inject MessageSender<Order<?>> sender;
@Inject OrderSender sender;

public static ConvertAndSendDemographicsUsecase getInstance() {
return INSTANCE;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.hhs.cdc.trustedintermediary.etor.orders;

import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import java.util.Optional;

/** Interface for sending a lab order. */
public interface OrderSender {
Optional<String> send(Order<?> order) throws UnableToSendMessageException;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package gov.hhs.cdc.trustedintermediary.etor.orders;

import gov.hhs.cdc.trustedintermediary.etor.messages.MessageSender;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep;
import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataException;
Expand All @@ -13,7 +12,7 @@
public class SendOrderUseCase {
private static final SendOrderUseCase INSTANCE = new SendOrderUseCase();
@Inject OrderConverter converter;
@Inject MessageSender<Order<?>> sender;
@Inject OrderSender sender;
@Inject MetricMetadata metadata;
@Inject PartnerMetadataOrchestrator partnerMetadataOrchestrator;
@Inject Logger logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@ public interface Result<T> {
T getUnderlyingResult();

String getFhirResourceId();

String getPatientId();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package gov.hhs.cdc.trustedintermediary.etor.results;

import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest;
import gov.hhs.cdc.trustedintermediary.external.hapi.HapiResult;
import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException;
import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import javax.inject.Inject;
import org.hl7.fhir.r4.model.Bundle;

public class ResultController {

private static final ResultController INSTANCE = new ResultController();
@Inject HapiFhir fhir;
@Inject Logger logger;

private ResultController() {}

public static ResultController getInstance() {
return INSTANCE;
}

public Result<?> parseResults(DomainRequest request) throws FhirParseException {
logger.logInfo("Parsing results");
var fhirBundle = fhir.parseResource(request.getBody(), Bundle.class);
// ETOR Results metadata
return new HapiResult(fhirBundle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package gov.hhs.cdc.trustedintermediary.etor.results;

public class ResultResponse {
private String fhirResourceId;

public ResultResponse(String fhirResourceId) {
this.fhirResourceId = fhirResourceId;
}

public ResultResponse(Result<?> result) {
this.fhirResourceId = result.getFhirResourceId();
}

public String getFhirResourceId() {
return fhirResourceId;
}

public void setFhirResourceId(final String fhirResourceId) {
this.fhirResourceId = fhirResourceId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.hhs.cdc.trustedintermediary.etor.results;

import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import java.util.Optional;

/** Interface for sending a lab result. */
public interface ResultSender {
Optional<String> send(Result<?> result) throws UnableToSendMessageException;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package gov.hhs.cdc.trustedintermediary.etor.results;

import gov.hhs.cdc.trustedintermediary.etor.messages.MessageSender;
import gov.hhs.cdc.trustedintermediary.etor.messages.SendMessageUseCase;
import gov.hhs.cdc.trustedintermediary.etor.messages.UnableToSendMessageException;
import javax.inject.Inject;

/** Use case for converting and sending a lab result message. */
public class SendResultUseCase implements SendMessageUseCase<Result<?>> {
private static final SendResultUseCase INSTANCE = new SendResultUseCase();
@Inject MessageSender<Result<?>> sender;
@Inject ResultSender sender;

private SendResultUseCase() {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gov.hhs.cdc.trustedintermediary.external.hapi;

import gov.hhs.cdc.trustedintermediary.etor.results.Result;
import org.hl7.fhir.r4.model.Bundle;

/** Filler concrete implementation of a {@link Result} using the Hapi FHIR library */
public class HapiResult implements Result<Bundle> {

private final Bundle innerResult;

public HapiResult(Bundle innerResult) {
this.innerResult = innerResult;
}

@Override
public Bundle getUnderlyingResult() {
return innerResult;
}

@Override
public String getFhirResourceId() {
return innerResult.getId();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package gov.hhs.cdc.trustedintermediary.external.reportstream;

import gov.hhs.cdc.trustedintermediary.etor.RSEndpointClient;
import gov.hhs.cdc.trustedintermediary.etor.messages.MessageSender;
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;
Expand All @@ -16,7 +16,7 @@
import javax.inject.Inject;

/** Accepts a {@link Order} and sends it to ReportStream. */
public class ReportStreamOrderSender implements MessageSender<Order<?>> {
public class ReportStreamOrderSender implements OrderSender {

private static final ReportStreamOrderSender INSTANCE = new ReportStreamOrderSender();

Expand Down
Loading

0 comments on commit 52d7aeb

Please sign in to comment.