@mojaloop/sdk-scheme-adapter 11.18.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +140 -0
- package/.eslintignore +2 -0
- package/.eslintrc.json +30 -0
- package/.nvmrc +1 -0
- package/.versionrc +15 -0
- package/CHANGELOG.md +118 -0
- package/InboundServer/api.yaml +3594 -0
- package/InboundServer/api_template.yaml +69 -0
- package/InboundServer/handlers.js +940 -0
- package/InboundServer/index.js +205 -0
- package/InboundServer/middlewares.js +426 -0
- package/OAuthTestServer/index.js +66 -0
- package/OAuthTestServer/model.js +70 -0
- package/OutboundServer/api.yaml +2732 -0
- package/OutboundServer/api_interfaces/index.d.ts +117 -0
- package/OutboundServer/api_interfaces/openapi.d.ts +1475 -0
- package/OutboundServer/api_template/components/parameters/bulkQuoteId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/bulkTransferId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/requestToPayTransactionId.yaml +9 -0
- package/OutboundServer/api_template/components/parameters/transferId.yaml +9 -0
- package/OutboundServer/api_template/components/responses/accountsCreationCompleted.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/accountsCreationTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/authorizationsServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkQuoteTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/bulkTransferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/responses/partiesByIdError404.yaml +9 -0
- package/OutboundServer/api_template/components/responses/partiesByIdSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/quotesServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPaySuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/requestToPayTransferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersPostSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/simpleTransfersServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferBadRequest.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferSuccess.yaml +5 -0
- package/OutboundServer/api_template/components/responses/transferTimeout.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/accountCreationStatus.yaml +18 -0
- package/OutboundServer/api_template/components/schemas/accountsCreationState.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/accountsRequest.yaml +20 -0
- package/OutboundServer/api_template/components/schemas/accountsResponse.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/async2SyncCurrentState.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostRequest.yaml +15 -0
- package/OutboundServer/api_template/components/schemas/authorizationsPostResponse.yaml +19 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteResponse.yaml +21 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkQuoteStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferErrorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferRequest.yaml +26 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferResponse.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatus.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/bulkTransferStatusResponse.yaml +17 -0
- package/OutboundServer/api_template/components/schemas/errorAccountsResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorAuthorizationsResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorQuotesResponse.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/errorResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/errorSimpleTransfersResponse.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/errorTransferResponse.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/extensionListEmptiable.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/individualQuote.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualQuoteResult.yaml +28 -0
- package/OutboundServer/api_template/components/schemas/individualTransfer.yaml +32 -0
- package/OutboundServer/api_template/components/schemas/individualTransferFulfilment.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/individualTransferResult.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/mojaloopError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/mojaloopTransactionRequestState.yaml +2 -0
- package/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quote.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/quoteError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/quotesPostRequest.yaml +13 -0
- package/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +48 -0
- package/OutboundServer/api_template/components/schemas/requestToPayRequest.yaml +39 -0
- package/OutboundServer/api_template/components/schemas/requestToPayResponse.yaml +41 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferRequest.yaml +42 -0
- package/OutboundServer/api_template/components/schemas/requestToPayTransferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/simpleTransferServerError.yaml +5 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostRequest.yaml +12 -0
- package/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +11 -0
- package/OutboundServer/api_template/components/schemas/transactionType.yaml +4 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptOTP.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptParty.yaml +8 -0
- package/OutboundServer/api_template/components/schemas/transferContinuationAcceptQuote.yaml +9 -0
- package/OutboundServer/api_template/components/schemas/transferError.yaml +16 -0
- package/OutboundServer/api_template/components/schemas/transferFulfilment.yaml +3 -0
- package/OutboundServer/api_template/components/schemas/transferParty.yaml +40 -0
- package/OutboundServer/api_template/components/schemas/transferRequest.yaml +37 -0
- package/OutboundServer/api_template/components/schemas/transferResponse.yaml +58 -0
- package/OutboundServer/api_template/components/schemas/transferStatus.yaml +6 -0
- package/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +13 -0
- package/OutboundServer/api_template/health.yaml +12 -0
- package/OutboundServer/api_template/openapi.yaml +55 -0
- package/OutboundServer/api_template/paths/accounts.yaml +26 -0
- package/OutboundServer/api_template/paths/authorizations.yaml +19 -0
- package/OutboundServer/api_template/paths/bulkQuotes.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkQuotes_bulkQuoteId.yaml +24 -0
- package/OutboundServer/api_template/paths/bulkTransfers.yaml +23 -0
- package/OutboundServer/api_template/paths/bulkTransfers_bulkTransferId.yaml +24 -0
- package/OutboundServer/api_template/paths/parties_Type_ID.yaml +20 -0
- package/OutboundServer/api_template/paths/parties_Type_ID_SubId.yaml +22 -0
- package/OutboundServer/api_template/paths/quotes.yaml +20 -0
- package/OutboundServer/api_template/paths/requestToPay.yaml +22 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer.yaml +57 -0
- package/OutboundServer/api_template/paths/requestToPayTransfer_requestToPayTransactionId.yaml +34 -0
- package/OutboundServer/api_template/paths/simpleTransfers.yaml +19 -0
- package/OutboundServer/api_template/paths/transfers.yaml +55 -0
- package/OutboundServer/api_template/paths/transfers_transferId.yaml +58 -0
- package/OutboundServer/handlers.js +622 -0
- package/OutboundServer/index.js +137 -0
- package/OutboundServer/middlewares.js +67 -0
- package/TestServer/api.yaml +62 -0
- package/TestServer/handlers.js +63 -0
- package/TestServer/index.js +215 -0
- package/audit-resolve.json +65 -0
- package/babel.config.js +3 -0
- package/config.js +158 -0
- package/index.d.ts +1 -0
- package/index.js +149 -0
- package/jest.config.js +15 -0
- package/lib/api/index.js +12 -0
- package/lib/cache.js +352 -0
- package/lib/check.js +25 -0
- package/lib/model/AccountsModel.js +396 -0
- package/lib/model/Async2SyncModel.js +283 -0
- package/lib/model/AuthorizationsModel.js +86 -0
- package/lib/model/InboundTransfersModel.js +730 -0
- package/lib/model/OutboundBulkQuotesModel.js +485 -0
- package/lib/model/OutboundBulkTransfersModel.js +479 -0
- package/lib/model/OutboundRequestToPayModel.js +517 -0
- package/lib/model/OutboundRequestToPayTransferModel.js +893 -0
- package/lib/model/OutboundTransfersModel.js +823 -0
- package/lib/model/PartiesModel.js +70 -0
- package/lib/model/ProxyModel/MatchRules/Expression.js +48 -0
- package/lib/model/ProxyModel/MatchRules/Headers.js +65 -0
- package/lib/model/ProxyModel/MatchRules/MatchRule.js +27 -0
- package/lib/model/ProxyModel/MatchRules/Path.js +36 -0
- package/lib/model/ProxyModel/MatchRules/Query.js +65 -0
- package/lib/model/ProxyModel/MatchRules/index.js +19 -0
- package/lib/model/ProxyModel/Route.js +82 -0
- package/lib/model/ProxyModel/configSchema.json +118 -0
- package/lib/model/ProxyModel/index.js +138 -0
- package/lib/model/QuotesModel.js +94 -0
- package/lib/model/TransfersModel.js +81 -0
- package/lib/model/common/BackendError.js +26 -0
- package/lib/model/common/PersistentStateMachine.js +93 -0
- package/lib/model/common/index.js +18 -0
- package/lib/model/index.js +43 -0
- package/lib/model/lib/deferredJob.js +113 -0
- package/lib/model/lib/index.js +9 -0
- package/lib/model/lib/requests/backendRequests.js +227 -0
- package/lib/model/lib/requests/common.js +76 -0
- package/lib/model/lib/requests/index.js +19 -0
- package/lib/model/lib/shared.js +468 -0
- package/lib/randomphrase/index.js +21 -0
- package/lib/randomphrase/words.json +3397 -0
- package/lib/router.js +28 -0
- package/lib/validate.js +205 -0
- package/package.json +102 -0
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +152 -0
- package/test/__mocks__/javascript-state-machine.js +21 -0
- package/test/__mocks__/redis.js +49 -0
- package/test/__mocks__/uuidv4.js +16 -0
- package/test/config/integration.env +136 -0
- package/test/integration/lib/Outbound/authorizations.test.js +58 -0
- package/test/integration/lib/Outbound/data/authorizationsPostRequest.json +43 -0
- package/test/integration/lib/Outbound/data/quotesPostRequest.json +52 -0
- package/test/integration/lib/Outbound/data/transfersPostRequest.json +24 -0
- package/test/integration/lib/Outbound/parties.test.js +28 -0
- package/test/integration/lib/Outbound/quotes.test.js +58 -0
- package/test/integration/lib/Outbound/simpleTransfers.test.js +67 -0
- package/test/integration/lib/cache.test.js +80 -0
- package/test/integration/testEnv.js +7 -0
- package/test/unit/InboundServer.test.js +443 -0
- package/test/unit/TestServer.test.js +394 -0
- package/test/unit/api/accounts/accounts.test.js +128 -0
- package/test/unit/api/accounts/data/postAccountsBody.json +7 -0
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +25 -0
- package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +19 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +17 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +21 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +21 -0
- package/test/unit/api/accounts/utils.js +65 -0
- package/test/unit/api/proxy/data/proxyConfig.yaml +82 -0
- package/test/unit/api/proxy/data/requestBody.json +22 -0
- package/test/unit/api/proxy/data/requestHeaders.json +5 -0
- package/test/unit/api/proxy/data/requestQuery.json +6 -0
- package/test/unit/api/proxy/data/responseBody.json +21 -0
- package/test/unit/api/proxy/data/responseHeaders.json +5 -0
- package/test/unit/api/proxy/proxy.test.js +220 -0
- package/test/unit/api/proxy/utils.js +79 -0
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +21 -0
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +17 -0
- package/test/unit/api/transfers/data/postQuotesBody.json +52 -0
- package/test/unit/api/transfers/data/postTransfersBadBody.json +17 -0
- package/test/unit/api/transfers/data/postTransfersBody.json +24 -0
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +53 -0
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +47 -0
- package/test/unit/api/transfers/data/postTransfersSimpleBody.json +26 -0
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +101 -0
- package/test/unit/api/transfers/data/putPartiesBody.json +20 -0
- package/test/unit/api/transfers/data/putQuotesBody.json +37 -0
- package/test/unit/api/transfers/data/putTransfersBody.json +17 -0
- package/test/unit/api/transfers/transfers.test.js +191 -0
- package/test/unit/api/transfers/utils.js +183 -0
- package/test/unit/api/utils.js +75 -0
- package/test/unit/config.test.js +119 -0
- package/test/unit/data/commonHttpHeaders.json +6 -0
- package/test/unit/data/defaultConfig.json +58 -0
- package/test/unit/data/postQuotesBody.json +52 -0
- package/test/unit/data/putParticipantsBody.json +12 -0
- package/test/unit/data/putPartiesBody.json +20 -0
- package/test/unit/data/testFile.json +29 -0
- package/test/unit/data/testFile.yaml +14 -0
- package/test/unit/inboundApi/data/mockArguments.json +117 -0
- package/test/unit/inboundApi/data/mockTransactionRequest.json +42 -0
- package/test/unit/inboundApi/handlers.test.js +799 -0
- package/test/unit/index.test.js +55 -0
- package/test/unit/lib/cache.test.js +146 -0
- package/test/unit/lib/model/AccountsModel.test.js +121 -0
- package/test/unit/lib/model/AuthorizationsModel.test.js +460 -0
- package/test/unit/lib/model/InboundTransfersModel.test.js +628 -0
- package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +249 -0
- package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +244 -0
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +166 -0
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +245 -0
- package/test/unit/lib/model/OutboundTransfersModel.test.js +836 -0
- package/test/unit/lib/model/PartiesModel.test.js +468 -0
- package/test/unit/lib/model/QuotesModel.test.js +470 -0
- package/test/unit/lib/model/TransfersModel.test.js +474 -0
- package/test/unit/lib/model/common/PersistentStateMachine.test.js +179 -0
- package/test/unit/lib/model/data/authorizationsResponse.json +13 -0
- package/test/unit/lib/model/data/bulkQuoteRequest.json +27 -0
- package/test/unit/lib/model/data/bulkQuoteResponse.json +35 -0
- package/test/unit/lib/model/data/bulkTransferFulfil.json +13 -0
- package/test/unit/lib/model/data/bulkTransferRequest.json +29 -0
- package/test/unit/lib/model/data/defaultConfig.json +47 -0
- package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +42 -0
- package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +22 -0
- package/test/unit/lib/model/data/getTransfersBackendResponse.json +34 -0
- package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +17 -0
- package/test/unit/lib/model/data/mockArguments.json +131 -0
- package/test/unit/lib/model/data/mockTxnRequestsArguments.json +63 -0
- package/test/unit/lib/model/data/notificationToPayee.json +10 -0
- package/test/unit/lib/model/data/payeeParty.json +16 -0
- package/test/unit/lib/model/data/putAuthorizationsResponse.json +10 -0
- package/test/unit/lib/model/data/putQuotesResponse.json +33 -0
- package/test/unit/lib/model/data/putTransfersResponse.json +5 -0
- package/test/unit/lib/model/data/quoteResponse.json +31 -0
- package/test/unit/lib/model/data/requestToPayRequest.json +20 -0
- package/test/unit/lib/model/data/requestToPayTransferRequest.json +27 -0
- package/test/unit/lib/model/data/transactionRequestResponse.json +18 -0
- package/test/unit/lib/model/data/transferFulfil.json +8 -0
- package/test/unit/lib/model/data/transferRequest.json +26 -0
- package/test/unit/lib/model/mockedLibRequests.js +74 -0
- package/test/unit/mockLogger.js +39 -0
- package/test/unit/outboundApi/data/bulkQuoteRequest.json +28 -0
- package/test/unit/outboundApi/data/bulkTransferRequest.json +28 -0
- package/test/unit/outboundApi/data/mockBulkQuoteError.json +45 -0
- package/test/unit/outboundApi/data/mockBulkTransferError.json +48 -0
- package/test/unit/outboundApi/data/mockError.json +41 -0
- package/test/unit/outboundApi/data/mockGetPartiesError.json +4 -0
- package/test/unit/outboundApi/data/mockRequestToPayError.json +32 -0
- package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +39 -0
- package/test/unit/outboundApi/data/requestToPay.json +21 -0
- package/test/unit/outboundApi/data/requestToPayTransferRequest.json +20 -0
- package/test/unit/outboundApi/data/transferRequest.json +21 -0
- package/test/unit/outboundApi/handlers.test.js +986 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* Murthy Kakarlamudi - murthy@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
// we use a mock standard components lib to intercept and mock certain funcs
|
|
14
|
+
jest.mock('@mojaloop/sdk-standard-components');
|
|
15
|
+
jest.mock('redis');
|
|
16
|
+
|
|
17
|
+
const Cache = require('../../../../lib/cache');
|
|
18
|
+
const Model = require('../../../../lib/model').OutboundRequestToPayModel;
|
|
19
|
+
const PartiesModel = require('../../../../lib/model').PartiesModel;
|
|
20
|
+
|
|
21
|
+
const { MojaloopRequests, Logger } = require('@mojaloop/sdk-standard-components');
|
|
22
|
+
const StateMachine = require('javascript-state-machine');
|
|
23
|
+
|
|
24
|
+
const defaultConfig = require('./data/defaultConfig');
|
|
25
|
+
const requestToPayRequest = require('./data/requestToPayRequest');
|
|
26
|
+
const payeeParty = require('./data/payeeParty');
|
|
27
|
+
const transactionRequestResponseTemplate = require('./data/transactionRequestResponse');
|
|
28
|
+
|
|
29
|
+
const genPartyId = (party) => {
|
|
30
|
+
const { partyIdType, partyIdentifier, partySubIdOrType } = party.party.partyIdInfo;
|
|
31
|
+
return PartiesModel.channelName({
|
|
32
|
+
type: partyIdType,
|
|
33
|
+
id: partyIdentifier,
|
|
34
|
+
subId: partySubIdOrType
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// util function to simulate a party resolution subscription message on a cache client
|
|
39
|
+
const emitPartyCacheMessage = (cache, party) => cache.publish(genPartyId(party), JSON.stringify(party));
|
|
40
|
+
|
|
41
|
+
// util function to simulate a quote response subscription message on a cache client
|
|
42
|
+
const emitTransactionRequestResponseCacheMessage = (cache, transactionRequestId, transactionRequestResponse) => cache.publish(`txnreq_${transactionRequestId}`, JSON.stringify(transactionRequestResponse));
|
|
43
|
+
|
|
44
|
+
describe('outboundModel', () => {
|
|
45
|
+
let transactionRequestResponse;
|
|
46
|
+
let config;
|
|
47
|
+
let logger;
|
|
48
|
+
let cache;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} opts
|
|
53
|
+
* @param {Number} opts.expirySeconds
|
|
54
|
+
* @param {Object} opts.delays
|
|
55
|
+
* @param {Number} delays.requestQuotes
|
|
56
|
+
* @param {Number} delays.prepareTransfer
|
|
57
|
+
* @param {Object} opts.rejects
|
|
58
|
+
* @param {boolean} rejects.quoteResponse
|
|
59
|
+
* @param {boolean} rejects.transferFulfils
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
beforeAll(async () => {
|
|
63
|
+
logger = new Logger.Logger({ context: { app: 'outbound-model-unit-tests-cache' }, stringify: () => '' });
|
|
64
|
+
transactionRequestResponse = JSON.parse(JSON.stringify(transactionRequestResponseTemplate));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
beforeEach(async () => {
|
|
68
|
+
config = JSON.parse(JSON.stringify(defaultConfig));
|
|
69
|
+
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve());
|
|
70
|
+
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve());
|
|
71
|
+
MojaloopRequests.__postTransactionRequests = jest.fn(() => Promise.resolve());
|
|
72
|
+
|
|
73
|
+
cache = new Cache({
|
|
74
|
+
host: 'dummycachehost',
|
|
75
|
+
port: 1234,
|
|
76
|
+
logger,
|
|
77
|
+
});
|
|
78
|
+
await cache.connect();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(async () => {
|
|
82
|
+
await cache.disconnect();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('initializes to starting state', async () => {
|
|
86
|
+
const model = new Model({
|
|
87
|
+
cache,
|
|
88
|
+
logger,
|
|
89
|
+
...config,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await model.initialize(JSON.parse(JSON.stringify(requestToPayRequest)));
|
|
93
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
test('executes all two stages without halting when AUTO_ACCEPT_PARTY is true', async () => {
|
|
98
|
+
config.autoAcceptParty = true;
|
|
99
|
+
|
|
100
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
101
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
MojaloopRequests.__postTransactionRequests = jest.fn((postTransactionRequestsBody) => {
|
|
106
|
+
// simulate a callback with the quote response
|
|
107
|
+
emitTransactionRequestResponseCacheMessage(cache, postTransactionRequestsBody.transactionRequestId, transactionRequestResponse);
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const model = new Model({
|
|
112
|
+
cache,
|
|
113
|
+
logger,
|
|
114
|
+
...config,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await model.initialize(JSON.parse(JSON.stringify(requestToPayRequest)));
|
|
118
|
+
|
|
119
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
120
|
+
|
|
121
|
+
// start the model running
|
|
122
|
+
const result = await model.run();
|
|
123
|
+
|
|
124
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(MojaloopRequests.__postTransactionRequests).toHaveBeenCalledTimes(1);
|
|
126
|
+
|
|
127
|
+
// check we stopped at payeeResolved state
|
|
128
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
129
|
+
expect(result.requestToPayState).toBe('RECEIVED');
|
|
130
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('resolves payee and halts when AUTO_ACCEPT_PARTY is false', async () => {
|
|
134
|
+
config.autoAcceptParty = false;
|
|
135
|
+
|
|
136
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
137
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
138
|
+
return Promise.resolve();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const model = new Model({
|
|
142
|
+
cache,
|
|
143
|
+
logger,
|
|
144
|
+
...config,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await model.initialize(JSON.parse(JSON.stringify(requestToPayRequest)));
|
|
148
|
+
|
|
149
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
150
|
+
|
|
151
|
+
// start the model running
|
|
152
|
+
const resultPromise = model.run();
|
|
153
|
+
|
|
154
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
155
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
156
|
+
|
|
157
|
+
// wait for the model to reach a terminal state
|
|
158
|
+
const result = await resultPromise;
|
|
159
|
+
|
|
160
|
+
// check we stopped at payeeResolved state
|
|
161
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
162
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
});
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* Murthy Kakarlamudi - murthy@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
// we use a mock standard components lib to intercept and mock certain funcs
|
|
14
|
+
jest.mock('@mojaloop/sdk-standard-components');
|
|
15
|
+
jest.mock('redis');
|
|
16
|
+
|
|
17
|
+
const Cache = require('../../../../lib/cache');
|
|
18
|
+
const Model = require('../../../../lib/model').OutboundRequestToPayTransferModel;
|
|
19
|
+
|
|
20
|
+
const { MojaloopRequests, Logger } = require('@mojaloop/sdk-standard-components');
|
|
21
|
+
const StateMachine = require('javascript-state-machine');
|
|
22
|
+
|
|
23
|
+
const defaultConfig = require('./data/defaultConfig');
|
|
24
|
+
const requestToPayTransferRequest = require('./data/requestToPayTransferRequest');
|
|
25
|
+
const quoteResponseTemplate = require('./data/quoteResponse');
|
|
26
|
+
const authorizationsResponse = require('./data/authorizationsResponse');
|
|
27
|
+
const transferFulfil = require('./data/transferFulfil');
|
|
28
|
+
|
|
29
|
+
// util function to simulate a quote response subscription message on a cache client
|
|
30
|
+
const emitQuoteResponseCacheMessage = (cache, quoteId, quoteResponse) => cache.publish(`qt_${quoteId}`, JSON.stringify(quoteResponse));
|
|
31
|
+
|
|
32
|
+
// util function to simulate a authorizations response subscription message on a cache client
|
|
33
|
+
const emitAuthorizationsResponseCacheMessage = (cache, authorizationsResponse) => cache.publish(`otp_${requestToPayTransferRequest.requestToPayTransactionId}`, JSON.stringify(authorizationsResponse));
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// util function to simulate a transfer fulfilment subscription message on a cache client
|
|
37
|
+
const emitTransferFulfilCacheMessage = (cache, transferId, fulfil) => cache.publish(`tf_${transferId}`, JSON.stringify(fulfil));
|
|
38
|
+
|
|
39
|
+
describe('outboundRequestToPayTransferModel', () => {
|
|
40
|
+
let quoteResponse;
|
|
41
|
+
let config;
|
|
42
|
+
let logger;
|
|
43
|
+
let cache;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} opts
|
|
48
|
+
* @param {Number} opts.expirySeconds
|
|
49
|
+
* @param {Object} opts.delays
|
|
50
|
+
* @param {Number} delays.requestQuotes
|
|
51
|
+
* @param {Number} delays.prepareTransfer
|
|
52
|
+
* @param {Object} opts.rejects
|
|
53
|
+
* @param {boolean} rejects.quoteResponse
|
|
54
|
+
* @param {boolean} rejects.transferFulfils
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
beforeAll(async () => {
|
|
59
|
+
logger = new Logger.Logger({ context: { app: 'outbound-model-unit-tests-cache' }, stringify: () => '' });
|
|
60
|
+
quoteResponse = JSON.parse(JSON.stringify(quoteResponseTemplate));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
config = JSON.parse(JSON.stringify(defaultConfig));
|
|
65
|
+
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve());
|
|
66
|
+
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve());
|
|
67
|
+
MojaloopRequests.__getAuthorizations = jest.fn(() => Promise.resolve());
|
|
68
|
+
MojaloopRequests.__postQuotes = jest.fn(() => Promise.resolve());
|
|
69
|
+
MojaloopRequests.__putQuotes = jest.fn(() => Promise.resolve());
|
|
70
|
+
MojaloopRequests.__putQuotesError = jest.fn(() => Promise.resolve());
|
|
71
|
+
MojaloopRequests.__postTransfers = jest.fn(() => Promise.resolve());
|
|
72
|
+
|
|
73
|
+
cache = new Cache({
|
|
74
|
+
host: 'dummycachehost',
|
|
75
|
+
port: 1234,
|
|
76
|
+
logger,
|
|
77
|
+
});
|
|
78
|
+
await cache.connect();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
afterEach(async () => {
|
|
82
|
+
await cache.disconnect();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('initializes to starting state', async () => {
|
|
86
|
+
const model = new Model({
|
|
87
|
+
cache,
|
|
88
|
+
logger,
|
|
89
|
+
...config,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await model.initialize(JSON.parse(JSON.stringify(requestToPayTransferRequest)));
|
|
93
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
test('executes all three transfer stages without halting when AUTO_ACCEPT_QUOTES and AUTO_ACCEPT_PARTY are true', async () => {
|
|
98
|
+
config.autoAcceptR2PDeviceOTP = true;
|
|
99
|
+
config.autoAcceptR2PDeviceQuotes = true;
|
|
100
|
+
config.autoAcceptQuotes = true;
|
|
101
|
+
|
|
102
|
+
MojaloopRequests.__getAuthorizations = jest.fn(() => {
|
|
103
|
+
emitAuthorizationsResponseCacheMessage(cache, authorizationsResponse);
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
108
|
+
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
109
|
+
// including extension list
|
|
110
|
+
const extensionList = postQuotesBody.extensionList.extension;
|
|
111
|
+
expect(extensionList).toBeTruthy();
|
|
112
|
+
expect(extensionList.length).toBe(2);
|
|
113
|
+
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
114
|
+
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
115
|
+
|
|
116
|
+
// simulate a callback with the quote response
|
|
117
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
122
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
123
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
124
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
125
|
+
|
|
126
|
+
const extensionList = postTransfersBody.extensionList.extension;
|
|
127
|
+
expect(extensionList).toBeTruthy();
|
|
128
|
+
expect(extensionList.length).toBe(2);
|
|
129
|
+
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
130
|
+
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
131
|
+
|
|
132
|
+
expect(destFspId).toBe(quoteResponse.headers['fspiop-source']);
|
|
133
|
+
expect(quoteResponse.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
134
|
+
|
|
135
|
+
// simulate a callback with the transfer fulfilment
|
|
136
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
137
|
+
return Promise.resolve();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const model = new Model({
|
|
141
|
+
cache,
|
|
142
|
+
logger,
|
|
143
|
+
...config,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await model.initialize(JSON.parse(JSON.stringify(requestToPayTransferRequest)));
|
|
147
|
+
|
|
148
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
149
|
+
|
|
150
|
+
// start the model running
|
|
151
|
+
const result = await model.run();
|
|
152
|
+
|
|
153
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
154
|
+
expect(MojaloopRequests.__getAuthorizations).toHaveBeenCalledTimes(1);
|
|
155
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
156
|
+
|
|
157
|
+
// check we stopped at payeeResolved state
|
|
158
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
159
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// test('halts and resumes after quotes and otp stages when AUTO_ACCEPT_QUOTES is false and AUTO_ACCEPT_OTP is false', async () => {
|
|
163
|
+
|
|
164
|
+
// config.autoAcceptR2PDeviceOTP = false;
|
|
165
|
+
// config.autoAcceptR2PDeviceQuotes = false;
|
|
166
|
+
|
|
167
|
+
// let model = new Model({
|
|
168
|
+
// cache,
|
|
169
|
+
// logger,
|
|
170
|
+
// ...config,
|
|
171
|
+
// });
|
|
172
|
+
|
|
173
|
+
// await model.initialize(JSON.parse(JSON.stringify(requestToPayTransferRequest)));
|
|
174
|
+
|
|
175
|
+
// expect(StateMachine.__instance.state).toBe('start');
|
|
176
|
+
|
|
177
|
+
// // start the model running
|
|
178
|
+
// let resultPromise = model.run();
|
|
179
|
+
|
|
180
|
+
// // now we started the model running we simulate a callback with the quote response
|
|
181
|
+
// cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
182
|
+
|
|
183
|
+
// // wait for the model to reach a terminal state
|
|
184
|
+
// let result = await resultPromise;
|
|
185
|
+
|
|
186
|
+
// // check we stopped at quoteReceived state
|
|
187
|
+
// expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
188
|
+
// expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
189
|
+
|
|
190
|
+
// const requestToPayTransactionId = requestToPayTransferRequest.requestToPayTransactionId;
|
|
191
|
+
|
|
192
|
+
// // load a new model from the saved state
|
|
193
|
+
// model = new Model({
|
|
194
|
+
// cache,
|
|
195
|
+
// logger,
|
|
196
|
+
// ...config,
|
|
197
|
+
// });
|
|
198
|
+
|
|
199
|
+
// await model.load(requestToPayTransactionId);
|
|
200
|
+
|
|
201
|
+
// // check the model loaded to the correct state
|
|
202
|
+
// expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
203
|
+
|
|
204
|
+
// // now run the model again. this should trigger transition to quote request
|
|
205
|
+
// resultPromise = model.run();
|
|
206
|
+
|
|
207
|
+
// // now we started the model running we simulate a callback with the otp response
|
|
208
|
+
// cache.publish(`otp_${requestToPayTransactionId}`, JSON.stringify(authorizationsResponse));
|
|
209
|
+
|
|
210
|
+
// // wait for the model to reach a terminal state
|
|
211
|
+
// result = await resultPromise;
|
|
212
|
+
|
|
213
|
+
// // check we stopped at quoteReceived state
|
|
214
|
+
// expect(result.currentState).toBe('WAITING_FOR_OTP_ACCEPTANCE');
|
|
215
|
+
// expect(StateMachine.__instance.state).toBe('otpReceived');
|
|
216
|
+
|
|
217
|
+
// // load a new model from the saved state
|
|
218
|
+
// model = new Model({
|
|
219
|
+
// cache,
|
|
220
|
+
// logger,
|
|
221
|
+
// ...config,
|
|
222
|
+
// });
|
|
223
|
+
|
|
224
|
+
// await model.load(requestToPayTransactionId);
|
|
225
|
+
|
|
226
|
+
// // check the model loaded to the correct state
|
|
227
|
+
// expect(StateMachine.__instance.state).toBe('otpReceived');
|
|
228
|
+
|
|
229
|
+
// // now run the model again. this should trigger transition to quote request
|
|
230
|
+
// resultPromise = model.run();
|
|
231
|
+
|
|
232
|
+
// // now we started the model running we simulate a callback with the transfer fulfilment
|
|
233
|
+
// cache.publish(`tf_${model.data.transferId}`, JSON.stringify(transferFulfil));
|
|
234
|
+
|
|
235
|
+
// // wait for the model to reach a terminal state
|
|
236
|
+
// result = await resultPromise;
|
|
237
|
+
|
|
238
|
+
// // check we stopped at quoteReceived state
|
|
239
|
+
// expect(result.currentState).toBe('COMPLETED');
|
|
240
|
+
// expect(StateMachine.__instance.state).toBe('succeeded');
|
|
241
|
+
|
|
242
|
+
// });
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
});
|
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
/**************************************************************************
|
|
2
|
+
* (C) Copyright ModusBox Inc. 2019 - All rights reserved. *
|
|
3
|
+
* *
|
|
4
|
+
* This file is made available under the terms of the license agreement *
|
|
5
|
+
* specified in the corresponding source code repository. *
|
|
6
|
+
* *
|
|
7
|
+
* ORIGINAL AUTHOR: *
|
|
8
|
+
* James Bush - james.bush@modusbox.com *
|
|
9
|
+
**************************************************************************/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
// we use a mock standard components lib to intercept and mock certain funcs
|
|
14
|
+
jest.mock('@mojaloop/sdk-standard-components');
|
|
15
|
+
jest.mock('redis');
|
|
16
|
+
|
|
17
|
+
const Cache = require('../../../../lib/cache');
|
|
18
|
+
const Model = require('../../../../lib/model').OutboundTransfersModel;
|
|
19
|
+
const PartiesModel = require('../../../../lib/model').PartiesModel;
|
|
20
|
+
|
|
21
|
+
const { MojaloopRequests, Logger } = require('@mojaloop/sdk-standard-components');
|
|
22
|
+
const StateMachine = require('javascript-state-machine');
|
|
23
|
+
|
|
24
|
+
const defaultConfig = require('./data/defaultConfig');
|
|
25
|
+
const transferRequest = require('./data/transferRequest');
|
|
26
|
+
const payeeParty = require('./data/payeeParty');
|
|
27
|
+
const quoteResponseTemplate = require('./data/quoteResponse');
|
|
28
|
+
const transferFulfil = require('./data/transferFulfil');
|
|
29
|
+
|
|
30
|
+
const genPartyId = (party) => {
|
|
31
|
+
const { partyIdType, partyIdentifier, partySubIdOrType } = party.party.partyIdInfo;
|
|
32
|
+
return PartiesModel.channelName({
|
|
33
|
+
type: partyIdType,
|
|
34
|
+
id: partyIdentifier,
|
|
35
|
+
subId: partySubIdOrType
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// util function to simulate a party resolution subscription message on a cache client
|
|
40
|
+
const emitPartyCacheMessage = (cache, party) => cache.publish(genPartyId(party), JSON.stringify(party));
|
|
41
|
+
|
|
42
|
+
// util function to simulate a quote response subscription message on a cache client
|
|
43
|
+
const emitQuoteResponseCacheMessage = (cache, quoteId, quoteResponse) => cache.publish(`qt_${quoteId}`, JSON.stringify(quoteResponse));
|
|
44
|
+
|
|
45
|
+
// util function to simulate a transfer fulfilment subscription message on a cache client
|
|
46
|
+
const emitTransferFulfilCacheMessage = (cache, transferId, fulfil) => cache.publish(`tf_${transferId}`, JSON.stringify(fulfil));
|
|
47
|
+
|
|
48
|
+
describe('outboundModel', () => {
|
|
49
|
+
let quoteResponse;
|
|
50
|
+
let config;
|
|
51
|
+
let logger;
|
|
52
|
+
let cache;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} opts
|
|
57
|
+
* @param {Number} opts.expirySeconds
|
|
58
|
+
* @param {Object} opts.delays
|
|
59
|
+
* @param {Number} delays.requestQuotes
|
|
60
|
+
* @param {Number} delays.prepareTransfer
|
|
61
|
+
* @param {Object} opts.rejects
|
|
62
|
+
* @param {boolean} rejects.quoteResponse
|
|
63
|
+
* @param {boolean} rejects.transferFulfils
|
|
64
|
+
*/
|
|
65
|
+
async function testTransferWithDelay({expirySeconds, delays, rejects}) {
|
|
66
|
+
const config = JSON.parse(JSON.stringify(defaultConfig));
|
|
67
|
+
config.autoAcceptParty = true;
|
|
68
|
+
config.autoAcceptQuotes = true;
|
|
69
|
+
config.expirySeconds = expirySeconds;
|
|
70
|
+
config.rejectExpiredQuoteResponses = rejects.quoteResponse;
|
|
71
|
+
config.rejectExpiredTransferFulfils = rejects.transferFulfils;
|
|
72
|
+
|
|
73
|
+
// simulate a callback with the resolved party
|
|
74
|
+
MojaloopRequests.__getParties = jest.fn(() => emitPartyCacheMessage(cache, payeeParty));
|
|
75
|
+
|
|
76
|
+
// simulate a delayed callback with the quote response
|
|
77
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
80
|
+
}, delays.requestQuotes ? delays.requestQuotes * 1000 : 0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// simulate a delayed callback with the transfer fulfilment
|
|
84
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
85
|
+
setTimeout(() => {
|
|
86
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
87
|
+
}, delays.prepareTransfer ? delays.prepareTransfer * 1000 : 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const model = new Model({
|
|
91
|
+
...config,
|
|
92
|
+
cache,
|
|
93
|
+
logger,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
97
|
+
|
|
98
|
+
let expectError;
|
|
99
|
+
if (rejects.quoteResponse && delays.requestQuotes && expirySeconds < delays.requestQuotes) {
|
|
100
|
+
expectError = 'Quote response missed expiry deadline';
|
|
101
|
+
}
|
|
102
|
+
if (rejects.transferFulfils && delays.prepareTransfer && expirySeconds < delays.prepareTransfer) {
|
|
103
|
+
expectError = 'Transfer fulfil missed expiry deadline';
|
|
104
|
+
}
|
|
105
|
+
if (expectError) {
|
|
106
|
+
await expect(model.run()).rejects.toThrowError(expectError);
|
|
107
|
+
} else {
|
|
108
|
+
const result = await model.run();
|
|
109
|
+
await expect(result.currentState).toBe('COMPLETED');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
beforeAll(async () => {
|
|
114
|
+
logger = new Logger.Logger({ context: { app: 'outbound-model-unit-tests-cache' }, stringify: () => '' });
|
|
115
|
+
quoteResponse = JSON.parse(JSON.stringify(quoteResponseTemplate));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
beforeEach(async () => {
|
|
119
|
+
config = JSON.parse(JSON.stringify(defaultConfig));
|
|
120
|
+
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve());
|
|
121
|
+
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve());
|
|
122
|
+
MojaloopRequests.__postQuotes = jest.fn(() => Promise.resolve());
|
|
123
|
+
MojaloopRequests.__putQuotes = jest.fn(() => Promise.resolve());
|
|
124
|
+
MojaloopRequests.__putQuotesError = jest.fn(() => Promise.resolve());
|
|
125
|
+
MojaloopRequests.__postTransfers = jest.fn(() => Promise.resolve());
|
|
126
|
+
|
|
127
|
+
cache = new Cache({
|
|
128
|
+
host: 'dummycachehost',
|
|
129
|
+
port: 1234,
|
|
130
|
+
logger,
|
|
131
|
+
});
|
|
132
|
+
await cache.connect();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
afterEach(async () => {
|
|
136
|
+
await cache.disconnect();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('initializes to starting state', async () => {
|
|
140
|
+
const model = new Model({
|
|
141
|
+
cache,
|
|
142
|
+
logger,
|
|
143
|
+
...config,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
147
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
test('executes all three transfer stages without halting when AUTO_ACCEPT_PARTY and AUTO_ACCEPT_QUOTES are true', async () => {
|
|
152
|
+
config.autoAcceptParty = true;
|
|
153
|
+
config.autoAcceptQuotes = true;
|
|
154
|
+
|
|
155
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
156
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
157
|
+
return Promise.resolve();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
161
|
+
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
162
|
+
// including extension list
|
|
163
|
+
const extensionList = postQuotesBody.extensionList.extension;
|
|
164
|
+
expect(extensionList).toBeTruthy();
|
|
165
|
+
expect(extensionList.length).toBe(2);
|
|
166
|
+
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
167
|
+
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
168
|
+
|
|
169
|
+
// simulate a callback with the quote response
|
|
170
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
171
|
+
return Promise.resolve();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
175
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
176
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
177
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
178
|
+
|
|
179
|
+
const extensionList = postTransfersBody.extensionList.extension;
|
|
180
|
+
expect(extensionList).toBeTruthy();
|
|
181
|
+
expect(extensionList.length).toBe(2);
|
|
182
|
+
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
183
|
+
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
184
|
+
|
|
185
|
+
expect(destFspId).toBe(quoteResponse.headers['fspiop-source']);
|
|
186
|
+
expect(model.data.to.fspId).toBe(payeeParty.party.partyIdInfo.fspId);
|
|
187
|
+
expect(quoteResponse.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
188
|
+
|
|
189
|
+
// simulate a callback with the transfer fulfilment
|
|
190
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
191
|
+
return Promise.resolve();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const model = new Model({
|
|
195
|
+
cache,
|
|
196
|
+
logger,
|
|
197
|
+
...config,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
201
|
+
|
|
202
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
203
|
+
|
|
204
|
+
// start the model running
|
|
205
|
+
const result = await model.run();
|
|
206
|
+
|
|
207
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
208
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
210
|
+
|
|
211
|
+
// check we stopped at payeeResolved state
|
|
212
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
213
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
test('uses quote response transfer amount for transfer prepare', async () => {
|
|
218
|
+
config.autoAcceptParty = true;
|
|
219
|
+
config.autoAcceptQuotes = true;
|
|
220
|
+
|
|
221
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
222
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
223
|
+
return Promise.resolve();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// change the the transfer amount and currency in the quote response
|
|
227
|
+
// so it is different to the initial request
|
|
228
|
+
quoteResponse.data.transferAmount = {
|
|
229
|
+
currency: 'XYZ',
|
|
230
|
+
amount: '9876543210'
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
expect(quoteResponse.data.transferAmount).not.toEqual({
|
|
234
|
+
amount: transferRequest.amount,
|
|
235
|
+
currency: transferRequest.currency
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
239
|
+
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
240
|
+
// including extension list
|
|
241
|
+
const extensionList = postQuotesBody.extensionList.extension;
|
|
242
|
+
expect(extensionList).toBeTruthy();
|
|
243
|
+
expect(extensionList.length).toBe(2);
|
|
244
|
+
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
245
|
+
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
246
|
+
|
|
247
|
+
// simulate a callback with the quote response
|
|
248
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
249
|
+
return Promise.resolve();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
253
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
254
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
255
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
256
|
+
|
|
257
|
+
const extensionList = postTransfersBody.extensionList.extension;
|
|
258
|
+
expect(extensionList).toBeTruthy();
|
|
259
|
+
expect(extensionList.length).toBe(2);
|
|
260
|
+
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
261
|
+
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
262
|
+
|
|
263
|
+
expect(destFspId).toBe(quoteResponse.headers['fspiop-source']);
|
|
264
|
+
expect(model.data.to.fspId).toBe(payeeParty.party.partyIdInfo.fspId);
|
|
265
|
+
expect(quoteResponse.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
266
|
+
|
|
267
|
+
expect(postTransfersBody.amount).toEqual(quoteResponse.data.transferAmount);
|
|
268
|
+
|
|
269
|
+
// simulate a callback with the transfer fulfilment
|
|
270
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
271
|
+
return Promise.resolve();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const model = new Model({
|
|
275
|
+
cache,
|
|
276
|
+
logger,
|
|
277
|
+
...config,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
281
|
+
|
|
282
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
283
|
+
|
|
284
|
+
// start the model running
|
|
285
|
+
const result = await model.run();
|
|
286
|
+
|
|
287
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
288
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
289
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
290
|
+
|
|
291
|
+
// check we stopped at payeeResolved state
|
|
292
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
293
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
test('test get transfer', async () => {
|
|
298
|
+
MojaloopRequests.__getTransfers = jest.fn((transferId) => {
|
|
299
|
+
emitTransferFulfilCacheMessage(cache, transferId, transferFulfil);
|
|
300
|
+
return Promise.resolve();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const model = new Model({
|
|
304
|
+
cache,
|
|
305
|
+
logger,
|
|
306
|
+
...config,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const TRANSFER_ID = 'tx-id000011';
|
|
310
|
+
|
|
311
|
+
await model.initialize(JSON.parse(JSON.stringify({
|
|
312
|
+
...transferRequest,
|
|
313
|
+
currentState: 'getTransfer',
|
|
314
|
+
transferId: TRANSFER_ID,
|
|
315
|
+
})));
|
|
316
|
+
|
|
317
|
+
expect(StateMachine.__instance.state).toBe('getTransfer');
|
|
318
|
+
|
|
319
|
+
// start the model running
|
|
320
|
+
const result = await model.run();
|
|
321
|
+
|
|
322
|
+
expect(MojaloopRequests.__getTransfers).toHaveBeenCalledTimes(1);
|
|
323
|
+
|
|
324
|
+
// check we stopped at payeeResolved state
|
|
325
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
326
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
test('resolves payee and halts when AUTO_ACCEPT_PARTY is false', async () => {
|
|
331
|
+
config.autoAcceptParty = false;
|
|
332
|
+
|
|
333
|
+
const model = new Model({
|
|
334
|
+
cache,
|
|
335
|
+
logger,
|
|
336
|
+
...config,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
340
|
+
|
|
341
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
342
|
+
|
|
343
|
+
// start the model running
|
|
344
|
+
const resultPromise = model.run();
|
|
345
|
+
|
|
346
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
347
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
348
|
+
|
|
349
|
+
// wait for the model to reach a terminal state
|
|
350
|
+
const result = await resultPromise;
|
|
351
|
+
|
|
352
|
+
// check we stopped at payeeResolved state
|
|
353
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
354
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
test('halts after resolving payee, resumes and then halts after receiving quote response when AUTO_ACCEPT_PARTY is false and AUTO_ACCEPT_QUOTES is false', async () => {
|
|
359
|
+
config.autoAcceptParty = false;
|
|
360
|
+
config.autoAcceptQuotes = false;
|
|
361
|
+
|
|
362
|
+
let model = new Model({
|
|
363
|
+
cache,
|
|
364
|
+
logger,
|
|
365
|
+
...config,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
369
|
+
|
|
370
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
371
|
+
|
|
372
|
+
// start the model running
|
|
373
|
+
let resultPromise = model.run();
|
|
374
|
+
|
|
375
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
376
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
377
|
+
|
|
378
|
+
// wait for the model to reach a terminal state
|
|
379
|
+
let result = await resultPromise;
|
|
380
|
+
|
|
381
|
+
// check we stopped at payeeResolved state
|
|
382
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
383
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
384
|
+
|
|
385
|
+
const transferId = result.transferId;
|
|
386
|
+
|
|
387
|
+
// load a new model from the saved state
|
|
388
|
+
model = new Model({
|
|
389
|
+
cache,
|
|
390
|
+
logger,
|
|
391
|
+
...config,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await model.load(transferId);
|
|
395
|
+
|
|
396
|
+
// check the model loaded to the correct state
|
|
397
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
398
|
+
|
|
399
|
+
// now run the model again. this should trigger transition to quote request
|
|
400
|
+
resultPromise = model.run();
|
|
401
|
+
|
|
402
|
+
// now we started the model running we simulate a callback with the quote response
|
|
403
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
404
|
+
|
|
405
|
+
// wait for the model to reach a terminal state
|
|
406
|
+
result = await resultPromise;
|
|
407
|
+
|
|
408
|
+
// check we stopped at payeeResolved state
|
|
409
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
410
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
test('halts and resumes after parties and quotes stages when AUTO_ACCEPT_PARTY is false and AUTO_ACCEPT_QUOTES is false', async () => {
|
|
415
|
+
config.autoAcceptParty = false;
|
|
416
|
+
config.autoAcceptQuotes = false;
|
|
417
|
+
|
|
418
|
+
let model = new Model({
|
|
419
|
+
cache,
|
|
420
|
+
logger,
|
|
421
|
+
...config,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
425
|
+
|
|
426
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
427
|
+
|
|
428
|
+
// start the model running
|
|
429
|
+
let resultPromise = model.run();
|
|
430
|
+
|
|
431
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
432
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
433
|
+
|
|
434
|
+
// wait for the model to reach a terminal state
|
|
435
|
+
let result = await resultPromise;
|
|
436
|
+
|
|
437
|
+
// check we stopped at payeeResolved state
|
|
438
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
439
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
440
|
+
|
|
441
|
+
const transferId = result.transferId;
|
|
442
|
+
|
|
443
|
+
// load a new model from the saved state
|
|
444
|
+
model = new Model({
|
|
445
|
+
cache,
|
|
446
|
+
logger,
|
|
447
|
+
...config,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
await model.load(transferId);
|
|
451
|
+
|
|
452
|
+
// check the model loaded to the correct state
|
|
453
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
454
|
+
|
|
455
|
+
// now run the model again. this should trigger transition to quote request
|
|
456
|
+
resultPromise = model.run();
|
|
457
|
+
|
|
458
|
+
// now we started the model running we simulate a callback with the quote response
|
|
459
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
460
|
+
|
|
461
|
+
// wait for the model to reach a terminal state
|
|
462
|
+
result = await resultPromise;
|
|
463
|
+
|
|
464
|
+
// check we stopped at quoteReceived state
|
|
465
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
466
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
467
|
+
|
|
468
|
+
// load a new model from the saved state
|
|
469
|
+
model = new Model({
|
|
470
|
+
cache,
|
|
471
|
+
logger,
|
|
472
|
+
...config,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
await model.load(transferId);
|
|
476
|
+
|
|
477
|
+
// check the model loaded to the correct state
|
|
478
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
479
|
+
|
|
480
|
+
// now run the model again. this should trigger transition to quote request
|
|
481
|
+
resultPromise = model.run();
|
|
482
|
+
|
|
483
|
+
// now we started the model running we simulate a callback with the transfer fulfilment
|
|
484
|
+
cache.publish(`tf_${model.data.transferId}`, JSON.stringify(transferFulfil));
|
|
485
|
+
|
|
486
|
+
// wait for the model to reach a terminal state
|
|
487
|
+
result = await resultPromise;
|
|
488
|
+
|
|
489
|
+
// check we stopped at quoteReceived state
|
|
490
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
491
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test('uses payee party fspid for transfer prepare when config USE_QUOTE_SOURCE_FSP_AS_TRANSFER_PAYEE_FSP is false', async () => {
|
|
495
|
+
config.autoAcceptParty = true;
|
|
496
|
+
config.autoAcceptQuotes = true;
|
|
497
|
+
config.useQuoteSourceFSPAsTransferPayeeFSP = false;
|
|
498
|
+
|
|
499
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
500
|
+
// simulate a callback with the resolved party
|
|
501
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
502
|
+
return Promise.resolve();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
506
|
+
// simulate a callback with the quote response
|
|
507
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
508
|
+
return Promise.resolve();
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
512
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
513
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
514
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
515
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
516
|
+
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
517
|
+
expect(payeeFsp).toEqual(payeeParty.party.partyIdInfo.fspId);
|
|
518
|
+
|
|
519
|
+
// simulate a callback with the transfer fulfilment
|
|
520
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
521
|
+
return Promise.resolve();
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const model = new Model({
|
|
525
|
+
cache,
|
|
526
|
+
logger,
|
|
527
|
+
...config,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
531
|
+
|
|
532
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
533
|
+
|
|
534
|
+
// start the model running
|
|
535
|
+
const resultPromise = model.run();
|
|
536
|
+
|
|
537
|
+
// wait for the model to reach a terminal state
|
|
538
|
+
const result = await resultPromise;
|
|
539
|
+
|
|
540
|
+
// check we stopped at payeeResolved state
|
|
541
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
542
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('uses quote response source fspid for transfer prepare when config USE_QUOTE_SOURCE_FSP_AS_TRANSFER_PAYEE_FSP is true', async () => {
|
|
546
|
+
config.autoAcceptParty = true;
|
|
547
|
+
config.autoAcceptQuotes = true;
|
|
548
|
+
config.useQuoteSourceFSPAsTransferPayeeFSP = true;
|
|
549
|
+
|
|
550
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
551
|
+
// simulate a callback with the resolved party
|
|
552
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
553
|
+
return Promise.resolve();
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
557
|
+
// simulate a callback with the quote response
|
|
558
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
559
|
+
return Promise.resolve();
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
563
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
564
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
565
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
566
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
567
|
+
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
568
|
+
expect(payeeFsp).toEqual(quoteResponse.headers['fspiop-source']);
|
|
569
|
+
|
|
570
|
+
// simulate a callback with the transfer fulfilment
|
|
571
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
572
|
+
return Promise.resolve();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
const model = new Model({
|
|
576
|
+
cache,
|
|
577
|
+
logger,
|
|
578
|
+
...config,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
582
|
+
|
|
583
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
584
|
+
|
|
585
|
+
// start the model running
|
|
586
|
+
const resultPromise = model.run();
|
|
587
|
+
|
|
588
|
+
// wait for the model to reach a terminal state
|
|
589
|
+
const result = await resultPromise;
|
|
590
|
+
|
|
591
|
+
// check we stopped at payeeResolved state
|
|
592
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
593
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test('pass quote response `expiration` deadline', () =>
|
|
597
|
+
testTransferWithDelay({
|
|
598
|
+
expirySeconds: 2,
|
|
599
|
+
delays: {
|
|
600
|
+
requestQuotes: 1,
|
|
601
|
+
},
|
|
602
|
+
rejects: {
|
|
603
|
+
quoteResponse: true,
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
test('pass transfer fulfills `expiration` deadline', () =>
|
|
609
|
+
testTransferWithDelay({
|
|
610
|
+
expirySeconds: 2,
|
|
611
|
+
delays: {
|
|
612
|
+
prepareTransfer: 1,
|
|
613
|
+
},
|
|
614
|
+
rejects: {
|
|
615
|
+
transferFulfils: true,
|
|
616
|
+
}
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
test('pass all stages `expiration` deadlines', () =>
|
|
621
|
+
testTransferWithDelay({
|
|
622
|
+
expirySeconds: 2,
|
|
623
|
+
delays: {
|
|
624
|
+
requestQuotes: 1,
|
|
625
|
+
prepareTransfer: 1,
|
|
626
|
+
},
|
|
627
|
+
rejects: {
|
|
628
|
+
quoteResponse: true,
|
|
629
|
+
transferFulfils: true,
|
|
630
|
+
}
|
|
631
|
+
})
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
test('fail on quote response `expiration` deadline', () =>
|
|
635
|
+
testTransferWithDelay({
|
|
636
|
+
expirySeconds: 1,
|
|
637
|
+
delays: {
|
|
638
|
+
requestQuotes: 2,
|
|
639
|
+
},
|
|
640
|
+
rejects: {
|
|
641
|
+
quoteResponse: true,
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
test('fail on transfer fulfills `expiration` deadline', () =>
|
|
647
|
+
testTransferWithDelay({
|
|
648
|
+
expirySeconds: 1,
|
|
649
|
+
delays: {
|
|
650
|
+
prepareTransfer: 2,
|
|
651
|
+
},
|
|
652
|
+
rejects: {
|
|
653
|
+
transferFulfils: true,
|
|
654
|
+
}
|
|
655
|
+
})
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
test('Throws with mojaloop error in response body when party resolution error callback occurs', async () => {
|
|
659
|
+
config.autoAcceptParty = true;
|
|
660
|
+
config.autoAcceptQuotes = true;
|
|
661
|
+
|
|
662
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
663
|
+
// simulate a callback with the resolved party
|
|
664
|
+
cache.publish(genPartyId(payeeParty), JSON.stringify(expectError));
|
|
665
|
+
return Promise.resolve();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const model = new Model({
|
|
669
|
+
cache,
|
|
670
|
+
logger,
|
|
671
|
+
...config,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
675
|
+
|
|
676
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
677
|
+
|
|
678
|
+
const expectError = {
|
|
679
|
+
errorInformation: {
|
|
680
|
+
errorCode: '3204',
|
|
681
|
+
errorDescription: 'Party not found'
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const errMsg = 'Got an error response resolving party: { errorInformation: { errorCode: \'3204\', errorDescription: \'Party not found\' } }';
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
await model.run();
|
|
689
|
+
}
|
|
690
|
+
catch(err) {
|
|
691
|
+
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
692
|
+
expect(err.transferState).toBeTruthy();
|
|
693
|
+
expect(err.transferState.lastError).toBeTruthy();
|
|
694
|
+
expect(err.transferState.lastError.mojaloopError).toEqual(expectError);
|
|
695
|
+
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
throw new Error('Outbound model should have thrown');
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
test('Throws with mojaloop error in response body when quote request error callback occurs', async () => {
|
|
704
|
+
config.autoAcceptParty = true;
|
|
705
|
+
config.autoAcceptQuotes = true;
|
|
706
|
+
|
|
707
|
+
const expectError = {
|
|
708
|
+
type: 'quoteResponseError',
|
|
709
|
+
data: {
|
|
710
|
+
errorInformation: {
|
|
711
|
+
errorCode: '3205',
|
|
712
|
+
errorDescription: 'Quote ID not found'
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
719
|
+
// simulate a callback with the resolved party
|
|
720
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
721
|
+
return Promise.resolve();
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
725
|
+
// simulate a callback with the quote response
|
|
726
|
+
cache.publish(`qt_${postQuotesBody.quoteId}`, JSON.stringify(expectError));
|
|
727
|
+
return Promise.resolve();
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
const model = new Model({
|
|
731
|
+
cache,
|
|
732
|
+
logger,
|
|
733
|
+
...config,
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
737
|
+
|
|
738
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
739
|
+
|
|
740
|
+
const errMsg = 'Got an error response requesting quote: { errorInformation:\n { errorCode: \'3205\', errorDescription: \'Quote ID not found\' } }';
|
|
741
|
+
|
|
742
|
+
try {
|
|
743
|
+
await model.run();
|
|
744
|
+
}
|
|
745
|
+
catch(err) {
|
|
746
|
+
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
747
|
+
expect(err.transferState).toBeTruthy();
|
|
748
|
+
expect(err.transferState.lastError).toBeTruthy();
|
|
749
|
+
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data);
|
|
750
|
+
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
throw new Error('Outbound model should have thrown');
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
test('Throws with mojaloop error in response body when transfer request error callback occurs', async () => {
|
|
759
|
+
config.autoAcceptParty = true;
|
|
760
|
+
config.autoAcceptQuotes = true;
|
|
761
|
+
|
|
762
|
+
const expectError = {
|
|
763
|
+
type: 'transferError',
|
|
764
|
+
data: {
|
|
765
|
+
errorInformation: {
|
|
766
|
+
errorCode: '4001',
|
|
767
|
+
errorDescription: 'Payer FSP insufficient liquidity'
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
773
|
+
// simulate a callback with the resolved party
|
|
774
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
775
|
+
return Promise.resolve();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
779
|
+
// simulate a callback with the quote response
|
|
780
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
781
|
+
return Promise.resolve();
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
785
|
+
// simulate an error callback with the transfer fulfilment
|
|
786
|
+
cache.publish(`tf_${postTransfersBody.transferId}`, JSON.stringify(expectError));
|
|
787
|
+
return Promise.resolve();
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const model = new Model({
|
|
791
|
+
cache,
|
|
792
|
+
logger,
|
|
793
|
+
...config,
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
797
|
+
|
|
798
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
799
|
+
|
|
800
|
+
const errMsg = 'Got an error response preparing transfer: { errorInformation:\n { errorCode: \'4001\',\n errorDescription: \'Payer FSP insufficient liquidity\' } }';
|
|
801
|
+
|
|
802
|
+
try {
|
|
803
|
+
await model.run();
|
|
804
|
+
}
|
|
805
|
+
catch(err) {
|
|
806
|
+
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
807
|
+
expect(err.transferState).toBeTruthy();
|
|
808
|
+
expect(err.transferState.lastError).toBeTruthy();
|
|
809
|
+
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data);
|
|
810
|
+
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
throw new Error('Outbound model should have thrown');
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
async function testTlsServer(enableTls) {
|
|
819
|
+
config.tls.enabled = enableTls;
|
|
820
|
+
|
|
821
|
+
new Model({
|
|
822
|
+
cache,
|
|
823
|
+
logger,
|
|
824
|
+
...config
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
const scheme = enableTls ? 'https' : 'http';
|
|
828
|
+
expect(MojaloopRequests.__instance.transportScheme).toBe(scheme);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
test('Outbound server should use HTTPS if outbound mTLS enabled', () =>
|
|
832
|
+
testTlsServer(true));
|
|
833
|
+
|
|
834
|
+
test('Outbound server should use HTTP if outbound mTLS disabled', () =>
|
|
835
|
+
testTlsServer(false));
|
|
836
|
+
});
|