Skip to content

Commit

Permalink
Merge pull request #2287 from metriport/develop
Browse files Browse the repository at this point in the history
RELEASE IHE V2 Retries + Updates to CICD Testing
  • Loading branch information
jonahkaye committed Jun 18, 2024
2 parents 0d5ea4d + b8f5ebf commit d38cf85
Show file tree
Hide file tree
Showing 26 changed files with 598 additions and 329 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/branch-to-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,30 @@ jobs:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
IHE_GW_KEYSTORE_STOREPASS: ${{ secrets.IHE_GW_KEYSTORE_STOREPASS_STAGING }}
IHE_GW_KEYSTORE_KEYPASS: ${{ secrets.IHE_GW_KEYSTORE_KEYPASS_STAGING }}

e2e-tests:
uses: ./.github/workflows/_e2e-tests.yml
needs: [api, infra-api-lambdas, infra-ihe-gw, ihe-gw-server]
# run even if one of the dependencies didn't
# can't use ${{ ! failure() && success() }} because `success()` "Returns true when none of the previous steps have failed or been canceled."
# can't use ${{ ! failure() && contains(needs.*.result, 'success') }} because if anything that came before succeeded, even if not a direct dependency, it will run
if: ${{ !failure() && (needs.api.result == 'success' || needs.infra-api-lambdas.result == 'success' || needs.infra-ihe-gw.result == 'success' || needs.ihe-gw-server.result == 'success') }}
with:
deploy_env: "staging"
api_url: ${{ vars.API_URL_STAGING }}
fhir_url: ${{ vars.FHIR_SERVER_URL_STAGING }}
test_patient_id: ${{ vars.TEST_PATIENT_ID }}
secrets:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
TEST_API_KEY: ${{ secrets.TEST_API_KEY_STAGING }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }}
CW_CERTIFICATE: ${{ secrets.CW_CERTIFICATE_STAGING }}
CW_PRIVATE_KEY: ${{ secrets.CW_PRIVATE_KEY_STAGING }}
CW_MEMBER_CERTIFICATE: ${{ secrets.CW_MEMBER_CERTIFICATE_STAGING }}
CW_MEMBER_PRIVATE_KEY: ${{ secrets.CW_MEMBER_PRIVATE_KEY_STAGING }}
CW_MEMBER_NAME: ${{ secrets.CW_MEMBER_NAME_STAGING }}
CW_MEMBER_OID: ${{ secrets.CW_MEMBER_OID_STAGING }}

Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
import { IHEGatewayV2 } from "@metriport/core/external/carequality/ihe-gateway-v2/ihe-gateway-v2";
import {
createSignSendProcessXCPDRequest,
createSignSendProcessDQRequests,
createSignSendProcessDRRequests,
createSignSendProcessDqRequests,
createSignSendProcessDrRequests,
} from "@metriport/core/external/carequality/ihe-gateway-v2/ihe-gateway-v2-logic";
import { SamlCertsAndKeys } from "@metriport/core/external/carequality/ihe-gateway-v2/saml/security/types";
import { Config } from "../../shared/config";
Expand Down Expand Up @@ -60,7 +60,7 @@ export class IHEGatewayV2Direct extends IHEGatewayV2 {
patientId: string;
cxId: string;
}): Promise<void> {
await createSignSendProcessDQRequests({
await createSignSendProcessDqRequests({
dqResponseUrl: this.dqResponseUrl,
dqRequestsGatewayV2,
samlCertsAndKeys: this.samlCertsAndKeys,
Expand All @@ -78,7 +78,7 @@ export class IHEGatewayV2Direct extends IHEGatewayV2 {
patientId: string;
cxId: string;
}): Promise<void> {
await createSignSendProcessDRRequests({
await createSignSendProcessDrRequests({
drResponseUrl: this.drResponseUrl,
drRequestsGatewayV2,
samlCertsAndKeys: this.samlCertsAndKeys,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ const specialNamespaceRequiredUrl =
const pointClickCareOid = "2.16.840.1.113883.3.6448";
const redoxOid = "2.16.840.1.113883.3.6147.458";
const redoxGatewayOid = "2.16.840.1.113883.3.6147.458.2";
const surescriptsOid = "2.16.840.1.113883.3.2054.2.1.1";

/*
* These gateways only accept a single document reference per request.
*/
const gatewaysThatAcceptOneDocRefPerRequest = [pointClickCareOid, redoxOid, redoxGatewayOid];
const gatewaysThatAcceptOneDocRefPerRequest = [
pointClickCareOid,
redoxOid,
redoxGatewayOid,
surescriptsOid,
];

const surescriptsOid = "2.16.840.1.113883.3.2054.2.1.1";
/*
* These gateways require a urn:uuid prefix before document Unique ids formatted as lowercase uuids
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,92 @@
import {
OutboundDocumentQueryReq,
OutboundDocumentQueryResp,
OutboundDocumentRetrievalReq,
OutboundDocumentRetrievalResp,
OutboundPatientDiscoveryReq,
OutboundPatientDiscoveryResp,
} from "@metriport/ihe-gateway-sdk";
import { executeWithNetworkRetries } from "@metriport/shared";
import { executeWithNetworkRetries, executeWithRetries } from "@metriport/shared";
import axios from "axios";
import { capture } from "../../../util/notifications";
import { createAndSignBulkDQRequests } from "./outbound/xca/create/iti38-envelope";
import { createAndSignBulkDRRequests } from "./outbound/xca/create/iti39-envelope";
import { processDQResponse } from "./outbound/xca/process/dq-response";
import { createAndSignBulkDQRequests, SignedDqRequest } from "./outbound/xca/create/iti38-envelope";
import { createAndSignBulkDRRequests, SignedDrRequest } from "./outbound/xca/create/iti39-envelope";
import { processDqResponse } from "./outbound/xca/process/dq-response";
import { processDrResponse } from "./outbound/xca/process/dr-response";
import { sendSignedDQRequests } from "./outbound/xca/send/dq-requests";
import { sendSignedDRRequests } from "./outbound/xca/send/dr-requests";
import { isRetryable } from "./outbound/xca/process/error";
import { sendSignedDqRequest } from "./outbound/xca/send/dq-requests";
import { sendSignedDrRequest } from "./outbound/xca/send/dr-requests";
import { createAndSignBulkXCPDRequests } from "./outbound/xcpd/create/iti55-envelope";
import { processXCPDResponse } from "./outbound/xcpd/process/xcpd-response";
import { sendSignedXCPDRequests } from "./outbound/xcpd/send/xcpd-requests";
import { SamlCertsAndKeys } from "./saml/security/types";

export async function sendProcessRetryDqRequest({
signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
}: {
signedRequest: SignedDqRequest;
samlCertsAndKeys: SamlCertsAndKeys;
patientId: string;
cxId: string;
index: number;
}): Promise<OutboundDocumentQueryResp> {
async function sendProcessDqRequest() {
const response = await sendSignedDqRequest({
request: signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
});
return await processDqResponse({
response,
});
}

return await executeWithRetries(sendProcessDqRequest, {
initialDelay: 3000,
maxAttempts: 3,
shouldRetry: isRetryable,
});
}

export async function sendProcessRetryDrRequest({
signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
}: {
signedRequest: SignedDrRequest;
samlCertsAndKeys: SamlCertsAndKeys;
patientId: string;
cxId: string;
index: number;
}): Promise<OutboundDocumentRetrievalResp> {
async function sendProcessDrRequest() {
const response = await sendSignedDrRequest({
request: signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
});
return await processDrResponse({
response,
});
}

return await executeWithRetries(sendProcessDrRequest, {
initialDelay: 3000,
maxAttempts: 3,
shouldRetry: isRetryable,
});
}

export async function createSignSendProcessXCPDRequest({
pdResponseUrl,
xcpdRequest,
Expand Down Expand Up @@ -61,7 +130,7 @@ export async function createSignSendProcessXCPDRequest({
}
}

export async function createSignSendProcessDQRequests({
export async function createSignSendProcessDqRequests({
dqResponseUrl,
dqRequestsGatewayV2,
samlCertsAndKeys,
Expand All @@ -78,18 +147,25 @@ export async function createSignSendProcessDQRequests({
bulkBodyData: dqRequestsGatewayV2,
samlCertsAndKeys,
});
const responses = await sendSignedDQRequests({
signedRequests,
samlCertsAndKeys,
patientId,
cxId,
});
const results = responses.map(response => {
return processDQResponse({
dqResponse: response,

const resultPromises = signedRequests.map(async (signedRequest, index) => {
return sendProcessRetryDqRequest({
signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
});
});
for (const result of results) {

const results = await Promise.allSettled(resultPromises);
const successfulResults = results
.filter(
(result): result is PromiseFulfilledResult<OutboundDocumentQueryResp> =>
result.status === "fulfilled"
)
.map(result => result.value);
for (const result of successfulResults) {
try {
await executeWithNetworkRetries(async () => axios.post(dqResponseUrl, result));
} catch (error) {
Expand All @@ -105,7 +181,7 @@ export async function createSignSendProcessDQRequests({
}
}

export async function createSignSendProcessDRRequests({
export async function createSignSendProcessDrRequests({
drResponseUrl,
drRequestsGatewayV2,
samlCertsAndKeys,
Expand All @@ -122,20 +198,26 @@ export async function createSignSendProcessDRRequests({
bulkBodyData: drRequestsGatewayV2,
samlCertsAndKeys,
});
const responses = await sendSignedDRRequests({
signedRequests,
samlCertsAndKeys,
patientId,
cxId,

const resultPromises = signedRequests.map(async (signedRequest, index) => {
return sendProcessRetryDrRequest({
signedRequest,
samlCertsAndKeys,
patientId,
cxId,
index,
});
});
const results = await Promise.all(
responses.map(async response => {
return await processDrResponse({
drResponse: response,
});
})
);
for (const result of results) {

const results = await Promise.allSettled(resultPromises);
const successfulResults = results
.filter(
(result): result is PromiseFulfilledResult<OutboundDocumentRetrievalResp> =>
result.status === "fulfilled"
)
.map(result => result.value);

for (const result of successfulResults) {
try {
await executeWithNetworkRetries(async () => axios.post(drResponseUrl, result));
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function storeXcpdResponses({
}
}

export async function storeDqResponses({
export async function storeDqResponse({
response,
outboundRequest,
gateway,
Expand Down Expand Up @@ -90,7 +90,7 @@ export async function storeDqResponses({
}
}

export async function storeDrResponses({
export async function storeDrResponse({
response,
outboundRequest,
gateway,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const expectedXcpdResponse: OutboundPatientDiscoveryRespSuccessfulSchema
},
],
},
iheGatewayV2: true,
};

export const expectedMultiNameAddressResponse: OutboundPatientDiscoveryRespSuccessfulSchema = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import fs from "fs";
import path from "path";
import { processDQResponse } from "../xca/process/dq-response";
import { processDqResponse } from "../xca/process/dq-response";
import { outboundDqRequest, expectedDqDocumentReference } from "./constants";

describe("processDQResponse", () => {
describe("processDqResponse", () => {
it("should process the successful DQ response correctly", async () => {
const xmlString = fs.readFileSync(path.join(__dirname, "xmls/dq_multiple_docs.xml"), "utf8");
const response = processDQResponse({
dqResponse: {
const response = await processDqResponse({
response: {
response: xmlString,
success: true,
gateway: outboundDqRequest.gateway,
Expand All @@ -21,8 +21,8 @@ describe("processDQResponse", () => {
});
it("should process the empty DQ response correctly", async () => {
const xmlString = fs.readFileSync(path.join(__dirname, "xmls/dq_empty.xml"), "utf8");
const response = processDQResponse({
dqResponse: {
const response = await processDqResponse({
response: {
response: xmlString,
success: true,
gateway: outboundDqRequest.gateway,
Expand All @@ -34,8 +34,8 @@ describe("processDQResponse", () => {

it("should process the DQ response with registry error correctly", async () => {
const xmlString = fs.readFileSync(path.join(__dirname, "xmls/dq_error.xml"), "utf8");
const response = processDQResponse({
dqResponse: {
const response = await processDqResponse({
response: {
response: xmlString,
success: true,
gateway: outboundDqRequest.gateway,
Expand All @@ -48,8 +48,8 @@ describe("processDQResponse", () => {
it("should process response that is not a string correctly", async () => {
const randomResponse = "This is a bad response and is not xml";

const response = processDQResponse({
dqResponse: {
const response = await processDqResponse({
response: {
success: true,
response: randomResponse,
gateway: outboundDqRequest.gateway,
Expand Down
Loading

0 comments on commit d38cf85

Please sign in to comment.