@mojaloop/sdk-scheme-adapter 18.0.0 → 18.0.1
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/.dockerignore +18 -0
- package/.eslintignore +2 -0
- package/CHANGELOG.md +8 -0
- package/audit-resolve.json +5 -0
- package/package.json +2 -2
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +0 -151
- package/test/__mocks__/javascript-state-machine.js +0 -21
- package/test/__mocks__/redis.js +0 -78
- package/test/__mocks__/uuidv4.js +0 -16
- package/test/config/integration.env +0 -146
- package/test/integration/lib/Outbound/data/quotesPostRequest.json +0 -52
- package/test/integration/lib/Outbound/data/transfersPostRequest.json +0 -24
- package/test/integration/lib/Outbound/parties.test.js +0 -31
- package/test/integration/lib/Outbound/quotes.test.js +0 -62
- package/test/integration/lib/Outbound/simpleTransfers.test.js +0 -70
- package/test/integration/lib/cache.test.js +0 -79
- package/test/integration/testEnv.js +0 -4
- package/test/unit/ControlClient.test.js +0 -69
- package/test/unit/ControlServer/events.js +0 -41
- package/test/unit/ControlServer/index.js +0 -227
- package/test/unit/ControlServer.test.js +0 -66
- package/test/unit/InboundServer.test.js +0 -443
- package/test/unit/TestServer.test.js +0 -392
- package/test/unit/api/accounts/accounts.test.js +0 -128
- package/test/unit/api/accounts/data/postAccountsBody.json +0 -7
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +0 -33
- package/test/unit/api/accounts/data/postAccountsErrorTimeoutResponse.json +0 -19
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +0 -31
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +0 -34
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +0 -39
- package/test/unit/api/accounts/utils.js +0 -79
- package/test/unit/api/proxy/data/proxyConfig.yaml +0 -82
- package/test/unit/api/proxy/data/requestBody.json +0 -22
- package/test/unit/api/proxy/data/requestHeaders.json +0 -5
- package/test/unit/api/proxy/data/requestQuery.json +0 -6
- package/test/unit/api/proxy/data/responseBody.json +0 -21
- package/test/unit/api/proxy/data/responseHeaders.json +0 -5
- package/test/unit/api/proxy/proxy.test.js +0 -220
- package/test/unit/api/proxy/utils.js +0 -79
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +0 -24
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +0 -18
- package/test/unit/api/transfers/data/postQuotesBody.json +0 -52
- package/test/unit/api/transfers/data/postTransfersBadBody.json +0 -17
- package/test/unit/api/transfers/data/postTransfersBody.json +0 -24
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +0 -62
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +0 -48
- package/test/unit/api/transfers/data/postTransfersSimpleBody.json +0 -26
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +0 -128
- package/test/unit/api/transfers/data/putPartiesBody.json +0 -20
- package/test/unit/api/transfers/data/putQuotesBody.json +0 -37
- package/test/unit/api/transfers/data/putTransfersBody.json +0 -17
- package/test/unit/api/transfers/transfers.test.js +0 -191
- package/test/unit/api/transfers/utils.js +0 -264
- package/test/unit/api/utils.js +0 -86
- package/test/unit/config.test.js +0 -119
- package/test/unit/data/commonHttpHeaders.json +0 -7
- package/test/unit/data/defaultConfig.json +0 -70
- package/test/unit/data/postQuotesBody.json +0 -52
- package/test/unit/data/putParticipantsBody.json +0 -12
- package/test/unit/data/putPartiesBody.json +0 -20
- package/test/unit/data/testFile.json +0 -29
- package/test/unit/data/testFile.yaml +0 -14
- package/test/unit/inboundApi/data/mockArguments.json +0 -117
- package/test/unit/inboundApi/data/mockTransactionRequest.json +0 -42
- package/test/unit/inboundApi/handlers.test.js +0 -786
- package/test/unit/index.test.js +0 -88
- package/test/unit/lib/cache.test.js +0 -145
- package/test/unit/lib/model/AccountsModel.test.js +0 -124
- package/test/unit/lib/model/InboundTransfersModel.test.js +0 -889
- package/test/unit/lib/model/OutboundBulkQuotesModel.test.js +0 -253
- package/test/unit/lib/model/OutboundBulkTransfersModel.test.js +0 -247
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +0 -166
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +0 -245
- package/test/unit/lib/model/OutboundTransfersModel.test.js +0 -1579
- package/test/unit/lib/model/PartiesModel.test.js +0 -478
- package/test/unit/lib/model/QuotesModel.test.js +0 -477
- package/test/unit/lib/model/TransfersModel.test.js +0 -481
- package/test/unit/lib/model/common/PersistentStateMachine.test.js +0 -178
- package/test/unit/lib/model/data/authorizationsResponse.json +0 -13
- package/test/unit/lib/model/data/bulkQuoteRequest.json +0 -27
- package/test/unit/lib/model/data/bulkQuoteResponse.json +0 -35
- package/test/unit/lib/model/data/bulkTransferFulfil.json +0 -13
- package/test/unit/lib/model/data/bulkTransferRequest.json +0 -29
- package/test/unit/lib/model/data/defaultConfig.json +0 -59
- package/test/unit/lib/model/data/getBulkTransfersBackendResponse.json +0 -42
- package/test/unit/lib/model/data/getBulkTransfersMojaloopResponse.json +0 -22
- package/test/unit/lib/model/data/getTransfersBackendResponse.json +0 -34
- package/test/unit/lib/model/data/getTransfersMojaloopResponse.json +0 -17
- package/test/unit/lib/model/data/mockArguments.json +0 -188
- package/test/unit/lib/model/data/mockTxnRequestsArguments.json +0 -63
- package/test/unit/lib/model/data/notificationAbortedToPayee.json +0 -10
- package/test/unit/lib/model/data/notificationReservedToPayee.json +0 -10
- package/test/unit/lib/model/data/notificationToPayee.json +0 -10
- package/test/unit/lib/model/data/payeeParty.json +0 -18
- package/test/unit/lib/model/data/putQuotesResponse.json +0 -33
- package/test/unit/lib/model/data/putTransfersResponse.json +0 -5
- package/test/unit/lib/model/data/quoteResponse.json +0 -42
- package/test/unit/lib/model/data/requestToPayRequest.json +0 -20
- package/test/unit/lib/model/data/requestToPayTransferRequest.json +0 -27
- package/test/unit/lib/model/data/transactionRequestResponse.json +0 -18
- package/test/unit/lib/model/data/transferFulfil.json +0 -10
- package/test/unit/lib/model/data/transferRequest.json +0 -26
- package/test/unit/lib/model/mockedLibRequests.js +0 -74
- package/test/unit/mockLogger.js +0 -39
- package/test/unit/outboundApi/data/bulkQuoteRequest.json +0 -28
- package/test/unit/outboundApi/data/bulkTransferRequest.json +0 -28
- package/test/unit/outboundApi/data/mockBulkQuoteError.json +0 -45
- package/test/unit/outboundApi/data/mockBulkTransferError.json +0 -48
- package/test/unit/outboundApi/data/mockError.json +0 -41
- package/test/unit/outboundApi/data/mockGetPartiesError.json +0 -4
- package/test/unit/outboundApi/data/mockRequestToPayError.json +0 -32
- package/test/unit/outboundApi/data/mockRequestToPayTransferError.json +0 -39
- package/test/unit/outboundApi/data/requestToPay.json +0 -21
- package/test/unit/outboundApi/data/requestToPayTransferRequest.json +0 -20
- package/test/unit/outboundApi/data/transferRequest.json +0 -21
- package/test/unit/outboundApi/handlers.test.js +0 -887
|
@@ -1,1579 +0,0 @@
|
|
|
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 { MetricsClient } = require('~/lib/metrics');
|
|
19
|
-
const Model = require('~/lib/model').OutboundTransfersModel;
|
|
20
|
-
const PartiesModel = require('~/lib/model').PartiesModel;
|
|
21
|
-
|
|
22
|
-
const { MojaloopRequests, Logger } = require('@mojaloop/sdk-standard-components');
|
|
23
|
-
const StateMachine = require('javascript-state-machine');
|
|
24
|
-
|
|
25
|
-
const defaultConfig = require('./data/defaultConfig');
|
|
26
|
-
const transferRequest = require('./data/transferRequest');
|
|
27
|
-
const payeeParty = require('./data/payeeParty');
|
|
28
|
-
const quoteResponseTemplate = require('./data/quoteResponse');
|
|
29
|
-
const transferFulfil = require('./data/transferFulfil');
|
|
30
|
-
|
|
31
|
-
const { SDKStateEnum } = require('../../../../src/lib/model/common');
|
|
32
|
-
const FSPIOPTransferStateEnum = require('@mojaloop/central-services-shared').Enum.Transfers.TransferState;
|
|
33
|
-
|
|
34
|
-
const genPartyId = (party) => {
|
|
35
|
-
const { partyIdType, partyIdentifier, partySubIdOrType } = party.body.party.partyIdInfo;
|
|
36
|
-
return PartiesModel.channelName({
|
|
37
|
-
type: partyIdType,
|
|
38
|
-
id: partyIdentifier,
|
|
39
|
-
subId: partySubIdOrType
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// util function to simulate a party resolution subscription message on a cache client
|
|
44
|
-
const emitPartyCacheMessage = (cache, party) => cache.publish(genPartyId(party), JSON.stringify(party));
|
|
45
|
-
const emitMultiPartiesCacheMessage = (cache, party) => cache.add(genPartyId(party), JSON.stringify(party));
|
|
46
|
-
|
|
47
|
-
// util function to simulate a quote response subscription message on a cache client
|
|
48
|
-
const emitQuoteResponseCacheMessage = (cache, quoteId, quoteResponse) => cache.publish(`qt_${quoteId}`, JSON.stringify(quoteResponse));
|
|
49
|
-
|
|
50
|
-
// util function to simulate a transfer fulfilment subscription message on a cache client
|
|
51
|
-
const emitTransferFulfilCacheMessage = (cache, transferId, fulfil) => cache.publish(`tf_${transferId}`, JSON.stringify(fulfil));
|
|
52
|
-
|
|
53
|
-
const dummyRequestsModuleResponse = {
|
|
54
|
-
originalRequest: {}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
describe('outboundModel', () => {
|
|
58
|
-
let quoteResponse;
|
|
59
|
-
let config;
|
|
60
|
-
let logger;
|
|
61
|
-
let cache;
|
|
62
|
-
let metricsClient;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
* @param {Object} opts
|
|
67
|
-
* @param {Number} opts.expirySeconds
|
|
68
|
-
* @param {Object} opts.delays
|
|
69
|
-
* @param {Number} delays.requestQuotes
|
|
70
|
-
* @param {Number} delays.prepareTransfer
|
|
71
|
-
* @param {Object} opts.rejects
|
|
72
|
-
* @param {boolean} rejects.quoteResponse
|
|
73
|
-
* @param {boolean} rejects.transferFulfils
|
|
74
|
-
*/
|
|
75
|
-
async function testTransferWithDelay({expirySeconds, delays, rejects}) {
|
|
76
|
-
const config = JSON.parse(JSON.stringify(defaultConfig));
|
|
77
|
-
config.autoAcceptParty = true;
|
|
78
|
-
config.autoAcceptQuotes = true;
|
|
79
|
-
config.expirySeconds = expirySeconds;
|
|
80
|
-
config.rejectExpiredQuoteResponses = rejects.quoteResponse;
|
|
81
|
-
config.rejectExpiredTransferFulfils = rejects.transferFulfils;
|
|
82
|
-
|
|
83
|
-
// simulate a callback with the resolved party
|
|
84
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
85
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
86
|
-
return {
|
|
87
|
-
originalRequest: {
|
|
88
|
-
headers: [],
|
|
89
|
-
body: {},
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// simulate a delayed callback with the quote response
|
|
95
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
96
|
-
setTimeout(() => {
|
|
97
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
98
|
-
}, delays.requestQuotes ? delays.requestQuotes * 1000 : 0);
|
|
99
|
-
return {
|
|
100
|
-
originalRequest: {
|
|
101
|
-
headers: [],
|
|
102
|
-
body: postQuotesBody,
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// simulate a delayed callback with the transfer fulfilment
|
|
108
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
109
|
-
setTimeout(() => {
|
|
110
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
111
|
-
}, delays.prepareTransfer ? delays.prepareTransfer * 1000 : 0);
|
|
112
|
-
return {
|
|
113
|
-
originalRequest: {
|
|
114
|
-
headers: [],
|
|
115
|
-
body: postTransfersBody,
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const model = new Model({
|
|
121
|
-
...config,
|
|
122
|
-
cache,
|
|
123
|
-
logger,
|
|
124
|
-
metricsClient,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
128
|
-
|
|
129
|
-
let expectError;
|
|
130
|
-
if (rejects.quoteResponse && delays.requestQuotes && expirySeconds < delays.requestQuotes) {
|
|
131
|
-
expectError = 'Quote response missed expiry deadline';
|
|
132
|
-
}
|
|
133
|
-
if (rejects.transferFulfils && delays.prepareTransfer && expirySeconds < delays.prepareTransfer) {
|
|
134
|
-
expectError = 'Transfer fulfil missed expiry deadline';
|
|
135
|
-
}
|
|
136
|
-
if (expectError) {
|
|
137
|
-
await expect(model.run()).rejects.toThrowError(expectError);
|
|
138
|
-
} else {
|
|
139
|
-
const result = await model.run();
|
|
140
|
-
await expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
beforeAll(async () => {
|
|
145
|
-
logger = new Logger.Logger({ context: { app: 'outbound-model-unit-tests-cache' }, stringify: () => '' });
|
|
146
|
-
quoteResponse = JSON.parse(JSON.stringify(quoteResponseTemplate));
|
|
147
|
-
metricsClient = new MetricsClient();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
beforeEach(async () => {
|
|
151
|
-
config = JSON.parse(JSON.stringify(defaultConfig));
|
|
152
|
-
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
153
|
-
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
154
|
-
MojaloopRequests.__putQuotes = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
155
|
-
MojaloopRequests.__putQuotesError = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
156
|
-
MojaloopRequests.__postQuotes = jest.fn((body) => Promise.resolve({
|
|
157
|
-
originalRequest: {
|
|
158
|
-
headers: [],
|
|
159
|
-
body: body,
|
|
160
|
-
}
|
|
161
|
-
}));
|
|
162
|
-
MojaloopRequests.__postTransfers = jest.fn((body) => Promise.resolve({
|
|
163
|
-
originalRequest: {
|
|
164
|
-
headers: [],
|
|
165
|
-
body: body,
|
|
166
|
-
}
|
|
167
|
-
}));
|
|
168
|
-
cache = new Cache({
|
|
169
|
-
cacheUrl: 'redis://dummy:1234',
|
|
170
|
-
logger,
|
|
171
|
-
});
|
|
172
|
-
await cache.connect();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
afterEach(async () => {
|
|
176
|
-
await cache.disconnect();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('initializes to starting state', async () => {
|
|
180
|
-
const model = new Model({
|
|
181
|
-
cache,
|
|
182
|
-
logger,
|
|
183
|
-
metricsClient,
|
|
184
|
-
...config,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
188
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test('executes all three transfer stages without halting when AUTO_ACCEPT_PARTY and AUTO_ACCEPT_QUOTES are true', async () => {
|
|
192
|
-
config.autoAcceptParty = true;
|
|
193
|
-
config.autoAcceptQuotes = true;
|
|
194
|
-
|
|
195
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
196
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
197
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
201
|
-
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
202
|
-
// including extension list
|
|
203
|
-
const extensionList = postQuotesBody.extensionList.extension;
|
|
204
|
-
expect(extensionList).toBeTruthy();
|
|
205
|
-
expect(extensionList.length).toBe(2);
|
|
206
|
-
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
207
|
-
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
208
|
-
|
|
209
|
-
// simulate a callback with the quote response
|
|
210
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
211
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
215
|
-
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
216
|
-
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
217
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
218
|
-
|
|
219
|
-
const extensionList = postTransfersBody.extensionList.extension;
|
|
220
|
-
expect(extensionList).toBeTruthy();
|
|
221
|
-
expect(extensionList.length).toBe(2);
|
|
222
|
-
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
223
|
-
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
224
|
-
|
|
225
|
-
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
226
|
-
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
227
|
-
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
228
|
-
|
|
229
|
-
// simulate a callback with the transfer fulfilment
|
|
230
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
231
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
const model = new Model({
|
|
235
|
-
cache,
|
|
236
|
-
logger,
|
|
237
|
-
metricsClient,
|
|
238
|
-
...config,
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
242
|
-
|
|
243
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
244
|
-
|
|
245
|
-
// start the model running
|
|
246
|
-
const result = await model.run();
|
|
247
|
-
|
|
248
|
-
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
249
|
-
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
250
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
251
|
-
|
|
252
|
-
// make sure no PATCH was sent as we did not set config or receive a RESERVED state
|
|
253
|
-
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(0);
|
|
254
|
-
|
|
255
|
-
// check we stopped at payeeResolved state
|
|
256
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
257
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('sends a PATCH /transfers/{transferId} request to payee DFSP when SEND_FINAL_NOTIFICATION_IF_REQUESTED is true', async () => {
|
|
261
|
-
config.autoAcceptParty = true;
|
|
262
|
-
config.autoAcceptQuotes = true;
|
|
263
|
-
config.sendFinalNotificationIfRequested = true;
|
|
264
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
265
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
266
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
267
|
-
});
|
|
268
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
269
|
-
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
270
|
-
// including extension list
|
|
271
|
-
const extensionList = postQuotesBody.extensionList.extension;
|
|
272
|
-
expect(extensionList).toBeTruthy();
|
|
273
|
-
expect(extensionList.length).toBe(2);
|
|
274
|
-
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
275
|
-
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
276
|
-
// simulate a callback with the quote response
|
|
277
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
278
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
279
|
-
});
|
|
280
|
-
const pb = JSON.parse(JSON.stringify(transferFulfil));
|
|
281
|
-
pb.data.body.transferState = FSPIOPTransferStateEnum.RESERVED;
|
|
282
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
283
|
-
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
284
|
-
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
285
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
286
|
-
const extensionList = postTransfersBody.extensionList.extension;
|
|
287
|
-
expect(extensionList).toBeTruthy();
|
|
288
|
-
expect(extensionList.length).toBe(2);
|
|
289
|
-
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
290
|
-
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
291
|
-
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
292
|
-
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
293
|
-
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
294
|
-
// simulate a callback with the transfer fulfilment
|
|
295
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, pb);
|
|
296
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
297
|
-
});
|
|
298
|
-
const model = new Model({
|
|
299
|
-
cache,
|
|
300
|
-
logger,
|
|
301
|
-
metricsClient,
|
|
302
|
-
...config,
|
|
303
|
-
});
|
|
304
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
305
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
306
|
-
// start the model running
|
|
307
|
-
const result = await model.run();
|
|
308
|
-
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
309
|
-
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
310
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
311
|
-
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(1);
|
|
312
|
-
expect(MojaloopRequests.__patchTransfers.mock.calls[0][0]).toEqual(model.data.transferId);
|
|
313
|
-
expect(MojaloopRequests.__patchTransfers.mock.calls[0][1].transferState).toEqual(FSPIOPTransferStateEnum.COMMITTED);
|
|
314
|
-
expect(MojaloopRequests.__patchTransfers.mock.calls[0][1].completedTimestamp).not.toBeUndefined();
|
|
315
|
-
expect(MojaloopRequests.__patchTransfers.mock.calls[0][2]).toEqual(quoteResponse.data.headers['fspiop-source']);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// check we stopped at payeeResolved state
|
|
319
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
320
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test('uses quote response transfer amount for transfer prepare', async () => {
|
|
324
|
-
config.autoAcceptParty = true;
|
|
325
|
-
config.autoAcceptQuotes = true;
|
|
326
|
-
|
|
327
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
328
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
329
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// change the the transfer amount and currency in the quote response
|
|
333
|
-
// so it is different to the initial request
|
|
334
|
-
quoteResponse.data.body.transferAmount = {
|
|
335
|
-
currency: 'XYZ',
|
|
336
|
-
amount: '9876543210'
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
expect(quoteResponse.data.body.transferAmount).not.toEqual({
|
|
340
|
-
amount: transferRequest.amount,
|
|
341
|
-
currency: transferRequest.currency
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
345
|
-
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
346
|
-
// including extension list
|
|
347
|
-
const extensionList = postQuotesBody.extensionList.extension;
|
|
348
|
-
expect(extensionList).toBeTruthy();
|
|
349
|
-
expect(extensionList.length).toBe(2);
|
|
350
|
-
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
351
|
-
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
352
|
-
|
|
353
|
-
// simulate a callback with the quote response
|
|
354
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
355
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
359
|
-
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
360
|
-
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
361
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
362
|
-
|
|
363
|
-
const extensionList = postTransfersBody.extensionList.extension;
|
|
364
|
-
expect(extensionList).toBeTruthy();
|
|
365
|
-
expect(extensionList.length).toBe(2);
|
|
366
|
-
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
367
|
-
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
368
|
-
|
|
369
|
-
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
370
|
-
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
371
|
-
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
372
|
-
|
|
373
|
-
expect(postTransfersBody.amount).toEqual(quoteResponse.data.body.transferAmount);
|
|
374
|
-
|
|
375
|
-
// simulate a callback with the transfer fulfilment
|
|
376
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
377
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const model = new Model({
|
|
381
|
-
cache,
|
|
382
|
-
logger,
|
|
383
|
-
metricsClient,
|
|
384
|
-
...config,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
388
|
-
|
|
389
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
390
|
-
|
|
391
|
-
// start the model running
|
|
392
|
-
const result = await model.run();
|
|
393
|
-
|
|
394
|
-
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
395
|
-
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
396
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
397
|
-
|
|
398
|
-
// make sure no PATCH was sent as we did not set config or receive a RESERVED state
|
|
399
|
-
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(0);
|
|
400
|
-
|
|
401
|
-
// check we stopped at payeeResolved state
|
|
402
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
403
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test('test get transfer', async () => {
|
|
407
|
-
MojaloopRequests.__getTransfers = jest.fn((transferId) => {
|
|
408
|
-
emitTransferFulfilCacheMessage(cache, transferId, transferFulfil);
|
|
409
|
-
return Promise.resolve();
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
const model = new Model({
|
|
413
|
-
cache,
|
|
414
|
-
logger,
|
|
415
|
-
metricsClient,
|
|
416
|
-
...config,
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const TRANSFER_ID = 'tx-id000011';
|
|
420
|
-
|
|
421
|
-
await model.initialize(JSON.parse(JSON.stringify({
|
|
422
|
-
...transferRequest,
|
|
423
|
-
currentState: 'getTransfer',
|
|
424
|
-
transferId: TRANSFER_ID,
|
|
425
|
-
})));
|
|
426
|
-
|
|
427
|
-
expect(StateMachine.__instance.state).toBe('getTransfer');
|
|
428
|
-
|
|
429
|
-
// start the model running
|
|
430
|
-
const result = await model.run();
|
|
431
|
-
|
|
432
|
-
expect(MojaloopRequests.__getTransfers).toHaveBeenCalledTimes(1);
|
|
433
|
-
|
|
434
|
-
// check we stopped at payeeResolved state
|
|
435
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
436
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
test('resolves payee and halts when AUTO_ACCEPT_PARTY is false', async () => {
|
|
441
|
-
config.autoAcceptParty = false;
|
|
442
|
-
|
|
443
|
-
const model = new Model({
|
|
444
|
-
cache,
|
|
445
|
-
logger,
|
|
446
|
-
metricsClient,
|
|
447
|
-
...config,
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
451
|
-
|
|
452
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
453
|
-
|
|
454
|
-
// start the model running
|
|
455
|
-
const resultPromise = model.run();
|
|
456
|
-
|
|
457
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
458
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
459
|
-
|
|
460
|
-
// wait for the model to reach a terminal state
|
|
461
|
-
const result = await resultPromise;
|
|
462
|
-
|
|
463
|
-
// check we stopped at payeeResolved state
|
|
464
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
465
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
test('uses payee party fspid as source header when supplied - resolving payee', async () => {
|
|
469
|
-
config.autoAcceptParty = false;
|
|
470
|
-
|
|
471
|
-
const model = new Model({
|
|
472
|
-
cache,
|
|
473
|
-
logger,
|
|
474
|
-
metricsClient,
|
|
475
|
-
...config,
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
479
|
-
const testFspId = 'TESTDESTFSPID';
|
|
480
|
-
req.to.fspId = testFspId;
|
|
481
|
-
|
|
482
|
-
await model.initialize(req);
|
|
483
|
-
|
|
484
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
485
|
-
|
|
486
|
-
// start the model running
|
|
487
|
-
const resultPromise = model.run();
|
|
488
|
-
|
|
489
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
490
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
491
|
-
|
|
492
|
-
// wait for the model to reach a terminal state
|
|
493
|
-
const result = await resultPromise;
|
|
494
|
-
|
|
495
|
-
// check we stopped at payeeResolved state
|
|
496
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
497
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
498
|
-
|
|
499
|
-
// check getParties mojaloop requests method was called with the correct arguments
|
|
500
|
-
expect(MojaloopRequests.__getParties).toHaveBeenCalledWith(req.to.idType, req.to.idValue, req.to.idSubValue, testFspId);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
test('resolves multiple payees and halts', async () => {
|
|
504
|
-
config.autoAcceptParty = false;
|
|
505
|
-
config.multiplePartiesResponse = true;
|
|
506
|
-
config.multiplePartiesResponseSeconds = 2;
|
|
507
|
-
const model = new Model({
|
|
508
|
-
cache,
|
|
509
|
-
logger,
|
|
510
|
-
metricsClient,
|
|
511
|
-
...config,
|
|
512
|
-
});
|
|
513
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
514
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
515
|
-
// start the model running
|
|
516
|
-
const resultPromise = model.run();
|
|
517
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
518
|
-
const payeeParty1 = JSON.parse(JSON.stringify(payeeParty));
|
|
519
|
-
payeeParty1.body.party.partyIdInfo.fspId = 'FirstFspId';
|
|
520
|
-
await emitMultiPartiesCacheMessage(cache, payeeParty1);
|
|
521
|
-
const payeeParty2 = JSON.parse(JSON.stringify(payeeParty));
|
|
522
|
-
payeeParty2.body.party.partyIdInfo.fspId = 'SecondFspId';
|
|
523
|
-
await emitMultiPartiesCacheMessage(cache, payeeParty2);
|
|
524
|
-
// wait for the model to reach a terminal state
|
|
525
|
-
const result = await resultPromise;
|
|
526
|
-
// check we stopped at payeeResolved state
|
|
527
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
528
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
529
|
-
expect(result.to[0].fspId).toEqual('FirstFspId');
|
|
530
|
-
expect(result.to[1].fspId).toEqual('SecondFspId');
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
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 () => {
|
|
534
|
-
config.autoAcceptParty = false;
|
|
535
|
-
config.autoAcceptQuotes = false;
|
|
536
|
-
|
|
537
|
-
let model = new Model({
|
|
538
|
-
cache,
|
|
539
|
-
logger,
|
|
540
|
-
metricsClient,
|
|
541
|
-
...config,
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
545
|
-
|
|
546
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
547
|
-
|
|
548
|
-
// start the model running
|
|
549
|
-
let resultPromise = model.run();
|
|
550
|
-
|
|
551
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
552
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
553
|
-
|
|
554
|
-
// wait for the model to reach a terminal state
|
|
555
|
-
let result = await resultPromise;
|
|
556
|
-
|
|
557
|
-
// check we stopped at payeeResolved state
|
|
558
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
559
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
560
|
-
|
|
561
|
-
const transferId = result.transferId;
|
|
562
|
-
|
|
563
|
-
// load a new model from the saved state
|
|
564
|
-
model = new Model({
|
|
565
|
-
cache,
|
|
566
|
-
logger,
|
|
567
|
-
metricsClient,
|
|
568
|
-
...config,
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
await model.load(transferId);
|
|
572
|
-
|
|
573
|
-
// check the model loaded to the correct state
|
|
574
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
575
|
-
|
|
576
|
-
// now run the model again. this should trigger transition to quote request
|
|
577
|
-
resultPromise = model.run({ acceptParty: true });
|
|
578
|
-
// now we started the model running we simulate a callback with the quote response
|
|
579
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
580
|
-
|
|
581
|
-
// wait for the model to reach a terminal state
|
|
582
|
-
result = await resultPromise;
|
|
583
|
-
|
|
584
|
-
// check we stopped at payeeResolved state
|
|
585
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
586
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
test('Allows change of transferAmount at accept party phase', async () => {
|
|
590
|
-
config.autoAcceptParty = false;
|
|
591
|
-
config.autoAcceptQuotes = false;
|
|
592
|
-
|
|
593
|
-
let model = new Model({
|
|
594
|
-
cache,
|
|
595
|
-
logger,
|
|
596
|
-
metricsClient,
|
|
597
|
-
...config,
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
601
|
-
|
|
602
|
-
// record the initial requested transfer amount
|
|
603
|
-
const initialAmount = req.amount;
|
|
604
|
-
|
|
605
|
-
await model.initialize(req);
|
|
606
|
-
|
|
607
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
608
|
-
|
|
609
|
-
// start the model running
|
|
610
|
-
let resultPromise = model.run();
|
|
611
|
-
|
|
612
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
613
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
614
|
-
|
|
615
|
-
// wait for the model to reach a terminal state
|
|
616
|
-
let result = await resultPromise;
|
|
617
|
-
|
|
618
|
-
// check we stopped at payeeResolved state
|
|
619
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
620
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
621
|
-
|
|
622
|
-
expect(result.amount).toEqual(initialAmount);
|
|
623
|
-
|
|
624
|
-
const transferId = result.transferId;
|
|
625
|
-
|
|
626
|
-
// load a new model from the saved state
|
|
627
|
-
model = new Model({
|
|
628
|
-
cache,
|
|
629
|
-
logger,
|
|
630
|
-
metricsClient,
|
|
631
|
-
...config,
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
await model.load(transferId);
|
|
635
|
-
|
|
636
|
-
// check the model loaded to the correct state
|
|
637
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
638
|
-
|
|
639
|
-
const resume = {
|
|
640
|
-
amount: 999,
|
|
641
|
-
acceptParty: true,
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
// now run the model again. this should trigger transition to quote request
|
|
645
|
-
resultPromise = model.run(resume);
|
|
646
|
-
|
|
647
|
-
// now we started the model running we simulate a callback with the quote response
|
|
648
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
649
|
-
|
|
650
|
-
// wait for the model to reach a terminal state
|
|
651
|
-
result = await resultPromise;
|
|
652
|
-
|
|
653
|
-
// check we stopped at quoteReceived state
|
|
654
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
655
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
656
|
-
|
|
657
|
-
// check the accept party key got merged to the state
|
|
658
|
-
expect(result.acceptParty).toEqual(true);
|
|
659
|
-
|
|
660
|
-
// check the amount key got changed
|
|
661
|
-
expect(result.amount).toEqual(resume.amount);
|
|
662
|
-
|
|
663
|
-
// check the quote request amount is the NEW amount, not the initial amount
|
|
664
|
-
expect(result.quoteRequest.body.amount.amount).toStrictEqual(resume.amount);
|
|
665
|
-
expect(result.quoteRequest.body.amount.amount).not.toEqual(initialAmount);
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
test('Allows change of payee party at accept party phase (round-robin support)', async () => {
|
|
669
|
-
config.autoAcceptParty = false;
|
|
670
|
-
config.autoAcceptQuotes = false;
|
|
671
|
-
|
|
672
|
-
let model = new Model({
|
|
673
|
-
cache,
|
|
674
|
-
logger,
|
|
675
|
-
metricsClient,
|
|
676
|
-
...config,
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
680
|
-
|
|
681
|
-
// record the initial requested transfer amount
|
|
682
|
-
const initialAmount = req.amount;
|
|
683
|
-
|
|
684
|
-
await model.initialize(req);
|
|
685
|
-
|
|
686
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
687
|
-
|
|
688
|
-
// start the model running
|
|
689
|
-
let resultPromise = model.run();
|
|
690
|
-
|
|
691
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
692
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
693
|
-
|
|
694
|
-
// wait for the model to reach a terminal state
|
|
695
|
-
let result = await resultPromise;
|
|
696
|
-
|
|
697
|
-
// check we stopped at payeeResolved state
|
|
698
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
699
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
700
|
-
|
|
701
|
-
expect(result.amount).toEqual(initialAmount);
|
|
702
|
-
|
|
703
|
-
const transferId = result.transferId;
|
|
704
|
-
|
|
705
|
-
// load a new model from the saved state
|
|
706
|
-
model = new Model({
|
|
707
|
-
cache,
|
|
708
|
-
logger,
|
|
709
|
-
metricsClient,
|
|
710
|
-
...config,
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
await model.load(transferId);
|
|
714
|
-
|
|
715
|
-
// check the model loaded to the correct state
|
|
716
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
717
|
-
|
|
718
|
-
const newPayee = {
|
|
719
|
-
partyIdInfo: {
|
|
720
|
-
partySubIdOrType: undefined,
|
|
721
|
-
partyIdType: 'PASSPORT',
|
|
722
|
-
partyIdentifier: 'AAABBBCCCDDDEEE',
|
|
723
|
-
fspId: 'TESTDFSP'
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
const newPayeeInternal = {
|
|
728
|
-
idType: newPayee.partyIdInfo.partyIdType,
|
|
729
|
-
idValue: newPayee.partyIdInfo.partyIdentifier,
|
|
730
|
-
fspId: newPayee.partyIdInfo.fspId,
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
const resume = {
|
|
734
|
-
acceptParty: true,
|
|
735
|
-
to: newPayeeInternal,
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
// now run the model again. this should trigger transition to quote request
|
|
739
|
-
resultPromise = model.run(resume);
|
|
740
|
-
|
|
741
|
-
// now we started the model running we simulate a callback with the quote response
|
|
742
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
743
|
-
|
|
744
|
-
// wait for the model to reach a terminal state
|
|
745
|
-
result = await resultPromise;
|
|
746
|
-
|
|
747
|
-
// check we stopped at quoteReceived state
|
|
748
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
749
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
750
|
-
|
|
751
|
-
// check the accept party key got merged to the state
|
|
752
|
-
expect(result.acceptParty).toEqual(true);
|
|
753
|
-
|
|
754
|
-
// check the "to" passed in to model resume is merged into the model state correctly
|
|
755
|
-
expect(result.to).toStrictEqual(newPayeeInternal);
|
|
756
|
-
|
|
757
|
-
// check the quote request payee party is the NEW one, not the initial one.
|
|
758
|
-
expect(result.quoteRequest.body.payee).toStrictEqual(newPayee);
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
test('Does not merge resume data keys into state that are not permitted', async () => {
|
|
762
|
-
config.autoAcceptParty = false;
|
|
763
|
-
config.autoAcceptQuotes = false;
|
|
764
|
-
|
|
765
|
-
let model = new Model({
|
|
766
|
-
cache,
|
|
767
|
-
logger,
|
|
768
|
-
metricsClient,
|
|
769
|
-
...config,
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
773
|
-
|
|
774
|
-
// record the initial requested transfer amount
|
|
775
|
-
const initialAmount = req.amount;
|
|
776
|
-
|
|
777
|
-
await model.initialize(req);
|
|
778
|
-
|
|
779
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
780
|
-
|
|
781
|
-
// start the model running
|
|
782
|
-
let resultPromise = model.run();
|
|
783
|
-
|
|
784
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
785
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
786
|
-
|
|
787
|
-
// wait for the model to reach a terminal state
|
|
788
|
-
let result = await resultPromise;
|
|
789
|
-
|
|
790
|
-
// check we stopped at payeeResolved state
|
|
791
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
792
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
793
|
-
|
|
794
|
-
expect(result.amount).toEqual(initialAmount);
|
|
795
|
-
|
|
796
|
-
const transferId = result.transferId;
|
|
797
|
-
|
|
798
|
-
// load a new model from the saved state
|
|
799
|
-
model = new Model({
|
|
800
|
-
cache,
|
|
801
|
-
logger,
|
|
802
|
-
metricsClient,
|
|
803
|
-
...config,
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
await model.load(transferId);
|
|
807
|
-
|
|
808
|
-
// check the model loaded to the correct state
|
|
809
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
810
|
-
|
|
811
|
-
const resume = {
|
|
812
|
-
amount: 999,
|
|
813
|
-
acceptParty: true,
|
|
814
|
-
someRandomKey: 'this key name is not permitted',
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
// now run the model again. this should trigger transition to quote request
|
|
818
|
-
resultPromise = model.run(resume);
|
|
819
|
-
|
|
820
|
-
// now we started the model running we simulate a callback with the quote response
|
|
821
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
822
|
-
|
|
823
|
-
// wait for the model to reach a terminal state
|
|
824
|
-
result = await resultPromise;
|
|
825
|
-
|
|
826
|
-
// check we stopped at quoteReceived state
|
|
827
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
828
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
829
|
-
|
|
830
|
-
// check the accept party key got merged to the state
|
|
831
|
-
expect(result.acceptParty).toEqual(true);
|
|
832
|
-
|
|
833
|
-
// check the amount key got changed
|
|
834
|
-
expect(result.amount).toEqual(resume.amount);
|
|
835
|
-
|
|
836
|
-
// check the quote request amount is the NEW amount, not the initial amount
|
|
837
|
-
expect(result.quoteRequest.body.amount.amount).toStrictEqual(resume.amount);
|
|
838
|
-
expect(result.quoteRequest.body.amount.amount).not.toEqual(initialAmount);
|
|
839
|
-
|
|
840
|
-
// check that our disallowed key is not merged to the transfer state
|
|
841
|
-
expect(result.someRandomKey).toBeUndefined();
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
test('skips resolving party when to.fspid is specified and skipPartyLookup is truthy', async () => {
|
|
845
|
-
config.autoAcceptParty = false;
|
|
846
|
-
config.autoAcceptQuotes = false;
|
|
847
|
-
|
|
848
|
-
let model = new Model({
|
|
849
|
-
cache,
|
|
850
|
-
logger,
|
|
851
|
-
metricsClient,
|
|
852
|
-
...config,
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
856
|
-
const testFspId = 'TESTDESTFSPID';
|
|
857
|
-
req.to.fspId = testFspId;
|
|
858
|
-
req.skipPartyLookup = true;
|
|
859
|
-
|
|
860
|
-
await model.initialize(req);
|
|
861
|
-
|
|
862
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
863
|
-
|
|
864
|
-
// start the model running
|
|
865
|
-
let resultPromise = model.run();
|
|
866
|
-
|
|
867
|
-
// now we started the model running we simulate a callback with the quote response
|
|
868
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
869
|
-
|
|
870
|
-
// wait for the model to reach a terminal state
|
|
871
|
-
let result = await resultPromise;
|
|
872
|
-
|
|
873
|
-
// check we stopped at quoteReceived state
|
|
874
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
875
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
test('aborts after party rejected by backend', async () => {
|
|
879
|
-
config.autoAcceptParty = false;
|
|
880
|
-
config.autoAcceptQuotes = false;
|
|
881
|
-
|
|
882
|
-
let model = new Model({
|
|
883
|
-
cache,
|
|
884
|
-
logger,
|
|
885
|
-
metricsClient,
|
|
886
|
-
...config,
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
890
|
-
|
|
891
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
892
|
-
|
|
893
|
-
// start the model running
|
|
894
|
-
let resultPromise = model.run();
|
|
895
|
-
|
|
896
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
897
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
898
|
-
|
|
899
|
-
// wait for the model to reach a terminal state
|
|
900
|
-
let result = await resultPromise;
|
|
901
|
-
|
|
902
|
-
// check we stopped at payeeResolved state
|
|
903
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
904
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
905
|
-
|
|
906
|
-
const transferId = result.transferId;
|
|
907
|
-
|
|
908
|
-
// load a new model from the saved state
|
|
909
|
-
model = new Model({
|
|
910
|
-
cache,
|
|
911
|
-
logger,
|
|
912
|
-
metricsClient,
|
|
913
|
-
...config,
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
await model.load(transferId);
|
|
917
|
-
|
|
918
|
-
// check the model loaded to the correct state
|
|
919
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
920
|
-
|
|
921
|
-
// now run the model again with a party rejection. this should trigger transition to quote request
|
|
922
|
-
result = await model.run({ resume: { acceptParty: false } });
|
|
923
|
-
|
|
924
|
-
// check we stopped at quoteReceived state
|
|
925
|
-
expect(result.currentState).toBe(SDKStateEnum.ABORTED);
|
|
926
|
-
expect(result.abortedReason).toBe('Payee rejected by backend');
|
|
927
|
-
expect(StateMachine.__instance.state).toBe('aborted');
|
|
928
|
-
});
|
|
929
|
-
|
|
930
|
-
test('aborts after quote rejected by backend', async () => {
|
|
931
|
-
config.autoAcceptParty = false;
|
|
932
|
-
config.autoAcceptQuotes = false;
|
|
933
|
-
|
|
934
|
-
let model = new Model({
|
|
935
|
-
cache,
|
|
936
|
-
logger,
|
|
937
|
-
metricsClient,
|
|
938
|
-
...config,
|
|
939
|
-
});
|
|
940
|
-
|
|
941
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
942
|
-
|
|
943
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
944
|
-
|
|
945
|
-
// start the model running
|
|
946
|
-
let resultPromise = model.run();
|
|
947
|
-
|
|
948
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
949
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
950
|
-
|
|
951
|
-
// wait for the model to reach a terminal state
|
|
952
|
-
let result = await resultPromise;
|
|
953
|
-
|
|
954
|
-
// check we stopped at payeeResolved state
|
|
955
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
956
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
957
|
-
|
|
958
|
-
const transferId = result.transferId;
|
|
959
|
-
|
|
960
|
-
// load a new model from the saved state
|
|
961
|
-
model = new Model({
|
|
962
|
-
cache,
|
|
963
|
-
logger,
|
|
964
|
-
metricsClient,
|
|
965
|
-
...config,
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
await model.load(transferId);
|
|
969
|
-
|
|
970
|
-
// check the model loaded to the correct state
|
|
971
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
972
|
-
|
|
973
|
-
// now run the model again. this should trigger transition to quote request
|
|
974
|
-
resultPromise = model.run({ acceptParty: true });
|
|
975
|
-
|
|
976
|
-
// now we started the model running we simulate a callback with the quote response
|
|
977
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
978
|
-
|
|
979
|
-
// wait for the model to reach quote received
|
|
980
|
-
result = await resultPromise;
|
|
981
|
-
|
|
982
|
-
// check we stopped at payeeResolved state
|
|
983
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
984
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
985
|
-
|
|
986
|
-
// now run the model again. this should trigger abort as the quote was not accepted
|
|
987
|
-
result = await model.run({ acceptQuote: false });
|
|
988
|
-
|
|
989
|
-
expect(result.currentState).toBe(SDKStateEnum.ABORTED);
|
|
990
|
-
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
991
|
-
expect(StateMachine.__instance.state).toBe('aborted');
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
test('should handle unknown state with a meaningful error message', async () => {
|
|
995
|
-
config.autoAcceptParty = false;
|
|
996
|
-
config.autoAcceptQuotes = false;
|
|
997
|
-
|
|
998
|
-
let model = new Model({
|
|
999
|
-
cache,
|
|
1000
|
-
logger,
|
|
1001
|
-
metricsClient,
|
|
1002
|
-
...config,
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
await model.initialize(JSON.parse(JSON.stringify({
|
|
1006
|
-
...transferRequest,
|
|
1007
|
-
currentState: 'abc'
|
|
1008
|
-
})));
|
|
1009
|
-
|
|
1010
|
-
expect(StateMachine.__instance.state).toBe('abc');
|
|
1011
|
-
|
|
1012
|
-
// start the model running
|
|
1013
|
-
let resultPromise = model.run();
|
|
1014
|
-
|
|
1015
|
-
// wait for the model to reach a terminal state
|
|
1016
|
-
let result = await resultPromise;
|
|
1017
|
-
expect(result).toBe(undefined);
|
|
1018
|
-
});
|
|
1019
|
-
|
|
1020
|
-
test('should handle subsequent put transfer calls incase of aborted transfer', async () => {
|
|
1021
|
-
config.autoAcceptParty = false;
|
|
1022
|
-
config.autoAcceptQuotes = false;
|
|
1023
|
-
|
|
1024
|
-
let model = new Model({
|
|
1025
|
-
cache,
|
|
1026
|
-
logger,
|
|
1027
|
-
metricsClient,
|
|
1028
|
-
...config,
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1032
|
-
|
|
1033
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1034
|
-
|
|
1035
|
-
// start the model running
|
|
1036
|
-
let resultPromise = model.run();
|
|
1037
|
-
|
|
1038
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
1039
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1040
|
-
|
|
1041
|
-
// wait for the model to reach a terminal state
|
|
1042
|
-
let result = await resultPromise;
|
|
1043
|
-
|
|
1044
|
-
// check we stopped at payeeResolved state
|
|
1045
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
1046
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
1047
|
-
|
|
1048
|
-
const transferId = result.transferId;
|
|
1049
|
-
|
|
1050
|
-
// load a new model from the saved state
|
|
1051
|
-
model = new Model({
|
|
1052
|
-
cache,
|
|
1053
|
-
logger,
|
|
1054
|
-
metricsClient,
|
|
1055
|
-
...config,
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
await model.load(transferId);
|
|
1059
|
-
|
|
1060
|
-
// check the model loaded to the correct state
|
|
1061
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
1062
|
-
|
|
1063
|
-
// now run the model again. this should trigger transition to quote request
|
|
1064
|
-
resultPromise = model.run({ acceptParty: true });
|
|
1065
|
-
|
|
1066
|
-
// now we started the model running we simulate a callback with the quote response
|
|
1067
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
1068
|
-
|
|
1069
|
-
// wait for the model to reach quote received
|
|
1070
|
-
result = await resultPromise;
|
|
1071
|
-
|
|
1072
|
-
// check we stopped at payeeResolved state
|
|
1073
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
1074
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
1075
|
-
|
|
1076
|
-
// now run the model again. this should trigger abort as the quote was not accepted
|
|
1077
|
-
result = await model.run({ acceptQuote: false });
|
|
1078
|
-
|
|
1079
|
-
expect(result.currentState).toBe(SDKStateEnum.ABORTED);
|
|
1080
|
-
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
1081
|
-
expect(StateMachine.__instance.state).toBe('aborted');
|
|
1082
|
-
|
|
1083
|
-
// now run the model again. this should get the same result as previous one
|
|
1084
|
-
result = await model.run({ acceptQuote: false });
|
|
1085
|
-
|
|
1086
|
-
expect(result.currentState).toBe(SDKStateEnum.ABORTED);
|
|
1087
|
-
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
1088
|
-
expect(StateMachine.__instance.state).toBe('aborted');
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
test('halts and resumes after parties and quotes stages when AUTO_ACCEPT_PARTY is false and AUTO_ACCEPT_QUOTES is false', async () => {
|
|
1092
|
-
config.autoAcceptParty = false;
|
|
1093
|
-
config.autoAcceptQuotes = false;
|
|
1094
|
-
|
|
1095
|
-
let model = new Model({
|
|
1096
|
-
cache,
|
|
1097
|
-
logger,
|
|
1098
|
-
metricsClient,
|
|
1099
|
-
...config,
|
|
1100
|
-
});
|
|
1101
|
-
|
|
1102
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1103
|
-
|
|
1104
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1105
|
-
|
|
1106
|
-
// start the model running
|
|
1107
|
-
let resultPromise = model.run();
|
|
1108
|
-
|
|
1109
|
-
// now we started the model running we simulate a callback with the resolved party
|
|
1110
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1111
|
-
|
|
1112
|
-
// wait for the model to reach a terminal state
|
|
1113
|
-
let result = await resultPromise;
|
|
1114
|
-
|
|
1115
|
-
// check we stopped at payeeResolved state
|
|
1116
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_PARTY_ACCEPTANCE);
|
|
1117
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
1118
|
-
|
|
1119
|
-
const transferId = result.transferId;
|
|
1120
|
-
|
|
1121
|
-
// load a new model from the saved state
|
|
1122
|
-
model = new Model({
|
|
1123
|
-
cache,
|
|
1124
|
-
logger,
|
|
1125
|
-
metricsClient,
|
|
1126
|
-
...config,
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
await model.load(transferId);
|
|
1130
|
-
|
|
1131
|
-
// check the model loaded to the correct state
|
|
1132
|
-
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
1133
|
-
|
|
1134
|
-
// now run the model again. this should trigger transition to quote request
|
|
1135
|
-
resultPromise = model.run({ acceptParty: true });
|
|
1136
|
-
|
|
1137
|
-
// now we started the model running we simulate a callback with the quote response
|
|
1138
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
1139
|
-
|
|
1140
|
-
// wait for the model to reach a terminal state
|
|
1141
|
-
result = await resultPromise;
|
|
1142
|
-
|
|
1143
|
-
// check we stopped at quoteReceived state
|
|
1144
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
1145
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
1146
|
-
|
|
1147
|
-
// load a new model from the saved state
|
|
1148
|
-
model = new Model({
|
|
1149
|
-
cache,
|
|
1150
|
-
logger,
|
|
1151
|
-
metricsClient,
|
|
1152
|
-
...config,
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
await model.load(transferId);
|
|
1156
|
-
|
|
1157
|
-
// check the model loaded to the correct state
|
|
1158
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
1159
|
-
|
|
1160
|
-
// now run the model again. this should trigger transition to quote request
|
|
1161
|
-
resultPromise = model.run({ acceptQuote: true });
|
|
1162
|
-
|
|
1163
|
-
// now we started the model running we simulate a callback with the transfer fulfilment
|
|
1164
|
-
cache.publish(`tf_${model.data.transferId}`, JSON.stringify(transferFulfil));
|
|
1165
|
-
|
|
1166
|
-
// wait for the model to reach a terminal state
|
|
1167
|
-
result = await resultPromise;
|
|
1168
|
-
|
|
1169
|
-
// check we stopped at quoteReceived state
|
|
1170
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
1171
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
test('uses payee party fspid for transfer prepare when config USE_QUOTE_SOURCE_FSP_AS_TRANSFER_PAYEE_FSP is false', async () => {
|
|
1175
|
-
config.autoAcceptParty = true;
|
|
1176
|
-
config.autoAcceptQuotes = true;
|
|
1177
|
-
config.useQuoteSourceFSPAsTransferPayeeFSP = false;
|
|
1178
|
-
|
|
1179
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
1180
|
-
// simulate a callback with the resolved party
|
|
1181
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1182
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
1186
|
-
// simulate a callback with the quote response
|
|
1187
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
1188
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
1192
|
-
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
1193
|
-
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
1194
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
1195
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
1196
|
-
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
1197
|
-
expect(payeeFsp).toEqual(payeeParty.body.party.partyIdInfo.fspId);
|
|
1198
|
-
|
|
1199
|
-
// simulate a callback with the transfer fulfilment
|
|
1200
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
1201
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
const model = new Model({
|
|
1205
|
-
cache,
|
|
1206
|
-
logger,
|
|
1207
|
-
metricsClient,
|
|
1208
|
-
...config,
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1212
|
-
|
|
1213
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1214
|
-
|
|
1215
|
-
// start the model running
|
|
1216
|
-
const resultPromise = model.run();
|
|
1217
|
-
|
|
1218
|
-
// wait for the model to reach a terminal state
|
|
1219
|
-
const result = await resultPromise;
|
|
1220
|
-
|
|
1221
|
-
// check we stopped at payeeResolved state
|
|
1222
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
1223
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
1224
|
-
});
|
|
1225
|
-
|
|
1226
|
-
test('uses quote response source fspid for transfer prepare when config USE_QUOTE_SOURCE_FSP_AS_TRANSFER_PAYEE_FSP is true', async () => {
|
|
1227
|
-
config.autoAcceptParty = true;
|
|
1228
|
-
config.autoAcceptQuotes = true;
|
|
1229
|
-
config.useQuoteSourceFSPAsTransferPayeeFSP = true;
|
|
1230
|
-
|
|
1231
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
1232
|
-
// simulate a callback with the resolved party
|
|
1233
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1234
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
1238
|
-
// simulate a callback with the quote response
|
|
1239
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
1240
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
1244
|
-
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
1245
|
-
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
1246
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
1247
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
1248
|
-
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
1249
|
-
expect(payeeFsp).toEqual(quoteResponse.data.headers['fspiop-source']);
|
|
1250
|
-
|
|
1251
|
-
// simulate a callback with the transfer fulfilment
|
|
1252
|
-
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
1253
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
|
-
const model = new Model({
|
|
1257
|
-
cache,
|
|
1258
|
-
logger,
|
|
1259
|
-
metricsClient,
|
|
1260
|
-
...config,
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1264
|
-
|
|
1265
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1266
|
-
|
|
1267
|
-
// start the model running
|
|
1268
|
-
const resultPromise = model.run();
|
|
1269
|
-
|
|
1270
|
-
// wait for the model to reach a terminal state
|
|
1271
|
-
const result = await resultPromise;
|
|
1272
|
-
|
|
1273
|
-
// check we stopped at payeeResolved state
|
|
1274
|
-
expect(result.currentState).toBe(SDKStateEnum.COMPLETED);
|
|
1275
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
1276
|
-
});
|
|
1277
|
-
|
|
1278
|
-
test('pass quote response `expiration` deadline', () =>
|
|
1279
|
-
testTransferWithDelay({
|
|
1280
|
-
expirySeconds: 2,
|
|
1281
|
-
delays: {
|
|
1282
|
-
requestQuotes: 1,
|
|
1283
|
-
},
|
|
1284
|
-
rejects: {
|
|
1285
|
-
quoteResponse: true,
|
|
1286
|
-
}
|
|
1287
|
-
})
|
|
1288
|
-
);
|
|
1289
|
-
|
|
1290
|
-
test('pass transfer fulfills `expiration` deadline', () =>
|
|
1291
|
-
testTransferWithDelay({
|
|
1292
|
-
expirySeconds: 2,
|
|
1293
|
-
delays: {
|
|
1294
|
-
prepareTransfer: 1,
|
|
1295
|
-
},
|
|
1296
|
-
rejects: {
|
|
1297
|
-
transferFulfils: true,
|
|
1298
|
-
}
|
|
1299
|
-
})
|
|
1300
|
-
);
|
|
1301
|
-
|
|
1302
|
-
test('pass all stages `expiration` deadlines', () =>
|
|
1303
|
-
testTransferWithDelay({
|
|
1304
|
-
expirySeconds: 2,
|
|
1305
|
-
delays: {
|
|
1306
|
-
requestQuotes: 1,
|
|
1307
|
-
prepareTransfer: 1,
|
|
1308
|
-
},
|
|
1309
|
-
rejects: {
|
|
1310
|
-
quoteResponse: true,
|
|
1311
|
-
transferFulfils: true,
|
|
1312
|
-
}
|
|
1313
|
-
})
|
|
1314
|
-
);
|
|
1315
|
-
|
|
1316
|
-
test('fail on quote response `expiration` deadline', () =>
|
|
1317
|
-
testTransferWithDelay({
|
|
1318
|
-
expirySeconds: 1,
|
|
1319
|
-
delays: {
|
|
1320
|
-
requestQuotes: 2,
|
|
1321
|
-
},
|
|
1322
|
-
rejects: {
|
|
1323
|
-
quoteResponse: true,
|
|
1324
|
-
}
|
|
1325
|
-
})
|
|
1326
|
-
);
|
|
1327
|
-
|
|
1328
|
-
test('fail on transfer fulfills `expiration` deadline', () =>
|
|
1329
|
-
testTransferWithDelay({
|
|
1330
|
-
expirySeconds: 1,
|
|
1331
|
-
delays: {
|
|
1332
|
-
prepareTransfer: 2,
|
|
1333
|
-
},
|
|
1334
|
-
rejects: {
|
|
1335
|
-
transferFulfils: true,
|
|
1336
|
-
}
|
|
1337
|
-
})
|
|
1338
|
-
);
|
|
1339
|
-
|
|
1340
|
-
test('Throws with mojaloop error in response body when party resolution error callback occurs', async () => {
|
|
1341
|
-
config.autoAcceptParty = true;
|
|
1342
|
-
config.autoAcceptQuotes = true;
|
|
1343
|
-
|
|
1344
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
1345
|
-
// simulate a callback with the resolved party
|
|
1346
|
-
cache.publish(genPartyId(payeeParty), JSON.stringify(expectError));
|
|
1347
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1348
|
-
});
|
|
1349
|
-
|
|
1350
|
-
const model = new Model({
|
|
1351
|
-
cache,
|
|
1352
|
-
logger,
|
|
1353
|
-
metricsClient,
|
|
1354
|
-
...config,
|
|
1355
|
-
});
|
|
1356
|
-
|
|
1357
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1358
|
-
|
|
1359
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1360
|
-
|
|
1361
|
-
const expectError = {
|
|
1362
|
-
body: {
|
|
1363
|
-
errorInformation: {
|
|
1364
|
-
errorCode: '3204',
|
|
1365
|
-
errorDescription: 'Party not found'
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
};
|
|
1369
|
-
|
|
1370
|
-
const errMsg = 'Got an error response resolving party: { errorInformation: { errorCode: \'3204\', errorDescription: \'Party not found\' } }';
|
|
1371
|
-
|
|
1372
|
-
try {
|
|
1373
|
-
await model.run();
|
|
1374
|
-
}
|
|
1375
|
-
catch(err) {
|
|
1376
|
-
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
1377
|
-
expect(err.transferState).toBeTruthy();
|
|
1378
|
-
expect(err.transferState.lastError).toBeTruthy();
|
|
1379
|
-
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.body);
|
|
1380
|
-
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
throw new Error('Outbound model should have thrown');
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
test('Throws with mojaloop error in response body when quote request error callback occurs', async () => {
|
|
1389
|
-
config.autoAcceptParty = true;
|
|
1390
|
-
config.autoAcceptQuotes = true;
|
|
1391
|
-
|
|
1392
|
-
const expectError = {
|
|
1393
|
-
type: 'quoteResponseError',
|
|
1394
|
-
data: {
|
|
1395
|
-
body: {
|
|
1396
|
-
errorInformation: {
|
|
1397
|
-
errorCode: '3205',
|
|
1398
|
-
errorDescription: 'Quote ID not found'
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
headers: {}
|
|
1402
|
-
}
|
|
1403
|
-
};
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
1407
|
-
// simulate a callback with the resolved party
|
|
1408
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1409
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1410
|
-
});
|
|
1411
|
-
|
|
1412
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
1413
|
-
// simulate a callback with the quote response
|
|
1414
|
-
cache.publish(`qt_${postQuotesBody.quoteId}`, JSON.stringify(expectError));
|
|
1415
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
const model = new Model({
|
|
1419
|
-
cache,
|
|
1420
|
-
logger,
|
|
1421
|
-
metricsClient,
|
|
1422
|
-
...config,
|
|
1423
|
-
});
|
|
1424
|
-
|
|
1425
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1426
|
-
|
|
1427
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1428
|
-
|
|
1429
|
-
const errMsg = 'Got an error response requesting quote: { errorInformation:\n { errorCode: \'3205\', errorDescription: \'Quote ID not found\' } }';
|
|
1430
|
-
|
|
1431
|
-
try {
|
|
1432
|
-
await model.run();
|
|
1433
|
-
}
|
|
1434
|
-
catch(err) {
|
|
1435
|
-
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
1436
|
-
expect(err.transferState).toBeTruthy();
|
|
1437
|
-
expect(err.transferState.lastError).toBeTruthy();
|
|
1438
|
-
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data.body);
|
|
1439
|
-
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
throw new Error('Outbound model should have thrown');
|
|
1444
|
-
});
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
test('Throws with mojaloop error in response body when transfer request error callback occurs', async () => {
|
|
1448
|
-
config.autoAcceptParty = true;
|
|
1449
|
-
config.autoAcceptQuotes = true;
|
|
1450
|
-
|
|
1451
|
-
const expectError = {
|
|
1452
|
-
type: 'transferError',
|
|
1453
|
-
data: {
|
|
1454
|
-
body: {
|
|
1455
|
-
errorInformation: {
|
|
1456
|
-
errorCode: '4001',
|
|
1457
|
-
errorDescription: 'Payer FSP insufficient liquidity'
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
}
|
|
1461
|
-
};
|
|
1462
|
-
|
|
1463
|
-
MojaloopRequests.__getParties = jest.fn(() => {
|
|
1464
|
-
// simulate a callback with the resolved party
|
|
1465
|
-
emitPartyCacheMessage(cache, payeeParty);
|
|
1466
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1467
|
-
});
|
|
1468
|
-
|
|
1469
|
-
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
1470
|
-
// simulate a callback with the quote response
|
|
1471
|
-
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
1472
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1473
|
-
});
|
|
1474
|
-
|
|
1475
|
-
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
1476
|
-
// simulate an error callback with the transfer fulfilment
|
|
1477
|
-
cache.publish(`tf_${postTransfersBody.transferId}`, JSON.stringify(expectError));
|
|
1478
|
-
return Promise.resolve(dummyRequestsModuleResponse);
|
|
1479
|
-
});
|
|
1480
|
-
|
|
1481
|
-
const model = new Model({
|
|
1482
|
-
cache,
|
|
1483
|
-
logger,
|
|
1484
|
-
metricsClient,
|
|
1485
|
-
...config,
|
|
1486
|
-
});
|
|
1487
|
-
|
|
1488
|
-
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
1489
|
-
|
|
1490
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1491
|
-
|
|
1492
|
-
const errMsg = 'Got an error response preparing transfer: { errorInformation:\n { errorCode: \'4001\',\n errorDescription: \'Payer FSP insufficient liquidity\' } }';
|
|
1493
|
-
|
|
1494
|
-
try {
|
|
1495
|
-
await model.run();
|
|
1496
|
-
}
|
|
1497
|
-
catch(err) {
|
|
1498
|
-
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
1499
|
-
expect(err.transferState).toBeTruthy();
|
|
1500
|
-
expect(err.transferState.lastError).toBeTruthy();
|
|
1501
|
-
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data.body);
|
|
1502
|
-
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
throw new Error('Outbound model should have thrown');
|
|
1507
|
-
});
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
async function testTlsServer(enableTls) {
|
|
1511
|
-
config.outbound.tls.mutualTLS.enabled = enableTls;
|
|
1512
|
-
|
|
1513
|
-
new Model({
|
|
1514
|
-
cache,
|
|
1515
|
-
logger,
|
|
1516
|
-
metricsClient,
|
|
1517
|
-
...config
|
|
1518
|
-
});
|
|
1519
|
-
|
|
1520
|
-
const scheme = enableTls ? 'https' : 'http';
|
|
1521
|
-
expect(MojaloopRequests.__instance.transportScheme).toBe(scheme);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
test('Outbound server should use HTTPS if outbound mTLS enabled', () =>
|
|
1525
|
-
testTlsServer(true));
|
|
1526
|
-
|
|
1527
|
-
test('Outbound server should use HTTP if outbound mTLS disabled', () =>
|
|
1528
|
-
testTlsServer(false));
|
|
1529
|
-
|
|
1530
|
-
test('Outbound transfers model should record metrics', async () => {
|
|
1531
|
-
const metrics = await metricsClient._prometheusRegister.metrics();
|
|
1532
|
-
expect(metrics).toBeTruthy();
|
|
1533
|
-
|
|
1534
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_party_lookup_request_count'));
|
|
1535
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_party_lookup_response_count'));
|
|
1536
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_quote_request_count'));
|
|
1537
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_quote_response_count'));
|
|
1538
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_transfer_prepare_count'));
|
|
1539
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_transfer_fulfil_response_count'));
|
|
1540
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_quote_request_latency'));
|
|
1541
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_transfer_latency'));
|
|
1542
|
-
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_party_lookup_latency'));
|
|
1543
|
-
});
|
|
1544
|
-
|
|
1545
|
-
test('skips resolving party when to.fspid is specified and skipPartyLookup is truthy', async () => {
|
|
1546
|
-
config.autoAcceptParty = false;
|
|
1547
|
-
config.autoAcceptQuotes = false;
|
|
1548
|
-
|
|
1549
|
-
let model = new Model({
|
|
1550
|
-
cache,
|
|
1551
|
-
logger,
|
|
1552
|
-
metricsClient,
|
|
1553
|
-
...config,
|
|
1554
|
-
});
|
|
1555
|
-
|
|
1556
|
-
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
1557
|
-
const testFspId = 'TESTDESTFSPID';
|
|
1558
|
-
req.to.fspId = testFspId;
|
|
1559
|
-
req.skipPartyLookup = true;
|
|
1560
|
-
|
|
1561
|
-
await model.initialize(req);
|
|
1562
|
-
|
|
1563
|
-
expect(StateMachine.__instance.state).toBe('start');
|
|
1564
|
-
|
|
1565
|
-
// start the model running
|
|
1566
|
-
let resultPromise = model.run();
|
|
1567
|
-
|
|
1568
|
-
// now we started the model running we simulate a callback with the quote response
|
|
1569
|
-
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
1570
|
-
|
|
1571
|
-
// wait for the model to reach a terminal state
|
|
1572
|
-
let result = await resultPromise;
|
|
1573
|
-
|
|
1574
|
-
// check we stopped at quoteReceived state
|
|
1575
|
-
expect(result.currentState).toBe(SDKStateEnum.WAITING_FOR_QUOTE_ACCEPTANCE);
|
|
1576
|
-
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
1577
|
-
});
|
|
1578
|
-
|
|
1579
|
-
});
|