diff --git a/modules/api-svc/package.json b/modules/api-svc/package.json index e2083aeb9..f17c47c77 100644 --- a/modules/api-svc/package.json +++ b/modules/api-svc/package.json @@ -92,8 +92,8 @@ "ws": "^8.8.1" }, "devDependencies": { - "@babel/core": "^7.19.0", - "@babel/preset-env": "^7.19.0", + "@babel/core": "^7.19.1", + "@babel/preset-env": "^7.19.1", "@redocly/openapi-cli": "^1.0.0-beta.94", "@types/jest": "^29.0.2", "babel-jest": "^29.0.3", diff --git a/modules/outbound-command-event-handler/package.json b/modules/outbound-command-event-handler/package.json index 7f0059f70..d3adf91ad 100644 --- a/modules/outbound-command-event-handler/package.json +++ b/modules/outbound-command-event-handler/package.json @@ -53,9 +53,9 @@ }, "devDependencies": { "@types/convict": "^6.1.1", - "@types/express": "^4.17.13", + "@types/express": "^4.17.14", "@types/jest": "^29.0.2", - "@types/node": "^18.7.17", + "@types/node": "^18.7.18", "@types/node-cache": "^4.2.5", "@types/supertest": "^2.0.12", "@types/swagger-ui-express": "^4.1.3", @@ -69,7 +69,7 @@ "npm-check-updates": "^16.1.2", "replace": "^1.2.1", "standard-version": "^9.5.0", - "ts-jest": "^29.0.0", + "ts-jest": "^29.0.1", "ts-node": "^10.9.1", "typescript": "^4.8.3" }, diff --git a/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/index.ts b/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/index.ts index d6fffd234..21c348cb9 100644 --- a/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/index.ts +++ b/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/index.ts @@ -27,7 +27,7 @@ import * as ProcessSDKOutboundBulkPartyInfoRequestHandler from './process_sdk_ou import * as ProcessSDKOutboundBulkPartyInfoRequestCompleteHandler from './process_sdk_outbound_bulk_party_info_request_complete'; import * as ProcessPartyInfoCallbackHandler from './process_party_info_callback'; import * as ProcessSDKOutboundBulkAcceptPartyInfoHandler from './process_sdk_outbound_bulk_accept_party_info'; -import * as ProcessBulkQuotesCallbackHandler from './process-bulk-quotes-callback'; +import * as ProcessBulkQuotesCallbackHandler from './process_bulk_quotes_callback'; import * as ProcessSDKOutboundBulkQuotesRequestHandler from './process_sdk_outbound_bulk_quotes_request'; import { CommandEvent } from '@mojaloop/sdk-scheme-adapter-private-shared-lib'; diff --git a/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process-bulk-quotes-callback.ts b/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process_bulk_quotes_callback.ts similarity index 79% rename from modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process-bulk-quotes-callback.ts rename to modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process_bulk_quotes_callback.ts index 87fa388cf..b357fb2d6 100755 --- a/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process-bulk-quotes-callback.ts +++ b/modules/outbound-command-event-handler/src/domain/bulk_transaction_agg/handlers/process_bulk_quotes_callback.ts @@ -50,30 +50,45 @@ export async function handleProcessBulkQuotesCallbackCmdEvt( processBulkQuotesCallbackMessage.batchId, ); const bulkQuotesResult = processBulkQuotesCallbackMessage.bulkQuotesResult; - // TODO: There is no currentState in the bulkQuotesResult specification, but its there in the response. Need to discuss on this. - // if(bulkQuotesResult.currentState && bulkQuotesResult.currentState === 'COMPLETED') { - if(bulkQuotesResult.individualQuoteResults.length > 0) { - bulkBatch.setState(BulkBatchInternalState.AGREEMENT_SUCCESS); + + // If individual quote result contains `lastError` the individual transfer state should be AGREEMENT_FAILED. + // bulkQuotesResult.currentState === 'ERROR_OCCURRED' necessitates erroring out all individual transfers in that bulk batch. + if(bulkQuotesResult.currentState && + bulkQuotesResult.currentState === 'COMPLETED') { + bulkBatch.setState(BulkBatchInternalState.AGREEMENT_COMPLETED); bulkTransactionAgg.incrementBulkQuotesSuccessCount(); + + // Iterate through items in batch and update the individual states + for await (const quoteResult of bulkQuotesResult.individualQuoteResults) { + if(quoteResult.quoteId && !quoteResult.lastError) { + const individualTransferId = bulkBatch.getReferenceIdForQuoteId(quoteResult.quoteId); + const individualTransfer = await bulkTransactionAgg.getIndividualTransferById(individualTransferId); + individualTransfer.setTransferState(IndividualTransferInternalState.AGREEMENT_SUCCESS); + individualTransfer.setQuoteResponse(quoteResult); + await bulkTransactionAgg.setIndividualTransferById(individualTransfer.id, individualTransfer); + } else { + const individualTransferId = bulkBatch.getReferenceIdForQuoteId(quoteResult.quoteId); + const individualTransfer = await bulkTransactionAgg.getIndividualTransferById(individualTransferId); + individualTransfer.setTransferState(IndividualTransferInternalState.AGREEMENT_FAILED); + individualTransfer.setQuoteResponse(quoteResult); + await bulkTransactionAgg.setIndividualTransferById(individualTransfer.id, individualTransfer); + } + } + // If the bulk quote is in any other state, update the bulk batch and all individual transfers + // to AGREEMENT_FAILED. } else { bulkBatch.setState(BulkBatchInternalState.AGREEMENT_FAILED); bulkTransactionAgg.incrementBulkQuotesFailedCount(); - } - bulkBatch.setBulkQuotesResponse(bulkQuotesResult); - await bulkTransactionAgg.setBulkBatchById(bulkBatch.id, bulkBatch); - - // Iterate through items in batch and update the individual states - for await (const quoteResult of bulkQuotesResult.individualQuoteResults) { - // TODO: quoteId should be required field in the quoteResponse. But it is optional now, so if it is empty we are ignoring the result for now - if(quoteResult.quoteId) { - const individualTransferId = bulkBatch.getReferenceIdForQuoteId(quoteResult.quoteId); + const individualTransferIds = Object.values(bulkBatch.quoteIdReferenceIdMap); + for await (const individualTransferId of individualTransferIds) { const individualTransfer = await bulkTransactionAgg.getIndividualTransferById(individualTransferId); - individualTransfer.setTransferState(IndividualTransferInternalState.AGREEMENT_SUCCESS); - individualTransfer.setQuoteResponse(quoteResult); + individualTransfer.setTransferState(IndividualTransferInternalState.AGREEMENT_FAILED); await bulkTransactionAgg.setIndividualTransferById(individualTransfer.id, individualTransfer); } } + bulkBatch.setBulkQuotesResponse(bulkQuotesResult); + await bulkTransactionAgg.setBulkBatchById(bulkBatch.id, bulkBatch); const bulkQuotesCallbackProcessedDmEvt = new BulkQuotesCallbackProcessedDmEvt({ bulkId: bulkTransactionAgg.bulkId, @@ -93,7 +108,7 @@ export async function handleProcessBulkQuotesCallbackCmdEvt( if(bulkQuotesTotalCount === (bulkQuotesSuccessCount + bulkQuotesFailedCount)) { // Update global state "AGREEMENT_COMPLETED" await bulkTransactionAgg.setGlobalState(BulkTransactionInternalState.AGREEMENT_COMPLETED); - + // Send the domain message SDKOutboundBulkQuotesRequestProcessed const sdkOutboundBulkQuotesRequestProcessedDmEvt = new SDKOutboundBulkQuotesRequestProcessedDmEvt({ bulkId: bulkTransactionAgg.bulkId, diff --git a/modules/outbound-command-event-handler/test/integration/application/outbound_command_event_handler.test.ts b/modules/outbound-command-event-handler/test/integration/application/outbound_command_event_handler.test.ts index 17c499ed1..ecff20e12 100644 --- a/modules/outbound-command-event-handler/test/integration/application/outbound_command_event_handler.test.ts +++ b/modules/outbound-command-event-handler/test/integration/application/outbound_command_event_handler.test.ts @@ -29,34 +29,29 @@ import { ILogger } from "@mojaloop/logging-bc-public-types-lib"; import { SDKSchemeAdapter } from '@mojaloop/api-snippets'; import { - CommandEvent, ICommandEventData, DomainEvent, - KafkaCommandEventProducer, IKafkaEventProducerOptions, KafkaDomainEventConsumer, IKafkaEventConsumerOptions, - ProcessSDKOutboundBulkRequestCmdEvt, - ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt, - ProcessSDKOutboundBulkPartyInfoRequestCmdEvt, - ProcessPartyInfoCallbackCmdEvt, - IProcessSDKOutboundBulkRequestCmdEvtData, + DomainEvent, + IKafkaEventConsumerOptions, + IKafkaEventProducerOptions, IProcessPartyInfoCallbackCmdEvtData, IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData, IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData, - IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData, - ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt, - IProcessSDKOutboundBulkQuotesRequestCmdEvtData, - ProcessSDKOutboundBulkQuotesRequestCmdEvt, - IProcessBulkQuotesCallbackCmdEvtData, - ProcessBulkQuotesCallbackCmdEvt, - RedisBulkTransactionStateRepo, + IProcessSDKOutboundBulkRequestCmdEvtData, IRedisBulkTransactionStateRepoOptions, - BulkBatchState, - BulkBatchInternalState, + KafkaCommandEventProducer, + KafkaDomainEventConsumer, + ProcessPartyInfoCallbackCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt, + ProcessSDKOutboundBulkRequestCmdEvt, + RedisBulkTransactionStateRepo, } from '@mojaloop/sdk-scheme-adapter-private-shared-lib' import { randomUUID } from "crypto"; // Tests can timeout in a CI pipeline so giving it leeway -jest.setTimeout(30000) +jest.setTimeout(10000) const logger: ILogger = new DefaultLogger('bc', 'appName', 'appVersion'); //TODO: parameterize the names here -const messageTimeout = 5000; +const messageTimeout = 2000; // Setup for Kafka Producer const commandEventProducerOptions: IKafkaEventProducerOptions = { @@ -831,492 +826,6 @@ describe("Tests for Outbound Command Event Handler", () => { expect(hasAcceptPartyEvent).toBeTruthy(); }); - test("9. Given inbound command event ProcessSDKOutboundBulkAcceptPartyInfo is received \ - Then the logic should loop through individual transfer in the bulk request \ - And update the individual transfer state to DISCOVERY_ACCEPTED or DISCOVERY_REJECTED based on the value in the incoming event \ - And update the overall global state to DISCOVERY_ACCEPTANCE_COMPLETED \ - And outbound event SDKOutboundBulkAcceptPartyInfoProcessed should be published", async () => { - - //Publish initial message so that it is stored internally in redis - const bulkTransactionId = randomUUID(); - const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { - bulkHomeTransactionID: "string", - bulkTransactionId: bulkTransactionId, - options: { - onlyValidateParty: true, - autoAcceptParty: { - enabled: false - }, - autoAcceptQuote: { - enabled: true, - }, - skipPartyLookup: false, - synchronous: true, - bulkExpiration: "2016-05-24T08:38:08.699-04:00" - }, - from: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212", - fspId: "string", - }, - }, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "123.45", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551213" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "678.91", - } - ] - } - const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { - bulkRequest, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - const individualTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); - - - // Command event for bulk party info request completed - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { - bulkId: bulkTransactionId, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Command event for bulk accept party info completed - const bulkTransactionContinuationAcceptParty: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionContinuationAcceptParty = { - bulkHomeTransactionID: bulkTransactionId, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[0], - acceptParty: true - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[1], - acceptParty: true - } - ] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvtData: IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { - bulkId: bulkTransactionId, - bulkTransactionContinuationAcceptParty, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvt: ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt(processSDKOutboundBulkAcceptPartyInfoCmdEvtData); - await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCmdEvt); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Check that the global state of individual transfers in bulk to be RECEIVED - const bulkState = await bulkTransactionEntityRepo.load(bulkTransactionId); - expect(bulkState.state).toBe('DISCOVERY_ACCEPTANCE_COMPLETED'); - - //Check that the state of individual transfers in bulk to be RECEIVED - let individualTransferData = await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, individualTransferIds[0]); - console.log('individualTransferData:', individualTransferData); - expect(individualTransferData.state).toBe('DISCOVERY_ACCEPTED'); - - individualTransferData = await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, individualTransferIds[1]); - console.log('individualTransferData:', individualTransferData); - expect(individualTransferData.state).toBe('DISCOVERY_REJECTED'); - - // Check domain events published to kafka - const hasAcceptPartyEvent = (domainEvents.find((e) => e.getName() === 'SDKOutboundBulkAcceptPartyInfoProcessedDmEvt')); - expect(hasAcceptPartyEvent).toBeTruthy(); - - }); - - // // TESTS FOR QUOTE PROCESSING - - // Functionality for this feature is not completed yet. Waiting on development to be complete - test("10. When inbound command event ProcessSDKOutboundBulkQuotesRequest is received\ - Then the logic should update the global state to AGREEMENT_PROCESSING, \ - And create batches based on FSP that has DISCOVERY_ACCEPTED state \ - And also has config maxEntryConfigPerBatch \ - And publish BulkQuotesRequested per each batch \ - And update the state of each batch to AGREEMENT_PROCESSING.", async () => { - - //Publish initial message so that it is stored internally in redis - const bulkTransactionId = randomUUID(); - const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { - bulkHomeTransactionID: "string", - bulkTransactionId: bulkTransactionId, - options: { - onlyValidateParty: true, - autoAcceptParty: { - enabled: false - }, - autoAcceptQuote: { - enabled: true, - }, - skipPartyLookup: false, - synchronous: true, - bulkExpiration: "2016-05-24T08:38:08.699-04:00" - }, - from: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212", - fspId: "string", - }, - }, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212", - fspId: "fsp1" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "12.34", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551213", - fspId: "fsp1" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "23.45", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551214", - fspId: "fsp2" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "34.56", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551215", - fspId: "fsp2" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "45.67", - } - ] - } - const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { - bulkRequest, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - const individualTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); - - - // Command event for bulk party info request completed - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { - bulkId: bulkTransactionId, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Command event for bulk accept party info completed - const bulkTransactionContinuationAcceptParty: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionContinuationAcceptParty = { - bulkHomeTransactionID: bulkTransactionId, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[0], - acceptParty: true - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[1], - acceptParty: true // This needs to be set to false - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[2], - acceptParty: true - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[3], - acceptParty: true // This needs to be set to false - } - ] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvtData: IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { - bulkId: bulkTransactionId, - bulkTransactionContinuationAcceptParty, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvt: ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt(processSDKOutboundBulkAcceptPartyInfoCmdEvtData); - await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCmdEvt); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Publish ProcessSDKOutboundBulkQuotesRequestDmEvt - const processSDKOutboundBulkQuotesRequestCmdEvtData: IProcessSDKOutboundBulkQuotesRequestCmdEvtData = { - bulkId: bulkTransactionId, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkQuotesRequestCmdEvt: ProcessSDKOutboundBulkQuotesRequestCmdEvt = new ProcessSDKOutboundBulkQuotesRequestCmdEvt(processSDKOutboundBulkQuotesRequestCmdEvtData); - await producer.sendCommandEvent(processSDKOutboundBulkQuotesRequestCmdEvt); - await new Promise(resolve => setTimeout(resolve, 1000)); - - - //Check that the global state of individual transfers in bulk to be AGREEMENT_PROCESSING - const bulkState = await bulkTransactionEntityRepo.load(bulkTransactionId); - expect(bulkState.state).toBe('AGREEMENT_PROCESSING'); - - //Check the state in redis for each batch to be as AGREEMENT_PROCESSING - const bulkBatchIds: Array = await bulkTransactionEntityRepo.getAllBulkBatchIds(bulkTransactionId); - let batchQuote: BulkBatchState = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, bulkBatchIds[0]); - expect(batchQuote.state).toBe(BulkBatchInternalState.AGREEMENT_PROCESSING); - - // Check BulkQuotesRequestedDmEvt domain events published to kafka - let bulkQuotesBatchesArray: Array = [] - bulkQuotesBatchesArray = (domainEvents.filter((e) => e.getName() === 'BulkQuotesRequestedDmEvt')); - expect(bulkQuotesBatchesArray.length).toBe(2); - - }); - - // skipped because of bug https://github.com/mojaloop/project/issues/2922 - test.skip("11. Given the callback for quote batch is successful \ - And the callback has a combination of success and failed responses for individual quotes \ - When Inbound command event ProcessBulkQuotesCallback is received \ - Then the logic should update the individual batch state to AGREEMENT_COMPLETED, \ - And for each individual quote in the batch , the state should be upadted to AGREEMENT_SUCCESS or AGREEMENT_FAILED accordingly \ - And the individual quote data in redis should be updated with the response \ - And domain event BulkQuotesProcessed should be published", async () => { - - //Publish initial message so that it is stored internally in redis - const bulkTransactionId = randomUUID(); - const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { - bulkHomeTransactionID: "string", - bulkTransactionId: bulkTransactionId, - options: { - onlyValidateParty: true, - autoAcceptParty: { - enabled: false - }, - autoAcceptQuote: { - enabled: true, - }, - skipPartyLookup: false, - synchronous: true, - bulkExpiration: "2016-05-24T08:38:08.699-04:00" - }, - from: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212", - fspId: "string", - }, - }, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551212", - fspId: "fsp1" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "12.34", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551213", - fspId: "fsp1" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "23.45", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551214", - fspId: "fsp2" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "34.56", - }, - { - homeTransactionId: randomUUID(), - to: { - partyIdInfo: { - partyIdType: "MSISDN", - partyIdentifier: "16135551215", - fspId: "fsp2" - }, - }, - amountType: "SEND", - currency: "USD", - amount: "45.67", - } - ] - } - const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { - bulkRequest, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - const individualTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); - - - // Command event for bulk party info request completed - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { - bulkId: bulkTransactionId, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData); - await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Command event for bulk accept party info completed - const bulkTransactionContinuationAcceptParty: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionContinuationAcceptParty = { - bulkHomeTransactionID: bulkTransactionId, - individualTransfers: [ - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[0], - acceptParty: true - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[1], - acceptParty: true // This needs to be set to false - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[2], - acceptParty: true - }, - { - homeTransactionId: randomUUID(), - transactionId: individualTransferIds[3], - acceptParty: true // This needs to be set to false - } - ] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvtData: IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { - bulkId: bulkTransactionId, - bulkTransactionContinuationAcceptParty, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkAcceptPartyInfoCmdEvt: ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt(processSDKOutboundBulkAcceptPartyInfoCmdEvtData); - await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCmdEvt); - await new Promise(resolve => setTimeout(resolve, 1000)); - - //Publish ProcessSDKOutboundBulkQuotesRequestDmEvt domain event - const processSDKOutboundBulkQuotesRequestCmdEvtData: IProcessSDKOutboundBulkQuotesRequestCmdEvtData = { - bulkId: bulkTransactionId, - timestamp: Date.now(), - headers: [] - } - const processSDKOutboundBulkQuotesRequestCmdEvt: ProcessSDKOutboundBulkQuotesRequestCmdEvt = new ProcessSDKOutboundBulkQuotesRequestCmdEvt(processSDKOutboundBulkQuotesRequestCmdEvtData); - await producer.sendCommandEvent(processSDKOutboundBulkQuotesRequestCmdEvt); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Publish ProcessBulkQuotesCallback domain event - const processBulkQuotesCallbackCmdEvtData: IProcessBulkQuotesCallbackCmdEvtData = { - bulkId: bulkTransactionId, - content: { - batchId: '12', - bulkQuoteId: '34', - bulkQuotesResult: { - bulkQuoteId: '34', - currentState: "COMPLETED", - individualQuoteResults: [{ - quoteId: "12", - transferAmount: { - currency: "USD", - amount: "10" - }, - ilpPacket: "", - condition: "" - }] - } - }, - timestamp: Date.now(), - headers: [] - } - const processBulkQuotesCallbackCmdEvt: ProcessBulkQuotesCallbackCmdEvt = new ProcessBulkQuotesCallbackCmdEvt(processBulkQuotesCallbackCmdEvtData); - await producer.sendCommandEvent(processBulkQuotesCallbackCmdEvt); - - //Check that the global state of individual transfers in bulk to be RECEIVED - const bulkState = await bulkTransactionEntityRepo.load(bulkTransactionId); - expect(bulkState.state).toBe('DISCOVERY_ACCEPTANCE_COMPLETED'); - - }); - // Functionality for this feature is not completed yet. Waiting on development to be complete test("12. Given autoAcceptQuote setting is false \ When inbound command event ProcessSDKOutboundBulkAutoAcceptQuote is received \ @@ -1417,6 +926,4 @@ describe("Tests for Outbound Command Event Handler", () => { }); - - }); diff --git a/modules/outbound-command-event-handler/test/integration/application/process_bulk_quotes_callback.test.ts b/modules/outbound-command-event-handler/test/integration/application/process_bulk_quotes_callback.test.ts new file mode 100644 index 000000000..ba9b332df --- /dev/null +++ b/modules/outbound-command-event-handler/test/integration/application/process_bulk_quotes_callback.test.ts @@ -0,0 +1,535 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list (alphabetical ordering) of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + * Modusbox + + - Sridevi Miriyala + - Kevin Leyow + -------------- + ******/ + +"use strict"; + +import { DefaultLogger } from "@mojaloop/logging-bc-client-lib"; +import { ILogger } from "@mojaloop/logging-bc-public-types-lib"; +import { SDKSchemeAdapter } from "@mojaloop/api-snippets"; + +import { + BulkBatchInternalState, + BulkTransactionInternalState, + DomainEvent, + IKafkaEventConsumerOptions, + IKafkaEventProducerOptions, + IndividualTransferInternalState, + IProcessBulkQuotesCallbackCmdEvtData, + IProcessPartyInfoCallbackCmdEvtData, + IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData, + IProcessSDKOutboundBulkQuotesRequestCmdEvtData, + IProcessSDKOutboundBulkRequestCmdEvtData, + IRedisBulkTransactionStateRepoOptions, + KafkaCommandEventProducer, + KafkaDomainEventConsumer, + ProcessBulkQuotesCallbackCmdEvt, + ProcessPartyInfoCallbackCmdEvt, + ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt, + ProcessSDKOutboundBulkQuotesRequestCmdEvt, + ProcessSDKOutboundBulkRequestCmdEvt, + RedisBulkTransactionStateRepo, +} from "@mojaloop/sdk-scheme-adapter-private-shared-lib" +import { randomUUID } from "crypto"; + +// Tests can timeout in a CI pipeline so giving it leeway +jest.setTimeout(30000) + +const logger: ILogger = new DefaultLogger('bc', 'appName', 'appVersion'); //TODO: parameterize the names here +const messageTimeout = 2000; + +// Setup for Kafka Producer +const commandEventProducerOptions: IKafkaEventProducerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topic: 'topic-sdk-outbound-command-events' +} +const producer = new KafkaCommandEventProducer(commandEventProducerOptions, logger) + +// Setup for Kafka Consumer +const domainEventConsumerOptions: IKafkaEventConsumerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topics: ['topic-sdk-outbound-domain-events'], + groupId: "domain_events_consumer_client_id" +} +var domainEvents: Array = [] +const _messageHandler = async (message: DomainEvent): Promise => { + console.log('Domain Message: ', message); + domainEvents.push(message); +} +const consumer = new KafkaDomainEventConsumer(_messageHandler.bind(this), domainEventConsumerOptions, logger) + +// Setup for Redis access +const bulkTransactionEntityRepoOptions: IRedisBulkTransactionStateRepoOptions = { + connStr: 'redis://localhost:6379' +} +const bulkTransactionEntityRepo = new RedisBulkTransactionStateRepo(bulkTransactionEntityRepoOptions, logger); + + +describe("Tests for ProcessBulkQuotesCallback Event Handler", () => { + + beforeEach(async () => { + domainEvents = []; + }); + + beforeAll(async () => { + await producer.init(); + await consumer.init(); + await consumer.start(); + await bulkTransactionEntityRepo.init(); + }); + + afterAll(async () => { + await producer.destroy(); + await consumer.destroy(); + await bulkTransactionEntityRepo.destroy(); + }); + + test("Given the callback for quote batch is successful \ + And the callback has a combination of success and failed responses for individual quotes \ + When Inbound command event ProcessBulkQuotesCallback is received \ + Then the logic should update the individual batch state to AGREEMENT_COMPLETED or AGREEMENT_FAILED, \ + And for each individual transfers in the batch, the state could be AGREEMENT_SUCCESS or AGREEMENT_FAILED accordingly \ + And the individual quote data in redis should be updated with the response \ + And domain event BulkQuotesCallbackProcessed should be published \ + And domain event SDKOutboundBulkQuotesRequestProcessed should be published", async () => { + // Publish this message so that it is stored internally in redis + const bulkTransactionId = randomUUID(); + const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { + bulkHomeTransactionID: "string", + bulkTransactionId: bulkTransactionId, + options: { + onlyValidateParty: true, + autoAcceptParty: { + enabled: true + }, + autoAcceptQuote: { + enabled: true, + }, + skipPartyLookup: false, + synchronous: true, + bulkExpiration: "2016-05-24T08:38:08.699-04:00" + }, + from: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551212", + fspId: "string", + }, + }, + individualTransfers: [ + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "1" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "1", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "2" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "2", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "3" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "3", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "4" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "4", + } + ] + } + const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { + bulkRequest, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); + await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + const bulkPartyInfoRequestCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const bulkPartyInfoRequestCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCmdEvt( + bulkPartyInfoRequestCommandEventData + ); + await producer.sendCommandEvent(bulkPartyInfoRequestCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Get the randomly generated transferIds for the callback + const randomGeneratedTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); + + // The transfer ids are unordered so using the transfer amounts to identify each transfer + // so we can reference the proper transferId in subsequent callbacks + const amountList: string[] = [] + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[0])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[1])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[2])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[3])).request.amount) + + // Simulate the domain handler sending the command handler PProcessPartyInfoCallback messages + // for each individual transfer + const processPartyInfoCallbackMessageData1: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('1')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + const processPartyInfoCallbackMessageData2: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('2')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + const processPartyInfoCallbackMessageData3: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('3')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '11111111111', + fspId: 'differentfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + const processPartyInfoCallbackMessageData4: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('4')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '222222222222', + fspId: 'differentfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + + const processPartyInfoCallbackMessageObjOne = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData1); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjOne); + const processPartyInfoCallbackMessageObjTwo = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData2); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjTwo); + const processPartyInfoCallbackMessageObjThree = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData3); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjThree); + const processPartyInfoCallbackMessageObjFour = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData4); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjFour); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Simulate the domain handler sending the command handler ProcessSDKOutboundBulkPartyInfoRequestComplete message + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData : IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt( + processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Command event for bulk accept party info + const processSDKOutboundBulkAcceptPartyInfoCommandEventData : IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { + bulkId: bulkTransactionId, + bulkTransactionContinuationAcceptParty: { + bulkHomeTransactionID: 'string', + individualTransfers: [ + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('1')], + acceptParty: true + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('2')], + acceptParty: true + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('3')], + acceptParty: true + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('4')], + acceptParty: true + } + ] + }, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkAcceptPartyInfoCommandEventObj = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt( + processSDKOutboundBulkAcceptPartyInfoCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Simulate domain handler sending command event for bulk quotes request + const processSDKOutboundBulkQuotesRequestCommandEventData : IProcessSDKOutboundBulkQuotesRequestCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkQuotesRequestCommandEventObj = new ProcessSDKOutboundBulkQuotesRequestCmdEvt( + processSDKOutboundBulkQuotesRequestCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkQuotesRequestCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Check that bulk batches have been created. + // One should be for receiverfsp and another for differentfsp + const bulkBatchIds = await bulkTransactionEntityRepo.getAllBulkBatchIds(bulkTransactionId); + expect(bulkBatchIds[0]).toBeDefined(); + expect(bulkBatchIds[1]).toBeDefined(); + + const bulkBatchOne = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, bulkBatchIds[0]); + const bulkBatchTwo = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, bulkBatchIds[1]); + + // Bulk batch ids are unordered so check the quotes for the intended fsp + // so we can send proper callbacks + let receiverFspBatch; + let differentFspBatch; + if (bulkBatchOne.bulkQuotesRequest.individualQuotes[0].to.fspId == 'receiverfsp') { + receiverFspBatch = bulkBatchOne + differentFspBatch = bulkBatchTwo + } else { + receiverFspBatch = bulkBatchTwo + differentFspBatch = bulkBatchOne + } + + const bulkQuoteId = randomUUID(); + + // Simulate the domain handler sending ProcessBulkQuotesCallback to command handler + // for receiverfsp batch + const processBulkQuotesCallbackCommandEventDataReceiverFsp : IProcessBulkQuotesCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + batchId: receiverFspBatch.id, + bulkQuoteId: bulkQuoteId, + bulkQuotesResult: { + bulkQuoteId: bulkQuoteId, + currentState: 'COMPLETED', + individualQuoteResults: [ + { + quoteId: receiverFspBatch.bulkQuotesRequest.individualQuotes[0].quoteId, + transferAmount: { + currency: 'USD', + amount: '1', + }, + ilpPacket: 'string', + condition: 'string' + }, + { + quoteId: receiverFspBatch.bulkQuotesRequest.individualQuotes[1].quoteId, + transferAmount: { + currency: 'USD', + amount: '2', + }, + ilpPacket: 'string', + condition: 'string', + lastError: { + httpStatusCode: 500, + mojaloopError: { + errorInformation:{ + errorCode: '0000', + errorDescription: 'some-error' + } + } + } + } + ] + } + }, + timestamp: Date.now(), + headers: [] + } + const processBulkQuotesCallbackCommandEventObjReceiverFsp = new ProcessBulkQuotesCallbackCmdEvt( + processBulkQuotesCallbackCommandEventDataReceiverFsp + ); + await producer.sendCommandEvent(processBulkQuotesCallbackCommandEventObjReceiverFsp); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + const bulkQuoteIdDifferentFsp = randomUUID(); + + // Simulate the domain handler sending ProcessBulkQuotesCallback to command handler + // for differentfsp batch with empty results + // Currently only empty individualQuoteResults result in AGREEMENT_FAILED for bulk batch state + const processBulkQuotesCallbackCommandEventDataDifferentFsp : IProcessBulkQuotesCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + batchId: differentFspBatch.id, + bulkQuoteId: bulkQuoteIdDifferentFsp, + bulkQuotesResult: { + bulkQuoteId: bulkQuoteIdDifferentFsp, + currentState: 'ERROR_OCCURRED', + individualQuoteResults: [] + } + }, + timestamp: Date.now(), + headers: [] + } + const processBulkQuotesCallbackCommandEventObjDifferentFsp = new ProcessBulkQuotesCallbackCmdEvt( + processBulkQuotesCallbackCommandEventDataDifferentFsp + ); + await producer.sendCommandEvent(processBulkQuotesCallbackCommandEventObjDifferentFsp); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Check that the state of bulk batch for receiverfsp to be AGREEMENT_COMPLETED + const postBulkBatchReceiverFsp = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, receiverFspBatch.id); + expect(postBulkBatchReceiverFsp.state).toBe(BulkBatchInternalState.AGREEMENT_COMPLETED); + + // Check that bulkQuoteResponse state has been updated to COMPLETED + expect(postBulkBatchReceiverFsp.bulkQuotesResponse!.currentState).toEqual("COMPLETED") + + // Check that the state of bulk batch for differentfsp to be AGREEMENT_FAILED + const postBulkBatchDifferentFsp = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, differentFspBatch.id); + expect(postBulkBatchDifferentFsp.state).toBe(BulkBatchInternalState.AGREEMENT_FAILED); + + // Check that bulkQuoteResponse state has been updated to COMPLETED + expect(postBulkBatchDifferentFsp.bulkQuotesResponse!.currentState).toEqual("ERROR_OCCURRED") + + // Check the individual transfer state whose quote was successful in a successful bulk quote batch + expect((await bulkTransactionEntityRepo.getIndividualTransfer( + bulkTransactionId, + randomGeneratedTransferIds[amountList.indexOf('1')])).state) + .toBe(IndividualTransferInternalState.AGREEMENT_SUCCESS); + + // Check the individual transfer state whose quote was errored in a successful bulk quote batch + expect((await bulkTransactionEntityRepo.getIndividualTransfer( + bulkTransactionId, + randomGeneratedTransferIds[amountList.indexOf('2')])).state) + .toBe(IndividualTransferInternalState.AGREEMENT_FAILED); + + // Check the individual transfer state whose quotes were in an errored bulk quote batch + expect((await bulkTransactionEntityRepo.getIndividualTransfer( + bulkTransactionId, + randomGeneratedTransferIds[amountList.indexOf('3')])).state) + .toBe(IndividualTransferInternalState.AGREEMENT_FAILED); + expect((await bulkTransactionEntityRepo.getIndividualTransfer( + bulkTransactionId, + randomGeneratedTransferIds[amountList.indexOf('4')])).state) + .toBe(IndividualTransferInternalState.AGREEMENT_FAILED); + + // Now that all the bulk batches have reached a final state check the global state + // Check that the global state of bulk to be AGREEMENT_COMPLETED + const bulkStateAgreementCompleted = await bulkTransactionEntityRepo.load(bulkTransactionId); + expect(bulkStateAgreementCompleted.state).toBe(BulkTransactionInternalState.AGREEMENT_COMPLETED); + + // Check that command handler published BulkQuotesCallbackProcessed message + const hasBulkQuotesCallbackProcessed = (domainEvents.find((e) => e.getName() === 'BulkQuotesCallbackProcessedDmEvt')); + expect(hasBulkQuotesCallbackProcessed).toBeTruthy(); + + // Check that command handler published SDKOutboundBulkQuotesRequestProcessed message + const hasSDKOutboundBulkQuotesRequestProcessed = (domainEvents.find((e) => e.getName() === 'SDKOutboundBulkQuotesRequestProcessedDmEvt')); + expect(hasSDKOutboundBulkQuotesRequestProcessed).toBeTruthy(); + }); + + // Skipping this since there is no easy way to programmatically control the + // maxEntryConfigPerBatch config value atm. + test.skip("Given the callback for quote batch is successful \ + And the callback has a combination of success and failed responses for individual quotes \ + When Inbound command event ProcessBulkQuotesCallback is received \ + Then the logic should update the individual batch state to AGREEMENT_COMPLETED, \ + And also splits quotes into batches using maxEntryConfigPerBatch \ + And for each individual quote in the batch, the state should be updated to AGREEMENT_SUCCESS or AGREEMENT_FAILED accordingly \ + And the individual quote data in redis should be updated with the response \ + And domain event BulkQuotesCallbackProcessed should be published \ + And domain event SDKOutboundBulkQuotesRequestProcessed should be published", + async () => {}); +}); diff --git a/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_accept_party_info.test.ts b/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_accept_party_info.test.ts new file mode 100644 index 000000000..aa0b2f7d7 --- /dev/null +++ b/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_accept_party_info.test.ts @@ -0,0 +1,295 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list (alphabetical ordering) of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + * Modusbox + + - Sridevi Miriyala + - Kevin Leyow + -------------- + ******/ + +"use strict"; + +import { DefaultLogger } from "@mojaloop/logging-bc-client-lib"; +import { ILogger } from "@mojaloop/logging-bc-public-types-lib"; +import { SDKSchemeAdapter } from '@mojaloop/api-snippets'; + +import { + BulkTransactionInternalState, + DomainEvent, + IKafkaEventConsumerOptions, + IKafkaEventProducerOptions, + IndividualTransferInternalState, + IProcessPartyInfoCallbackCmdEvtData, + IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData, + IProcessSDKOutboundBulkRequestCmdEvtData, + IRedisBulkTransactionStateRepoOptions, + KafkaCommandEventProducer, + KafkaDomainEventConsumer, + ProcessPartyInfoCallbackCmdEvt, + ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt, + ProcessSDKOutboundBulkRequestCmdEvt, + RedisBulkTransactionStateRepo, +} from '@mojaloop/sdk-scheme-adapter-private-shared-lib' +import { randomUUID } from "crypto"; + +// Tests can timeout in a CI pipeline so giving it leeway +jest.setTimeout(20000) + +const logger: ILogger = new DefaultLogger('bc', 'appName', 'appVersion'); //TODO: parameterize the names here +const messageTimeout = 2000; + +// Setup for Kafka Producer +const commandEventProducerOptions: IKafkaEventProducerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topic: 'topic-sdk-outbound-command-events' +} +const producer = new KafkaCommandEventProducer(commandEventProducerOptions, logger) + +// Setup for Kafka Consumer +const domainEventConsumerOptions: IKafkaEventConsumerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topics: ['topic-sdk-outbound-domain-events'], + groupId: "domain_events_consumer_client_id" +} +var domainEvents: Array = [] +const _messageHandler = async (message: DomainEvent): Promise => { + console.log('Domain Message: ', message); + domainEvents.push(message); +} +const consumer = new KafkaDomainEventConsumer(_messageHandler.bind(this), domainEventConsumerOptions, logger) + +// Setup for Redis access +const bulkTransactionEntityRepoOptions: IRedisBulkTransactionStateRepoOptions = { + connStr: 'redis://localhost:6379' +} +const bulkTransactionEntityRepo = new RedisBulkTransactionStateRepo(bulkTransactionEntityRepoOptions, logger); + + +describe("Tests for ProcessSDKOutboundBulkAcceptPartyInfo Event Handler", () => { + + beforeEach(async () => { + domainEvents = []; + }); + + beforeAll(async () => { + await producer.init(); + await consumer.init(); + await consumer.start(); + await bulkTransactionEntityRepo.init(); + }); + + afterAll(async () => { + await producer.destroy(); + await consumer.destroy(); + await bulkTransactionEntityRepo.destroy(); + }); + + test("Given inbound command event ProcessSDKOutboundBulkAcceptPartyInfo is received \ + Then the logic should loop through individual transfer in the bulk request \ + And update the individual transfer state to DISCOVERY_ACCEPTED or DISCOVERY_REJECTED based on the value in the incoming event \ + And update the overall global state to DISCOVERY_ACCEPTANCE_COMPLETED \ + And outbound event SDKOutboundBulkAcceptPartyInfoProcessed should be published", async () => { + // Publish this message so that it is stored internally in redis + const bulkTransactionId = randomUUID(); + const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { + bulkHomeTransactionID: "string", + bulkTransactionId: bulkTransactionId, + options: { + onlyValidateParty: true, + autoAcceptParty: { + enabled: true + }, + autoAcceptQuote: { + enabled: true, + }, + skipPartyLookup: false, + synchronous: true, + bulkExpiration: "2016-05-24T08:38:08.699-04:00" + }, + from: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551212", + fspId: "string", + }, + }, + individualTransfers: [ + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551212" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "1", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551213" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "2", + } + ] + } + const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { + bulkRequest, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); + await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + const bulkPartyInfoRequestCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const bulkPartyInfoRequestCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCmdEvt( + bulkPartyInfoRequestCommandEventData + ); + await producer.sendCommandEvent(bulkPartyInfoRequestCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Get the randomly generated transferIds for the callback + const randomGeneratedTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); + + // The transfer ids are unordered so using the transfer amounts to identify each transfer + // so we can reference the proper transferId in subsequent callbacks + const amountList: string[] = [] + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[0])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[1])).request.amount) + + // Simulate the domain handler sending the command handler PProcessPartyInfoCallback messages + // for each individual transfer + const processPartyInfoCallbackMessageData1: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('1')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + const processPartyInfoCallbackMessageData2: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('2')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + + const processPartyInfoCallbackMessageObjOne = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData1); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjOne); + const processPartyInfoCallbackMessageObjTwo = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData2); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjTwo); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Simulate the domain handler sending the command handler ProcessSDKOutboundBulkPartyInfoRequestComplete message + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData : IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt( + processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Command event for sdk outbound bulk accept party info request + const processSDKOutboundBulkAcceptPartyInfoCommandEventData : IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { + bulkId: bulkTransactionId, + bulkTransactionContinuationAcceptParty: { + bulkHomeTransactionID: 'string', + individualTransfers: [ + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('1')], + acceptParty: true + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('2')], + acceptParty: false + } + ] + }, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkAcceptPartyInfoCommandEventObj = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt( + processSDKOutboundBulkAcceptPartyInfoCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Check that the global state to be DISCOVERY_ACCEPTANCE_COMPLETED + const bulkStateTwo = await bulkTransactionEntityRepo.load(bulkTransactionId); + expect(bulkStateTwo.state).toBe(BulkTransactionInternalState.DISCOVERY_ACCEPTANCE_COMPLETED); + + // Check that accepted party for individual transfers have been updated to DISCOVERY_ACCEPTED + const acceptedIndividualTransfer = await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[amountList.indexOf('1')]); + expect(acceptedIndividualTransfer.state).toBe(IndividualTransferInternalState.DISCOVERY_ACCEPTED); + + // Check that rejected party for individual transfers have been updated to DISCOVERY_REJECTED + const rejectedIndividualTransfer = await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[amountList.indexOf('2')]); + expect(rejectedIndividualTransfer.state).toBe(IndividualTransferInternalState.DISCOVERY_REJECTED); + + // Check that command handler sends event to domain handler + const hasSDKOutboundBulkAcceptPartyInfoProcessed = (domainEvents.find((e) => e.getName() === 'SDKOutboundBulkAcceptPartyInfoProcessedDmEvt')); + expect(hasSDKOutboundBulkAcceptPartyInfoProcessed).toBeTruthy(); + }); +}); diff --git a/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_quotes_request.test.ts b/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_quotes_request.test.ts new file mode 100644 index 000000000..42a851573 --- /dev/null +++ b/modules/outbound-command-event-handler/test/integration/application/process_sdk_outbound_bulk_quotes_request.test.ts @@ -0,0 +1,377 @@ +/***** + License + -------------- + Copyright © 2017 Bill & Melinda Gates Foundation + The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the "License") and you may not use these files except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + Contributors + -------------- + This is the official list (alphabetical ordering) of the Mojaloop project contributors for this file. + Names of the original copyright holders (individuals or organizations) + should be listed with a '*' in the first column. People who have + contributed from an organization can be listed under the organization + that actually holds the copyright for their contributions (see the + Gates Foundation organization for an example). Those individuals should have + their names indented and be marked with a '-'. Email address can be added + optionally within square brackets . + * Gates Foundation + - Name Surname + * Modusbox + + - Sridevi Miriyala + - Kevin Leyow + -------------- + ******/ + +"use strict"; + +import { DefaultLogger } from "@mojaloop/logging-bc-client-lib"; +import { ILogger } from "@mojaloop/logging-bc-public-types-lib"; +import { SDKSchemeAdapter } from '@mojaloop/api-snippets'; + +import { + BulkTransactionInternalState, + DomainEvent, + IKafkaEventConsumerOptions, + IKafkaEventProducerOptions, + IndividualTransferInternalState, + IProcessPartyInfoCallbackCmdEvtData, + IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData, + IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData, + IProcessSDKOutboundBulkQuotesRequestCmdEvtData, + IProcessSDKOutboundBulkRequestCmdEvtData, + IRedisBulkTransactionStateRepoOptions, + KafkaCommandEventProducer, + KafkaDomainEventConsumer, + ProcessPartyInfoCallbackCmdEvt, + ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCmdEvt, + ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt, + ProcessSDKOutboundBulkQuotesRequestCmdEvt, + ProcessSDKOutboundBulkRequestCmdEvt, + RedisBulkTransactionStateRepo, +} from '@mojaloop/sdk-scheme-adapter-private-shared-lib' +import { randomUUID } from "crypto"; + +// Tests can timeout in a CI pipeline so giving it leeway +jest.setTimeout(20000) + +const logger: ILogger = new DefaultLogger('bc', 'appName', 'appVersion'); //TODO: parameterize the names here +const messageTimeout = 2000; + +// Setup for Kafka Producer +const commandEventProducerOptions: IKafkaEventProducerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topic: 'topic-sdk-outbound-command-events' +} +const producer = new KafkaCommandEventProducer(commandEventProducerOptions, logger) + +// Setup for Kafka Consumer +const domainEventConsumerOptions: IKafkaEventConsumerOptions = { + brokerList: 'localhost:9092', + clientId: 'test-integration_client_id', + topics: ['topic-sdk-outbound-domain-events'], + groupId: "domain_events_consumer_client_id" +} +var domainEvents: Array = [] +const _messageHandler = async (message: DomainEvent): Promise => { + console.log('Domain Message: ', message); + domainEvents.push(message); +} +const consumer = new KafkaDomainEventConsumer(_messageHandler.bind(this), domainEventConsumerOptions, logger) + +// Setup for Redis access +const bulkTransactionEntityRepoOptions: IRedisBulkTransactionStateRepoOptions = { + connStr: 'redis://localhost:6379' +} +const bulkTransactionEntityRepo = new RedisBulkTransactionStateRepo(bulkTransactionEntityRepoOptions, logger); + +describe("Tests for ProcessSDKOutboundBulkQuotesRequest Event Handler", () => { + + beforeEach(async () => { + domainEvents = []; + }); + + beforeAll(async () => { + await producer.init(); + await consumer.init(); + await consumer.start(); + await bulkTransactionEntityRepo.init(); + }); + + afterAll(async () => { + await producer.destroy(); + await consumer.destroy(); + await bulkTransactionEntityRepo.destroy(); + }); + + test("When Inbound command event ProcessSDKOutboundBulkQuotesRequest is received\ + Then the logic should update the global state to AGREEMENT_PROCESSING, \ + And create batches based on FSP that has DISCOVERY_ACCEPTED state \ + And also has config maxEntryConfigPerBatch \ + And publish BulkQuotesRequested per each batch \ + And update the state of each batch to AGREEMENT_PROCESSING.", async () => { + // Publish this message so that it is stored internally in redis + const bulkTransactionId = randomUUID(); + const bulkRequest: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkTransactionRequest = { + bulkHomeTransactionID: "string", + bulkTransactionId: bulkTransactionId, + options: { + onlyValidateParty: true, + autoAcceptParty: { + enabled: true + }, + autoAcceptQuote: { + enabled: true, + }, + skipPartyLookup: false, + synchronous: true, + bulkExpiration: "2016-05-24T08:38:08.699-04:00" + }, + from: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551212", + fspId: "string", + }, + }, + individualTransfers: [ + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551212" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "1", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551213" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "2", + }, + { + homeTransactionId: randomUUID(), + to: { + partyIdInfo: { + partyIdType: "MSISDN", + partyIdentifier: "16135551214" + }, + }, + amountType: "SEND", + currency: "USD", + amount: "3", + } + ] + } + const sampleCommandEventData: IProcessSDKOutboundBulkRequestCmdEvtData = { + bulkRequest, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkRequestMessageObj = new ProcessSDKOutboundBulkRequestCmdEvt(sampleCommandEventData); + await producer.sendCommandEvent(processSDKOutboundBulkRequestMessageObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + const bulkPartyInfoRequestCommandEventData: IProcessSDKOutboundBulkPartyInfoRequestCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const bulkPartyInfoRequestCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCmdEvt( + bulkPartyInfoRequestCommandEventData + ); + await producer.sendCommandEvent(bulkPartyInfoRequestCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Get the randomly generated transferIds for the callback + const randomGeneratedTransferIds = await bulkTransactionEntityRepo.getAllIndividualTransferIds(bulkTransactionId); + + // The transfer ids are unordered so using the transfer amounts to identify each transfer + // so we can reference the proper transferId in subsequent callbacks + const amountList: string[] = [] + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[0])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[1])).request.amount) + amountList.push((await bulkTransactionEntityRepo.getIndividualTransfer(bulkTransactionId, randomGeneratedTransferIds[2])).request.amount) + + // Simulate the domain handler sending the command handler ProcessPartyInfoCallback messages + // for each individual transfer + const processPartyInfoCallbackMessageData1: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('1')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + const processPartyInfoCallbackMessageData2: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('2')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '678999', + fspId: 'receiverfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + + const processPartyInfoCallbackMessageData3: IProcessPartyInfoCallbackCmdEvtData = { + bulkId: bulkTransactionId, + content: { + transferId: randomGeneratedTransferIds[amountList.indexOf('3')], + partyResult: { + party: { + partyIdInfo: { + partyIdType: 'MSISDN', + partyIdentifier: '123456', + fspId: 'differentfsp' + } + }, + currentState: 'COMPLETED' + }, + }, + timestamp: Date.now(), + headers: [] + } + + const processPartyInfoCallbackMessageObjOne = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData1); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjOne); + const processPartyInfoCallbackMessageObjTwo = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData2); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjTwo); + const processPartyInfoCallbackMessageObjThree = new ProcessPartyInfoCallbackCmdEvt(processPartyInfoCallbackMessageData3); + await producer.sendCommandEvent(processPartyInfoCallbackMessageObjThree); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Simulate the domain handler sending the command handler ProcessSDKOutboundBulkPartyInfoRequestComplete message + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData : IProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj = new ProcessSDKOutboundBulkPartyInfoRequestCompleteCmdEvt( + processSDKOutboundBulkPartyInfoRequestCompleteCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkPartyInfoRequestCompleteCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Command event for sdk outbound accept bulk party info request + const processSDKOutboundBulkAcceptPartyInfoCommandEventData : IProcessSDKOutboundBulkAcceptPartyInfoCmdEvtData = { + bulkId: bulkTransactionId, + bulkTransactionContinuationAcceptParty: { + bulkHomeTransactionID: 'string', + individualTransfers: [ + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('1')], + acceptParty: true + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('2')], + acceptParty: false + }, + { + homeTransactionId: 'string', + transactionId: randomGeneratedTransferIds[amountList.indexOf('3')], + acceptParty: true + } + ] + }, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkAcceptPartyInfoCommandEventObj = new ProcessSDKOutboundBulkAcceptPartyInfoCmdEvt( + processSDKOutboundBulkAcceptPartyInfoCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkAcceptPartyInfoCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Simulate domain handler sending command event for sdk outbound bulk quotes request + const processSDKOutboundBulkQuotesRequestCommandEventData : IProcessSDKOutboundBulkQuotesRequestCmdEvtData = { + bulkId: bulkTransactionId, + timestamp: Date.now(), + headers: [] + } + const processSDKOutboundBulkQuotesRequestCommandEventObj = new ProcessSDKOutboundBulkQuotesRequestCmdEvt( + processSDKOutboundBulkQuotesRequestCommandEventData + ); + await producer.sendCommandEvent(processSDKOutboundBulkQuotesRequestCommandEventObj); + await new Promise(resolve => setTimeout(resolve, messageTimeout)); + + // Check that the global state of bulk to be AGREEMENT_PROCESSING + const bulkStateThree = await bulkTransactionEntityRepo.load(bulkTransactionId); + expect(bulkStateThree.state).toBe(BulkTransactionInternalState.AGREEMENT_PROCESSING); + + // Check that command handler published BulkQuotesRequested message + const hasBulkQuotesRequested = (domainEvents.find((e) => e.getName() === 'BulkQuotesRequestedDmEvt')); + expect(hasBulkQuotesRequested).toBeTruthy(); + + // Check that bulk batches have been created. + // One should be for receiverfsp and another for differentfsp + const bulkBatchIds = await bulkTransactionEntityRepo.getAllBulkBatchIds(bulkTransactionId); + expect(bulkBatchIds[0]).toBeDefined(); + expect(bulkBatchIds[1]).toBeDefined(); + + const bulkBatchOne = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, bulkBatchIds[0]); + const bulkBatchTwo = await bulkTransactionEntityRepo.getBulkBatch(bulkTransactionId, bulkBatchIds[1]); + + // Bulk batch ids are unordered so check the quotes for the intended fsp + // so we can send proper callbacks + let receiverFspBatch; + let differentFspBatch; + if (bulkBatchOne.bulkQuotesRequest.individualQuotes[0].to.fspId == 'receiverfsp') { + receiverFspBatch = bulkBatchOne; + differentFspBatch = bulkBatchTwo; + } else { + receiverFspBatch = bulkBatchTwo; + differentFspBatch = bulkBatchOne; + } + + // Assert that the quote number match the accepted parties + // Assert the bulk batch state is AGREEMENT_PROCESSING + // Assert that the reject party was not added in bulk batch + expect(receiverFspBatch.state).toEqual(BulkTransactionInternalState.AGREEMENT_PROCESSING); + expect(receiverFspBatch.bulkQuotesRequest.individualQuotes.length).toEqual(1); + expect(receiverFspBatch.bulkQuotesRequest.individualQuotes[0].to.fspId).toEqual('receiverfsp'); + expect(receiverFspBatch.bulkQuotesRequest.individualQuotes[0].to.idValue).not.toEqual('678999'); + + // Assert that the quote number match the accepted parties. + // Assert the bulk batch state is AGREEMENT_PROCESSING + // Assert that the reject party was not added in bulk batch + expect(differentFspBatch.state).toEqual(BulkTransactionInternalState.AGREEMENT_PROCESSING); + expect(differentFspBatch.bulkQuotesRequest.individualQuotes.length).toEqual(1); + expect(differentFspBatch.bulkQuotesRequest.individualQuotes[0].to.fspId).toEqual('differentfsp'); + expect(differentFspBatch.bulkQuotesRequest.individualQuotes[0].to.idValue).not.toEqual('678999'); + }); +}); diff --git a/modules/outbound-domain-event-handler/package.json b/modules/outbound-domain-event-handler/package.json index d97b14178..3996a2155 100644 --- a/modules/outbound-domain-event-handler/package.json +++ b/modules/outbound-domain-event-handler/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@types/convict": "^6.1.1", "@types/jest": "^29.0.2", - "@types/node": "^18.7.17", + "@types/node": "^18.7.18", "@types/node-cache": "^4.2.5", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", @@ -56,7 +56,7 @@ "npm-check-updates": "^16.1.2", "replace": "^1.2.1", "standard-version": "^9.5.0", - "ts-jest": "^29.0.0", + "ts-jest": "^29.0.1", "ts-node": "^10.9.1", "typescript": "^4.8.3" }, diff --git a/modules/outbound-domain-event-handler/test/integration/application/outbound_event_handler.test.ts b/modules/outbound-domain-event-handler/test/integration/application/outbound_event_handler.test.ts index 1c0a5ebaf..bfecc73ce 100644 --- a/modules/outbound-domain-event-handler/test/integration/application/outbound_event_handler.test.ts +++ b/modules/outbound-domain-event-handler/test/integration/application/outbound_event_handler.test.ts @@ -47,7 +47,8 @@ import { randomUUID } from "crypto"; import { SDKSchemeAdapter } from '@mojaloop/api-snippets'; const logger: ILogger = new DefaultLogger('bc', 'appName', 'appVersion'); //TODO: parameterize the names here -const messageTimeout = 2000; +jest.setTimeout(15000); +const messageTimeout = 6000; const domainEventProducerOptions: IKafkaEventProducerOptions = { brokerList: 'localhost:9092', diff --git a/modules/private-shared-lib/package.json b/modules/private-shared-lib/package.json index e2ee4b053..de12b7c81 100644 --- a/modules/private-shared-lib/package.json +++ b/modules/private-shared-lib/package.json @@ -36,13 +36,13 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@types/node": "^18.7.17", + "@types/node": "^18.7.18", "eslint": "^8.23.1", "jest": "^29.0.3", "npm-check-updates": "^16.1.2", "replace": "^1.2.1", "standard-version": "^9.5.0", - "ts-jest": "^29.0.0", + "ts-jest": "^29.0.1", "typescript": "^4.8.3" }, "standard-version": { diff --git a/modules/private-shared-lib/src/domain/bulk_batch_entity.ts b/modules/private-shared-lib/src/domain/bulk_batch_entity.ts index b19e8fd30..e88687aa4 100644 --- a/modules/private-shared-lib/src/domain/bulk_batch_entity.ts +++ b/modules/private-shared-lib/src/domain/bulk_batch_entity.ts @@ -44,7 +44,7 @@ const ajv = new Ajv({ export enum BulkBatchInternalState { CREATED = 'CREATED', AGREEMENT_PROCESSING = 'AGREEMENT_PROCESSING', - AGREEMENT_SUCCESS = 'AGREEMENT_SUCCESS', + AGREEMENT_COMPLETED = 'AGREEMENT_COMPLETED', AGREEMENT_FAILED = 'AGREEMENT_FAILED', TRANSFER_PROCESSING = 'TRANSFER_PROCESSING', } @@ -97,6 +97,10 @@ export class BulkBatchEntity extends BaseEntity { return this._state.bulkTransfersResponse; } + get quoteIdReferenceIdMap():{ [quoteId: string]: string } { + return this._state.quoteIdReferenceIdMap; + } + private static _convertPartyToFrom(party: SDKSchemeAdapter.Outbound.V2_0_0.Types.Party): SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkQuoteRequest['from'] { return { idType: party.partyIdInfo.partyIdType, @@ -159,7 +163,7 @@ export class BulkBatchEntity extends BaseEntity { referenceId: string, ) { this._state.bulkTransfersRequest.individualTransfers.push(individualTransfer); - this._state.transferIdReferenceIdMap[individualTransfer.transferId] = referenceId; + this._state.transferIdReferenceIdMap[individualTransfer.transferId] = referenceId; } getReferenceIdForQuoteId(quoteId: string) : string { @@ -182,7 +186,7 @@ export class BulkBatchEntity extends BaseEntity { this._state.bulkQuotesResponse = response; } - + validateBulkQuotesRequest() { BulkBatchEntity._validateBulkQuotesRequest(this._state.bulkQuotesRequest); } @@ -190,7 +194,7 @@ export class BulkBatchEntity extends BaseEntity { validateBulkTransfersRequest() { BulkBatchEntity._validateBulkTransfersRequest(this._state.bulkTransfersRequest); } - + private static _validateBulkQuotesRequest(request: SDKSchemeAdapter.Outbound.V2_0_0.Types.bulkQuoteRequest): void { const requestSchema = SDKSchemeAdapter.Outbound.V2_0_0.Schemas.bulkQuoteRequest; const validate = ajv.compile(requestSchema); diff --git a/modules/private-shared-lib/src/events/outbound_domain_event/party_info_callback_processed.ts b/modules/private-shared-lib/src/events/outbound_domain_event/party_info_callback_processed.ts index 432038f33..bd5225497 100644 --- a/modules/private-shared-lib/src/events/outbound_domain_event/party_info_callback_processed.ts +++ b/modules/private-shared-lib/src/events/outbound_domain_event/party_info_callback_processed.ts @@ -27,7 +27,6 @@ import { DomainEvent } from '../domain_event'; import { IMessageHeader } from '@mojaloop/platform-shared-lib-messaging-types-lib'; -import { IPartyResult } from '@module-types'; export interface IPartyInfoCallbackProcessedDmEvtData { bulkId: string; diff --git a/modules/private-shared-lib/src/types/bulk_transaction_entity_repo.ts b/modules/private-shared-lib/src/types/bulk_transaction_entity_repo.ts index 7e83e7a39..5a4ae62d5 100644 --- a/modules/private-shared-lib/src/types/bulk_transaction_entity_repo.ts +++ b/modules/private-shared-lib/src/types/bulk_transaction_entity_repo.ts @@ -57,7 +57,7 @@ export type IBulkTransactionEntityRepo = { setBulkQuotesFailedCount: (bulkId: string, count: number) => Promise incrementBulkQuotesFailedCount: (bulkId: string) => Promise setPartyLookupTotalCount: (bulkId: string, count: number) => Promise - getPartyLookupTotalCount: (bulkId: string, count: number) => Promise + getPartyLookupTotalCount: (bulkId: string) => Promise incrementPartyLookupSuccessCount: (bulkId: string, increment: number) => Promise setPartyLookupSuccessCount: (bulkId: string, count: number) => Promise getPartyLookupSuccessCount: (bulkId: string) => Promise @@ -73,7 +73,7 @@ export type IBulkTransactionEntityReadOnlyRepo = { getBulkQuotesTotalCount: (bulkId: string) => Promise getBulkQuotesSuccessCount: (bulkId: string) => Promise getBulkQuotesFailedCount: (bulkId: string) => Promise - getPartyLookupTotalCount: (bulkId: string, count: number) => Promise + getPartyLookupTotalCount: (bulkId: string) => Promise getPartyLookupSuccessCount: (bulkId: string) => Promise getPartyLookupFailedCount: (bulkId: string) => Promise } & IEntityStateReadOnlyRepository; diff --git a/package.json b/package.json index 752ce0af8..2a2a49525 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ }, "devDependencies": { "@types/jest": "^29.0.2", - "@types/node": "^18.7.17", + "@types/node": "^18.7.18", "@types/node-cache": "^4.2.5", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", @@ -79,7 +79,7 @@ "npm-check-updates": "^16.1.2", "replace": "^1.2.1", "standard-version": "^9.5.0", - "ts-jest": "^29.0.0", + "ts-jest": "^29.0.1", "ts-node": "^10.9.1", "typescript": "^4.8.3", "yarn-audit-fix": "^9.3.5" diff --git a/yarn.lock b/yarn.lock index 3c1a57b8b..264451a55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -113,6 +113,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/compat-data@npm:7.19.1" + checksum: f985887ea08a140e4af87a94d3fb17af0345491eb97f5a85b1840255c2e2a97429f32a8fd12a7aae9218af5f1024f1eb12a5cd280d2d69b2337583c17ea506ba + languageName: node + linkType: hard + "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": version: 7.18.6 resolution: "@babel/core@npm:7.18.6" @@ -136,26 +143,26 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/core@npm:7.19.0" +"@babel/core@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/core@npm:7.19.1" dependencies: "@ampproject/remapping": ^2.1.0 "@babel/code-frame": ^7.18.6 "@babel/generator": ^7.19.0 - "@babel/helper-compilation-targets": ^7.19.0 + "@babel/helper-compilation-targets": ^7.19.1 "@babel/helper-module-transforms": ^7.19.0 "@babel/helpers": ^7.19.0 - "@babel/parser": ^7.19.0 + "@babel/parser": ^7.19.1 "@babel/template": ^7.18.10 - "@babel/traverse": ^7.19.0 + "@babel/traverse": ^7.19.1 "@babel/types": ^7.19.0 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.1 semver: ^6.3.0 - checksum: 0d5b52b552e215802d2fd7b266611c390d90b28dece09db8a142666c32928c5d404eb72a95630b4cb726c4d80a53fcdc2d6464cd7ad28bb26087475f1b2205e2 + checksum: 941c8c119b80bdba5fafc80bbaa424d51146b6d3c30b8fae35879358dd37c11d3d0926bc7e970a0861229656eedaa8c884d4a3a25cc904086eb73b827a2f1168 languageName: node linkType: hard @@ -264,6 +271,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/helper-compilation-targets@npm:7.19.1" + dependencies: + "@babel/compat-data": ^7.19.1 + "@babel/helper-validator-option": ^7.18.6 + browserslist: ^4.21.3 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: c2d3039265e498b341a6b597f855f2fcef02659050fefedf36ad4e6815e6aafe1011a761214cc80d98260ed07ab15a8cbe959a0458e97bec5f05a450e1b1741b + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-create-class-features-plugin@npm:7.18.6" @@ -305,9 +326,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.3.2": - version: 0.3.2 - resolution: "@babel/helper-define-polyfill-provider@npm:0.3.2" +"@babel/helper-define-polyfill-provider@npm:^0.3.3": + version: 0.3.3 + resolution: "@babel/helper-define-polyfill-provider@npm:0.3.3" dependencies: "@babel/helper-compilation-targets": ^7.17.7 "@babel/helper-plugin-utils": ^7.16.7 @@ -317,7 +338,7 @@ __metadata: semver: ^6.1.2 peerDependencies: "@babel/core": ^7.4.0-0 - checksum: 8f693ab8e9d73873c2e547c7764c7d32d73c14f8dcefdd67fd3a038eb75527e2222aa53412ea673b9bfc01c32a8779a60e77a7381bbdd83452f05c9b7ef69c2c + checksum: 8e3fe75513302e34f6d92bd67b53890e8545e6c5bca8fe757b9979f09d68d7e259f6daea90dc9e01e332c4f8781bda31c5fe551c82a277f9bc0bec007aed497c languageName: node linkType: hard @@ -667,6 +688,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/parser@npm:7.19.1" + bin: + parser: ./bin/babel-parser.js + checksum: b1e0acb346b2a533c857e1e97ac0886cdcbd76aafef67835a2b23f760c10568eb53ad8a27dd5f862d8ba4e583742e6067f107281ccbd68959d61bc61e4ddaa51 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" @@ -691,9 +721,9 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-async-generator-functions@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.19.0" +"@babel/plugin-proposal-async-generator-functions@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.19.1" dependencies: "@babel/helper-environment-visitor": ^7.18.9 "@babel/helper-plugin-utils": ^7.19.0 @@ -701,7 +731,7 @@ __metadata: "@babel/plugin-syntax-async-generators": ^7.8.4 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: f1876286d608650928f60ac6091b9a6e7839e005941be483df47693b98c90649202aa1793f28f6e9b4ce69bf0773552144fa40f38751f56dc5d02051a8ee0461 + checksum: f101555b00aee6ee0107c9e40d872ad646bbd3094abdbeda56d17b107df69a0cb49e5d02dcf5f9d8753e25564e798d08429f12d811aaa1b307b6a725c0b8159c languageName: node linkType: hard @@ -1311,15 +1341,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.19.0" +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.19.1" dependencies: "@babel/helper-create-regexp-features-plugin": ^7.19.0 "@babel/helper-plugin-utils": ^7.19.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 60f7b2c537fa3e8392f19b1f1026ba68844c5dc7942867e7a96a636d8a52d4766629b898e59aa690d3806bf02a7fa52e12d1f7c1ca2ef4fa2b53f3fe0a835117 + checksum: 8a40f5d04f2140c44fe890a5a3fd72abc2a88445443ac2bd92e1e85d9366d3eb8f1ebb7e2c89d2daeaf213d9b28cb65605502ac9b155936d48045eeda6053494 languageName: node linkType: hard @@ -1470,17 +1500,17 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.19.0": - version: 7.19.0 - resolution: "@babel/preset-env@npm:7.19.0" +"@babel/preset-env@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/preset-env@npm:7.19.1" dependencies: - "@babel/compat-data": ^7.19.0 - "@babel/helper-compilation-targets": ^7.19.0 + "@babel/compat-data": ^7.19.1 + "@babel/helper-compilation-targets": ^7.19.1 "@babel/helper-plugin-utils": ^7.19.0 "@babel/helper-validator-option": ^7.18.6 "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.18.6 "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.18.9 - "@babel/plugin-proposal-async-generator-functions": ^7.19.0 + "@babel/plugin-proposal-async-generator-functions": ^7.19.1 "@babel/plugin-proposal-class-properties": ^7.18.6 "@babel/plugin-proposal-class-static-block": ^7.18.6 "@babel/plugin-proposal-dynamic-import": ^7.18.6 @@ -1528,7 +1558,7 @@ __metadata: "@babel/plugin-transform-modules-commonjs": ^7.18.6 "@babel/plugin-transform-modules-systemjs": ^7.19.0 "@babel/plugin-transform-modules-umd": ^7.18.6 - "@babel/plugin-transform-named-capturing-groups-regex": ^7.19.0 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.19.1 "@babel/plugin-transform-new-target": ^7.18.6 "@babel/plugin-transform-object-super": ^7.18.6 "@babel/plugin-transform-parameters": ^7.18.8 @@ -1544,14 +1574,14 @@ __metadata: "@babel/plugin-transform-unicode-regex": ^7.18.6 "@babel/preset-modules": ^0.1.5 "@babel/types": ^7.19.0 - babel-plugin-polyfill-corejs2: ^0.3.2 - babel-plugin-polyfill-corejs3: ^0.5.3 - babel-plugin-polyfill-regenerator: ^0.4.0 - core-js-compat: ^3.22.1 + babel-plugin-polyfill-corejs2: ^0.3.3 + babel-plugin-polyfill-corejs3: ^0.6.0 + babel-plugin-polyfill-regenerator: ^0.4.1 + core-js-compat: ^3.25.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ae1866b9a6c9749d52618f39aab8c369e0d6dc88e327341fae932411a0d51db2ec51b882cebc62ff3d49443261a6940e3fc03762ff3925d165884e7990eb612c + checksum: 3fcd4f3e768b8b0c9e8f9fb2b23d694d838d3cc936c783aaa9c436b863ae24811059b6ffed80e2ac7d54e7d2c18b0a190f4de05298cf461d27b2817b617ea71f languageName: node linkType: hard @@ -1673,6 +1703,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/traverse@npm:7.19.1" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.19.0 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.19.1 + "@babel/types": ^7.19.0 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 9d782b5089ebc989e54c2406814ed1206cb745ed2734e6602dee3e23d4b6ebbb703ff86e536276630f8de83fda6cde99f0634e3c3d847ddb40572d0303ba8800 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.7, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.18.7 resolution: "@babel/types@npm:7.18.7" @@ -2516,8 +2564,8 @@ __metadata: version: 0.0.0-use.local resolution: "@mojaloop/sdk-scheme-adapter-api-svc@workspace:modules/api-svc" dependencies: - "@babel/core": ^7.19.0 - "@babel/preset-env": ^7.19.0 + "@babel/core": ^7.19.1 + "@babel/preset-env": ^7.19.1 "@koa/cors": ^3.4.1 "@mojaloop/api-snippets": ^15.0.1 "@mojaloop/central-services-error-handling": ^12.0.4 @@ -2578,9 +2626,9 @@ __metadata: "@mojaloop/logging-bc-public-types-lib": ^0.1.10 "@mojaloop/sdk-scheme-adapter-private-shared-lib": "workspace:^" "@types/convict": ^6.1.1 - "@types/express": ^4.17.13 + "@types/express": ^4.17.14 "@types/jest": ^29.0.2 - "@types/node": ^18.7.17 + "@types/node": ^18.7.18 "@types/node-cache": ^4.2.5 "@types/supertest": ^2.0.12 "@types/swagger-ui-express": ^4.1.3 @@ -2600,7 +2648,7 @@ __metadata: replace: ^1.2.1 standard-version: ^9.5.0 swagger-ui-express: ^4.5.0 - ts-jest: ^29.0.0 + ts-jest: ^29.0.1 ts-node: ^10.9.1 typescript: ^4.8.3 yamljs: ^0.3.0 @@ -2616,7 +2664,7 @@ __metadata: "@mojaloop/sdk-scheme-adapter-private-shared-lib": "workspace:^" "@types/convict": ^6.1.1 "@types/jest": ^29.0.2 - "@types/node": ^18.7.17 + "@types/node": ^18.7.18 "@types/node-cache": ^4.2.5 "@typescript-eslint/eslint-plugin": ^5.37.0 "@typescript-eslint/parser": ^5.37.0 @@ -2627,7 +2675,7 @@ __metadata: npm-check-updates: ^16.1.2 replace: ^1.2.1 standard-version: ^9.5.0 - ts-jest: ^29.0.0 + ts-jest: ^29.0.1 ts-node: ^10.9.1 typescript: ^4.8.3 languageName: unknown @@ -2641,7 +2689,7 @@ __metadata: "@mojaloop/logging-bc-public-types-lib": ^0.1.10 "@mojaloop/platform-shared-lib-messaging-types-lib": ^0.0.3 "@mojaloop/platform-shared-lib-nodejs-kafka-client-lib": ^0.0.9 - "@types/node": ^18.7.17 + "@types/node": ^18.7.18 ajv: ^8.11.0 eslint: ^8.23.1 jest: ^29.0.3 @@ -2649,7 +2697,7 @@ __metadata: redis: ^4.3.1 replace: ^1.2.1 standard-version: ^9.5.0 - ts-jest: ^29.0.0 + ts-jest: ^29.0.1 typescript: ^4.8.3 uuid: ^9.0.0 languageName: unknown @@ -2660,7 +2708,7 @@ __metadata: resolution: "@mojaloop/sdk-scheme-adapter@workspace:." dependencies: "@types/jest": ^29.0.2 - "@types/node": ^18.7.17 + "@types/node": ^18.7.18 "@types/node-cache": ^4.2.5 "@typescript-eslint/eslint-plugin": ^5.37.0 "@typescript-eslint/parser": ^5.37.0 @@ -2675,7 +2723,7 @@ __metadata: nx: 14.7.5 replace: ^1.2.1 standard-version: ^9.5.0 - ts-jest: ^29.0.0 + ts-jest: ^29.0.1 ts-node: ^10.9.1 tslib: ^2.4.0 typescript: ^4.8.3 @@ -3216,7 +3264,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13": +"@types/express@npm:*": version: 4.17.13 resolution: "@types/express@npm:4.17.13" dependencies: @@ -3228,6 +3276,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.14": + version: 4.17.14 + resolution: "@types/express@npm:4.17.14" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.18 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 + languageName: node + linkType: hard + "@types/find-cache-dir@npm:^3.2.1": version: 3.2.1 resolution: "@types/find-cache-dir@npm:3.2.1" @@ -3425,10 +3485,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.7.17": - version: 18.7.17 - resolution: "@types/node@npm:18.7.17" - checksum: bb1b832f4ad42a106d189d87e95aef117f0026a7ea291f34c5d106a25419d6d4c9d39099688ad46746cc3c2307c263be4adec1049a35dc12c9e48f19975b2dc6 +"@types/node@npm:^18.7.18": + version: 18.7.18 + resolution: "@types/node@npm:18.7.18" + checksum: 8aec61f0f96e2a69ce51f1f40f949ca578bbb4fe05d7c0b8ce3aeeb848e90f755837f17f6ac132ca404d974fe9b2974150ad3b4984fc9dc7c3ceddb10bae0167 languageName: node linkType: hard @@ -4259,39 +4319,39 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.3.2": - version: 0.3.2 - resolution: "babel-plugin-polyfill-corejs2@npm:0.3.2" +"babel-plugin-polyfill-corejs2@npm:^0.3.3": + version: 0.3.3 + resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" dependencies: "@babel/compat-data": ^7.17.7 - "@babel/helper-define-polyfill-provider": ^0.3.2 + "@babel/helper-define-polyfill-provider": ^0.3.3 semver: ^6.1.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a76e7bb1a5cc0a4507baa523c23f9efd75764069a25845beba92290386e5e48ed85b894005ece3b527e13c3d2d9c6589cc0a23befb72ea6fc7aa8711f231bb4d + checksum: 7db3044993f3dddb3cc3d407bc82e640964a3bfe22de05d90e1f8f7a5cb71460011ab136d3c03c6c1ba428359ebf635688cd6205e28d0469bba221985f5c6179 languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.5.3": - version: 0.5.3 - resolution: "babel-plugin-polyfill-corejs3@npm:0.5.3" +"babel-plugin-polyfill-corejs3@npm:^0.6.0": + version: 0.6.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.6.0" dependencies: - "@babel/helper-define-polyfill-provider": ^0.3.2 - core-js-compat: ^3.21.0 + "@babel/helper-define-polyfill-provider": ^0.3.3 + core-js-compat: ^3.25.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 9c6644a1b0afbe59e402827fdafc6f44994ff92c5b2f258659cbbfd228f7075dea49e95114af10e66d70f36cbde12ff1d81263eb67be749b3ef0e2c18cf3c16d + checksum: 470bb8c59f7c0912bd77fe1b5a2e72f349b3f65bbdee1d60d6eb7e1f4a085c6f24b2dd5ab4ac6c2df6444a96b070ef6790eccc9edb6a2668c60d33133bfb62c6 languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.4.0": - version: 0.4.0 - resolution: "babel-plugin-polyfill-regenerator@npm:0.4.0" +"babel-plugin-polyfill-regenerator@npm:^0.4.1": + version: 0.4.1 + resolution: "babel-plugin-polyfill-regenerator@npm:0.4.1" dependencies: - "@babel/helper-define-polyfill-provider": ^0.3.2 + "@babel/helper-define-polyfill-provider": ^0.3.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 699aa9c0dc5a2259d7fa52b26613fa1e782439eee54cd98506991f87fddf0c00eec6c5b1917edf586c170731d9e318903bc41210225a691e7bb8087652bbda94 + checksum: ab0355efbad17d29492503230387679dfb780b63b25408990d2e4cf421012dae61d6199ddc309f4d2409ce4e9d3002d187702700dd8f4f8770ebbba651ed066c languageName: node linkType: hard @@ -4541,7 +4601,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2, browserslist@npm:^4.21.0": +"browserslist@npm:^4.20.2": version: 4.21.1 resolution: "browserslist@npm:4.21.1" dependencies: @@ -4555,6 +4615,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.21.3": + version: 4.21.3 + resolution: "browserslist@npm:4.21.3" + dependencies: + caniuse-lite: ^1.0.30001370 + electron-to-chromium: ^1.4.202 + node-releases: ^2.0.6 + update-browserslist-db: ^1.0.5 + bin: + browserslist: cli.js + checksum: ff512a7bcca1c530e2854bbdfc7be2791d0fb524097a6340e56e1d5924164c7e4e0a9b070de04cdc4c149d15cb4d4275cb7c626ebbce954278a2823aaad2452a + languageName: node + linkType: hard + "bs-logger@npm:0.x": version: 0.2.6 resolution: "bs-logger@npm:0.2.6" @@ -4764,6 +4838,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001370": + version: 1.0.30001399 + resolution: "caniuse-lite@npm:1.0.30001399" + checksum: dd105b06fbbdc89867780a2f4debc3ecb184cff82f35b34aaac486628fcc9cf6bacf37573a9cc22dedc661178d460fa8401374a933cb9d2f8ee67316d98b2a8f + languageName: node + linkType: hard + "center-align@npm:^0.1.1": version: 0.1.3 resolution: "center-align@npm:0.1.3" @@ -5631,13 +5712,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.21.0, core-js-compat@npm:^3.22.1": - version: 3.23.3 - resolution: "core-js-compat@npm:3.23.3" +"core-js-compat@npm:^3.25.1": + version: 3.25.1 + resolution: "core-js-compat@npm:3.25.1" dependencies: - browserslist: ^4.21.0 - semver: 7.0.0 - checksum: a5fd680a31b8e667ce0f852238a2fd6769d495ecf0e8a6e04a240e5e259e9a33a77b2839131b640f03c206fff12c51dca7e362ac1897f629bf4c5e39075c83a7 + browserslist: ^4.21.3 + checksum: 34dbec657adc2f660f4cd701709c9c5e27cbd608211c65df09458f80f3e357b9492ba1c5173e17cca72d889dcc6da01268cadf88fb407cf1726e76d301c6143e languageName: node linkType: hard @@ -6149,6 +6229,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.202": + version: 1.4.249 + resolution: "electron-to-chromium@npm:1.4.249" + checksum: 830a35a157af7ae226f1528d727e369bb13f53bc7a4edefdf718651ace09d7d7b4bd7b70d33b5018b8eff6cf99ee58409b6c4140cd6d56350c1966f280ac5c93 + languageName: node + linkType: hard + "emittery@npm:^0.10.2": version: 0.10.2 resolution: "emittery@npm:0.10.2" @@ -10510,6 +10597,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.6": + version: 2.0.6 + resolution: "node-releases@npm:2.0.6" + checksum: e86a926dc9fbb3b41b4c4a89d998afdf140e20a4e8dbe6c0a807f7b2948b42ea97d7fd3ad4868041487b6e9ee98409829c6e4d84a734a4215dff060a7fbeb4bf + languageName: node + linkType: hard + "nodemon@npm:^2.0.19": version: 2.0.19 resolution: "nodemon@npm:2.0.19" @@ -12554,15 +12648,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.0.0, semver@npm:~7.0.0": - version: 7.0.0 - resolution: "semver@npm:7.0.0" - bin: - semver: bin/semver.js - checksum: 272c11bf8d083274ef79fe40a81c55c184dff84dd58e3c325299d0927ba48cece1f020793d138382b85f89bab5002a35a5ba59a3a68a7eebbb597eb733838778 - languageName: node - linkType: hard - "semver@npm:7.3.4": version: 7.3.4 resolution: "semver@npm:7.3.4" @@ -12594,6 +12679,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:~7.0.0": + version: 7.0.0 + resolution: "semver@npm:7.0.0" + bin: + semver: bin/semver.js + checksum: 272c11bf8d083274ef79fe40a81c55c184dff84dd58e3c325299d0927ba48cece1f020793d138382b85f89bab5002a35a5ba59a3a68a7eebbb597eb733838778 + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -13649,9 +13743,9 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.0.0": - version: 29.0.0 - resolution: "ts-jest@npm:29.0.0" +"ts-jest@npm:^29.0.1": + version: 29.0.1 + resolution: "ts-jest@npm:29.0.1" dependencies: bs-logger: 0.x fast-json-stable-stringify: 2.x @@ -13678,7 +13772,7 @@ __metadata: optional: true bin: ts-jest: cli.js - checksum: f4e811aa910b7cd2ef6a9269700dda17ccf253ebdb6ba9e596982d238292d6482973e670e7a6875209e7d246cdf575d79590fc503e12e9368e9105d1d74f09fc + checksum: f126675beb0d103d440b0297dc331ce37ba0f1e1a8002f4e97bfeb9043cf21b4de03deebf11ac1f08b7e4978d87f6a1c71e398b1c2f8535df3b684338786408e languageName: node linkType: hard @@ -14048,6 +14142,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.0.5": + version: 1.0.9 + resolution: "update-browserslist-db@npm:1.0.9" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + browserslist-lint: cli.js + checksum: f625899b236f6a4d7f62b56be1b8da230c5563d1fef84d3ef148f2e1a3f11a5a4b3be4fd7e3703e51274c116194017775b10afb4de09eb2c0d09d36b90f1f578 + languageName: node + linkType: hard + "update-notifier@npm:^6.0.2": version: 6.0.2 resolution: "update-notifier@npm:6.0.2"