@mojaloop/sdk-scheme-adapter 12.3.0 → 13.0.2
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 +3 -0
- package/CHANGELOG.md +25 -0
- package/docker/ml-testing-toolkit/spec_files/api_definitions/fspiop_1.1/trigger_templates/transaction_request_followup.json +2 -2
- package/docker/ml-testing-toolkit/spec_files/rules_callback/default.json +7 -7
- package/docker/ml-testing-toolkit/spec_files/rules_response/default.json +16 -16
- package/docker/ml-testing-toolkit/spec_files/rules_response/default_pisp_rules.json +5 -5
- package/docker/ml-testing-toolkit/spec_files/rules_validation/default.json +10 -10
- package/package.json +3 -3
- package/src/InboundServer/handlers.js +114 -52
- package/src/OutboundServer/api.yaml +105 -32
- package/src/OutboundServer/api_interfaces/openapi.d.ts +46 -16
- package/src/OutboundServer/api_template/components/schemas/accountsResponse.yaml +9 -0
- package/src/OutboundServer/api_template/components/schemas/partiesByIdResponse.yaml +10 -3
- package/src/OutboundServer/api_template/components/schemas/quotesPostResponse.yaml +42 -34
- package/src/OutboundServer/api_template/components/schemas/simpleTransfersPostResponse.yaml +9 -2
- package/src/OutboundServer/api_template/components/schemas/transferRequest.yaml +3 -0
- package/src/OutboundServer/api_template/components/schemas/transferResponse.yaml +28 -2
- package/src/OutboundServer/api_template/components/schemas/transferStatusResponse.yaml +8 -1
- package/src/OutboundServer/handlers.js +1 -1
- package/src/config.js +1 -1
- package/src/lib/model/AccountsModel.js +13 -11
- package/src/lib/model/InboundTransfersModel.js +166 -24
- package/src/lib/model/OutboundRequestToPayModel.js +5 -6
- package/src/lib/model/OutboundRequestToPayTransferModel.js +2 -2
- package/src/lib/model/OutboundTransfersModel.js +261 -56
- package/src/lib/model/PartiesModel.js +15 -2
- package/src/lib/model/common/BackendError.js +28 -4
- package/src/lib/model/common/index.js +2 -1
- package/test/__mocks__/@mojaloop/sdk-standard-components.js +3 -2
- package/test/integration/lib/Outbound/parties.test.js +2 -0
- package/test/integration/lib/Outbound/quotes.test.js +2 -0
- package/test/integration/lib/Outbound/simpleTransfers.test.js +2 -0
- package/test/unit/InboundServer.test.js +9 -9
- package/test/unit/TestServer.test.js +11 -13
- package/test/unit/api/accounts/data/postAccountsErrorMojaloopResponse.json +11 -3
- package/test/unit/api/accounts/data/postAccountsSuccessResponse.json +14 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError1.json +13 -0
- package/test/unit/api/accounts/data/postAccountsSuccessResponseWithError2.json +18 -0
- package/test/unit/api/accounts/utils.js +15 -1
- package/test/unit/api/transfers/data/getTransfersCommittedResponse.json +18 -15
- package/test/unit/api/transfers/data/getTransfersErrorNotFound.json +1 -0
- package/test/unit/api/transfers/data/postTransfersErrorMojaloopResponse.json +9 -0
- package/test/unit/api/transfers/data/postTransfersErrorTimeoutResponse.json +1 -0
- package/test/unit/api/transfers/data/postTransfersSuccessResponse.json +74 -47
- package/test/unit/api/transfers/utils.js +85 -4
- package/test/unit/data/commonHttpHeaders.json +1 -0
- package/test/unit/inboundApi/handlers.test.js +45 -14
- package/test/unit/lib/model/AccountsModel.test.js +9 -6
- package/test/unit/lib/model/InboundTransfersModel.test.js +210 -30
- package/test/unit/lib/model/OutboundRequestToPayModel.test.js +1 -1
- package/test/unit/lib/model/OutboundRequestToPayTransferModel.test.js +3 -3
- package/test/unit/lib/model/OutboundTransfersModel.test.js +826 -157
- package/test/unit/lib/model/PartiesModel.test.js +13 -7
- package/test/unit/lib/model/QuotesModel.test.js +8 -2
- package/test/unit/lib/model/TransfersModel.test.js +8 -2
- package/test/unit/lib/model/data/defaultConfig.json +9 -9
- package/test/unit/lib/model/data/mockArguments.json +97 -40
- package/test/unit/lib/model/data/payeeParty.json +13 -11
- package/test/unit/lib/model/data/quoteResponse.json +36 -25
- package/test/unit/lib/model/data/transferFulfil.json +5 -3
|
@@ -29,7 +29,7 @@ const quoteResponseTemplate = require('./data/quoteResponse');
|
|
|
29
29
|
const transferFulfil = require('./data/transferFulfil');
|
|
30
30
|
|
|
31
31
|
const genPartyId = (party) => {
|
|
32
|
-
const { partyIdType, partyIdentifier, partySubIdOrType } = party.party.partyIdInfo;
|
|
32
|
+
const { partyIdType, partyIdentifier, partySubIdOrType } = party.body.party.partyIdInfo;
|
|
33
33
|
return PartiesModel.channelName({
|
|
34
34
|
type: partyIdType,
|
|
35
35
|
id: partyIdentifier,
|
|
@@ -39,6 +39,7 @@ const genPartyId = (party) => {
|
|
|
39
39
|
|
|
40
40
|
// util function to simulate a party resolution subscription message on a cache client
|
|
41
41
|
const emitPartyCacheMessage = (cache, party) => cache.publish(genPartyId(party), JSON.stringify(party));
|
|
42
|
+
const emitMultiPartiesCacheMessage = (cache, party) => cache.add(genPartyId(party), JSON.stringify(party));
|
|
42
43
|
|
|
43
44
|
// util function to simulate a quote response subscription message on a cache client
|
|
44
45
|
const emitQuoteResponseCacheMessage = (cache, quoteId, quoteResponse) => cache.publish(`qt_${quoteId}`, JSON.stringify(quoteResponse));
|
|
@@ -46,6 +47,10 @@ const emitQuoteResponseCacheMessage = (cache, quoteId, quoteResponse) => cache.p
|
|
|
46
47
|
// util function to simulate a transfer fulfilment subscription message on a cache client
|
|
47
48
|
const emitTransferFulfilCacheMessage = (cache, transferId, fulfil) => cache.publish(`tf_${transferId}`, JSON.stringify(fulfil));
|
|
48
49
|
|
|
50
|
+
const dummyRequestsModuleResponse = {
|
|
51
|
+
originalRequest: {}
|
|
52
|
+
};
|
|
53
|
+
|
|
49
54
|
describe('outboundModel', () => {
|
|
50
55
|
let quoteResponse;
|
|
51
56
|
let config;
|
|
@@ -73,13 +78,27 @@ describe('outboundModel', () => {
|
|
|
73
78
|
config.rejectExpiredTransferFulfils = rejects.transferFulfils;
|
|
74
79
|
|
|
75
80
|
// simulate a callback with the resolved party
|
|
76
|
-
MojaloopRequests.__getParties = jest.fn(() =>
|
|
81
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
82
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
83
|
+
return {
|
|
84
|
+
originalRequest: {
|
|
85
|
+
headers: [],
|
|
86
|
+
body: {},
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
});
|
|
77
90
|
|
|
78
91
|
// simulate a delayed callback with the quote response
|
|
79
92
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
80
93
|
setTimeout(() => {
|
|
81
94
|
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
82
95
|
}, delays.requestQuotes ? delays.requestQuotes * 1000 : 0);
|
|
96
|
+
return {
|
|
97
|
+
originalRequest: {
|
|
98
|
+
headers: [],
|
|
99
|
+
body: postQuotesBody,
|
|
100
|
+
}
|
|
101
|
+
};
|
|
83
102
|
});
|
|
84
103
|
|
|
85
104
|
// simulate a delayed callback with the transfer fulfilment
|
|
@@ -87,6 +106,12 @@ describe('outboundModel', () => {
|
|
|
87
106
|
setTimeout(() => {
|
|
88
107
|
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
89
108
|
}, delays.prepareTransfer ? delays.prepareTransfer * 1000 : 0);
|
|
109
|
+
return {
|
|
110
|
+
originalRequest: {
|
|
111
|
+
headers: [],
|
|
112
|
+
body: postTransfersBody,
|
|
113
|
+
}
|
|
114
|
+
};
|
|
90
115
|
});
|
|
91
116
|
|
|
92
117
|
const model = new Model({
|
|
@@ -121,13 +146,22 @@ describe('outboundModel', () => {
|
|
|
121
146
|
|
|
122
147
|
beforeEach(async () => {
|
|
123
148
|
config = JSON.parse(JSON.stringify(defaultConfig));
|
|
124
|
-
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve());
|
|
125
|
-
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve());
|
|
126
|
-
MojaloopRequests.
|
|
127
|
-
MojaloopRequests.
|
|
128
|
-
MojaloopRequests.
|
|
129
|
-
|
|
130
|
-
|
|
149
|
+
MojaloopRequests.__postParticipants = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
150
|
+
MojaloopRequests.__getParties = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
151
|
+
MojaloopRequests.__putQuotes = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
152
|
+
MojaloopRequests.__putQuotesError = jest.fn(() => Promise.resolve(dummyRequestsModuleResponse));
|
|
153
|
+
MojaloopRequests.__postQuotes = jest.fn((body) => Promise.resolve({
|
|
154
|
+
originalRequest: {
|
|
155
|
+
headers: [],
|
|
156
|
+
body: body,
|
|
157
|
+
}
|
|
158
|
+
}));
|
|
159
|
+
MojaloopRequests.__postTransfers = jest.fn((body) => Promise.resolve({
|
|
160
|
+
originalRequest: {
|
|
161
|
+
headers: [],
|
|
162
|
+
body: body,
|
|
163
|
+
}
|
|
164
|
+
}));
|
|
131
165
|
cache = new Cache({
|
|
132
166
|
host: 'dummycachehost',
|
|
133
167
|
port: 1234,
|
|
@@ -152,14 +186,13 @@ describe('outboundModel', () => {
|
|
|
152
186
|
expect(StateMachine.__instance.state).toBe('start');
|
|
153
187
|
});
|
|
154
188
|
|
|
155
|
-
|
|
156
189
|
test('executes all three transfer stages without halting when AUTO_ACCEPT_PARTY and AUTO_ACCEPT_QUOTES are true', async () => {
|
|
157
190
|
config.autoAcceptParty = true;
|
|
158
191
|
config.autoAcceptQuotes = true;
|
|
159
192
|
|
|
160
193
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
161
194
|
emitPartyCacheMessage(cache, payeeParty);
|
|
162
|
-
return Promise.resolve();
|
|
195
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
163
196
|
});
|
|
164
197
|
|
|
165
198
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
@@ -173,13 +206,13 @@ describe('outboundModel', () => {
|
|
|
173
206
|
|
|
174
207
|
// simulate a callback with the quote response
|
|
175
208
|
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
176
|
-
return Promise.resolve();
|
|
209
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
177
210
|
});
|
|
178
211
|
|
|
179
212
|
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
180
213
|
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
181
214
|
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
182
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
215
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
183
216
|
|
|
184
217
|
const extensionList = postTransfersBody.extensionList.extension;
|
|
185
218
|
expect(extensionList).toBeTruthy();
|
|
@@ -187,12 +220,190 @@ describe('outboundModel', () => {
|
|
|
187
220
|
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
188
221
|
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
189
222
|
|
|
190
|
-
expect(destFspId).toBe(quoteResponse.headers['fspiop-source']);
|
|
191
|
-
expect(model.data.to.fspId).toBe(payeeParty.party.partyIdInfo.fspId);
|
|
192
|
-
expect(quoteResponse.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
223
|
+
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
224
|
+
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
225
|
+
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
193
226
|
|
|
194
227
|
// simulate a callback with the transfer fulfilment
|
|
195
228
|
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
229
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const model = new Model({
|
|
233
|
+
cache,
|
|
234
|
+
logger,
|
|
235
|
+
metricsClient,
|
|
236
|
+
...config,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
240
|
+
|
|
241
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
242
|
+
|
|
243
|
+
// start the model running
|
|
244
|
+
const result = await model.run();
|
|
245
|
+
|
|
246
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
247
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
248
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
249
|
+
|
|
250
|
+
// make sure no PATCH was sent as we did not set config or receive a RESERVED state
|
|
251
|
+
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(0);
|
|
252
|
+
|
|
253
|
+
// check we stopped at payeeResolved state
|
|
254
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
255
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('sends a PATCH /transfers/{transferId} request to payee DFSP when SEND_FINAL_NOTIFICATION_IF_REQUESTED is true', async () => {
|
|
259
|
+
config.autoAcceptParty = true;
|
|
260
|
+
config.autoAcceptQuotes = true;
|
|
261
|
+
config.sendFinalNotificationIfRequested = true;
|
|
262
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
263
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
264
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
265
|
+
});
|
|
266
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
267
|
+
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
268
|
+
// including extension list
|
|
269
|
+
const extensionList = postQuotesBody.extensionList.extension;
|
|
270
|
+
expect(extensionList).toBeTruthy();
|
|
271
|
+
expect(extensionList.length).toBe(2);
|
|
272
|
+
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
273
|
+
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
274
|
+
// simulate a callback with the quote response
|
|
275
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
276
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
277
|
+
});
|
|
278
|
+
const pb = JSON.parse(JSON.stringify(transferFulfil));
|
|
279
|
+
pb.data.body.transferState = 'RESERVED';
|
|
280
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
281
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
282
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
283
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
284
|
+
const extensionList = postTransfersBody.extensionList.extension;
|
|
285
|
+
expect(extensionList).toBeTruthy();
|
|
286
|
+
expect(extensionList.length).toBe(2);
|
|
287
|
+
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
288
|
+
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
289
|
+
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
290
|
+
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
291
|
+
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
292
|
+
// simulate a callback with the transfer fulfilment
|
|
293
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, pb);
|
|
294
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
295
|
+
});
|
|
296
|
+
const model = new Model({
|
|
297
|
+
cache,
|
|
298
|
+
logger,
|
|
299
|
+
metricsClient,
|
|
300
|
+
...config,
|
|
301
|
+
});
|
|
302
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
303
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
304
|
+
// start the model running
|
|
305
|
+
const result = await model.run();
|
|
306
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
307
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
308
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(1);
|
|
310
|
+
expect(MojaloopRequests.__patchTransfers.mock.calls[0][0]).toEqual(model.data.transferId);
|
|
311
|
+
expect(MojaloopRequests.__patchTransfers.mock.calls[0][1].transferState).toEqual('COMMITTED');
|
|
312
|
+
expect(MojaloopRequests.__patchTransfers.mock.calls[0][1].completedTimestamp).not.toBeUndefined();
|
|
313
|
+
expect(MojaloopRequests.__patchTransfers.mock.calls[0][2]).toEqual(quoteResponse.data.headers['fspiop-source']);
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
// check we stopped at payeeResolved state
|
|
317
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
318
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('uses quote response transfer amount for transfer prepare', async () => {
|
|
322
|
+
config.autoAcceptParty = true;
|
|
323
|
+
config.autoAcceptQuotes = true;
|
|
324
|
+
|
|
325
|
+
MojaloopRequests.__getParties = jest.fn(() => {
|
|
326
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
327
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// change the the transfer amount and currency in the quote response
|
|
331
|
+
// so it is different to the initial request
|
|
332
|
+
quoteResponse.data.body.transferAmount = {
|
|
333
|
+
currency: 'XYZ',
|
|
334
|
+
amount: '9876543210'
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
expect(quoteResponse.data.body.transferAmount).not.toEqual({
|
|
338
|
+
amount: transferRequest.amount,
|
|
339
|
+
currency: transferRequest.currency
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
343
|
+
// ensure that the `MojaloopRequests.postQuotes` method has been called with correct arguments
|
|
344
|
+
// including extension list
|
|
345
|
+
const extensionList = postQuotesBody.extensionList.extension;
|
|
346
|
+
expect(extensionList).toBeTruthy();
|
|
347
|
+
expect(extensionList.length).toBe(2);
|
|
348
|
+
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
349
|
+
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
350
|
+
|
|
351
|
+
// simulate a callback with the quote response
|
|
352
|
+
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
353
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody, destFspId) => {
|
|
357
|
+
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
358
|
+
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
359
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
360
|
+
|
|
361
|
+
const extensionList = postTransfersBody.extensionList.extension;
|
|
362
|
+
expect(extensionList).toBeTruthy();
|
|
363
|
+
expect(extensionList.length).toBe(2);
|
|
364
|
+
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
365
|
+
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
366
|
+
|
|
367
|
+
expect(destFspId).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
368
|
+
expect(model.data.to.fspId).toBe(payeeParty.body.party.partyIdInfo.fspId);
|
|
369
|
+
expect(quoteResponse.data.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
370
|
+
|
|
371
|
+
expect(postTransfersBody.amount).toEqual(quoteResponse.data.body.transferAmount);
|
|
372
|
+
|
|
373
|
+
// simulate a callback with the transfer fulfilment
|
|
374
|
+
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
375
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const model = new Model({
|
|
379
|
+
cache,
|
|
380
|
+
logger,
|
|
381
|
+
metricsClient,
|
|
382
|
+
...config,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
386
|
+
|
|
387
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
388
|
+
|
|
389
|
+
// start the model running
|
|
390
|
+
const result = await model.run();
|
|
391
|
+
|
|
392
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledTimes(1);
|
|
393
|
+
expect(MojaloopRequests.__postQuotes).toHaveBeenCalledTimes(1);
|
|
394
|
+
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
395
|
+
|
|
396
|
+
// make sure no PATCH was sent as we did not set config or receive a RESERVED state
|
|
397
|
+
expect(MojaloopRequests.__patchTransfers).toHaveBeenCalledTimes(0);
|
|
398
|
+
|
|
399
|
+
// check we stopped at payeeResolved state
|
|
400
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
401
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('test get transfer', async () => {
|
|
405
|
+
MojaloopRequests.__getTransfers = jest.fn((transferId) => {
|
|
406
|
+
emitTransferFulfilCacheMessage(cache, transferId, transferFulfil);
|
|
196
407
|
return Promise.resolve();
|
|
197
408
|
});
|
|
198
409
|
|
|
@@ -203,81 +414,470 @@ describe('outboundModel', () => {
|
|
|
203
414
|
...config,
|
|
204
415
|
});
|
|
205
416
|
|
|
206
|
-
|
|
417
|
+
const TRANSFER_ID = 'tx-id000011';
|
|
418
|
+
|
|
419
|
+
await model.initialize(JSON.parse(JSON.stringify({
|
|
420
|
+
...transferRequest,
|
|
421
|
+
currentState: 'getTransfer',
|
|
422
|
+
transferId: TRANSFER_ID,
|
|
423
|
+
})));
|
|
424
|
+
|
|
425
|
+
expect(StateMachine.__instance.state).toBe('getTransfer');
|
|
426
|
+
|
|
427
|
+
// start the model running
|
|
428
|
+
const result = await model.run();
|
|
429
|
+
|
|
430
|
+
expect(MojaloopRequests.__getTransfers).toHaveBeenCalledTimes(1);
|
|
431
|
+
|
|
432
|
+
// check we stopped at payeeResolved state
|
|
433
|
+
expect(result.currentState).toBe('COMPLETED');
|
|
434
|
+
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
test('resolves payee and halts when AUTO_ACCEPT_PARTY is false', async () => {
|
|
439
|
+
config.autoAcceptParty = false;
|
|
440
|
+
|
|
441
|
+
const model = new Model({
|
|
442
|
+
cache,
|
|
443
|
+
logger,
|
|
444
|
+
metricsClient,
|
|
445
|
+
...config,
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
449
|
+
|
|
450
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
451
|
+
|
|
452
|
+
// start the model running
|
|
453
|
+
const resultPromise = model.run();
|
|
454
|
+
|
|
455
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
456
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
457
|
+
|
|
458
|
+
// wait for the model to reach a terminal state
|
|
459
|
+
const result = await resultPromise;
|
|
460
|
+
|
|
461
|
+
// check we stopped at payeeResolved state
|
|
462
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
463
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('uses payee party fspid as source header when supplied - resolving payee', async () => {
|
|
467
|
+
config.autoAcceptParty = false;
|
|
468
|
+
|
|
469
|
+
const model = new Model({
|
|
470
|
+
cache,
|
|
471
|
+
logger,
|
|
472
|
+
metricsClient,
|
|
473
|
+
...config,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
477
|
+
const testFspId = 'TESTDESTFSPID';
|
|
478
|
+
req.to.fspId = testFspId;
|
|
479
|
+
|
|
480
|
+
await model.initialize(req);
|
|
481
|
+
|
|
482
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
483
|
+
|
|
484
|
+
// start the model running
|
|
485
|
+
const resultPromise = model.run();
|
|
486
|
+
|
|
487
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
488
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
489
|
+
|
|
490
|
+
// wait for the model to reach a terminal state
|
|
491
|
+
const result = await resultPromise;
|
|
492
|
+
|
|
493
|
+
// check we stopped at payeeResolved state
|
|
494
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
495
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
496
|
+
|
|
497
|
+
// check getParties mojaloop requests method was called with the correct arguments
|
|
498
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledWith(req.to.idType, req.to.idValue, req.to.idSubValue, testFspId);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test('resolves multiple payees and halts', async () => {
|
|
502
|
+
config.autoAcceptParty = false;
|
|
503
|
+
config.multiplePartiesResponse = true;
|
|
504
|
+
config.multiplePartiesResponseSeconds = 2;
|
|
505
|
+
const model = new Model({
|
|
506
|
+
cache,
|
|
507
|
+
logger,
|
|
508
|
+
metricsClient,
|
|
509
|
+
...config,
|
|
510
|
+
});
|
|
511
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
512
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
513
|
+
// start the model running
|
|
514
|
+
const resultPromise = model.run();
|
|
515
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
516
|
+
const payeeParty1 = JSON.parse(JSON.stringify(payeeParty));
|
|
517
|
+
payeeParty1.body.party.partyIdInfo.fspId = 'FirstFspId';
|
|
518
|
+
await emitMultiPartiesCacheMessage(cache, payeeParty1);
|
|
519
|
+
const payeeParty2 = JSON.parse(JSON.stringify(payeeParty));
|
|
520
|
+
payeeParty2.body.party.partyIdInfo.fspId = 'SecondFspId';
|
|
521
|
+
await emitMultiPartiesCacheMessage(cache, payeeParty2);
|
|
522
|
+
// wait for the model to reach a terminal state
|
|
523
|
+
const result = await resultPromise;
|
|
524
|
+
// check we stopped at payeeResolved state
|
|
525
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
526
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
527
|
+
expect(result.to[0].fspId).toEqual('FirstFspId');
|
|
528
|
+
expect(result.to[1].fspId).toEqual('SecondFspId');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
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 () => {
|
|
532
|
+
config.autoAcceptParty = false;
|
|
533
|
+
config.autoAcceptQuotes = false;
|
|
534
|
+
|
|
535
|
+
let model = new Model({
|
|
536
|
+
cache,
|
|
537
|
+
logger,
|
|
538
|
+
metricsClient,
|
|
539
|
+
...config,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
await model.initialize(JSON.parse(JSON.stringify(transferRequest)));
|
|
543
|
+
|
|
544
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
545
|
+
|
|
546
|
+
// start the model running
|
|
547
|
+
let resultPromise = model.run();
|
|
548
|
+
|
|
549
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
550
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
551
|
+
|
|
552
|
+
// wait for the model to reach a terminal state
|
|
553
|
+
let result = await resultPromise;
|
|
554
|
+
|
|
555
|
+
// check we stopped at payeeResolved state
|
|
556
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
557
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
558
|
+
|
|
559
|
+
const transferId = result.transferId;
|
|
560
|
+
|
|
561
|
+
// load a new model from the saved state
|
|
562
|
+
model = new Model({
|
|
563
|
+
cache,
|
|
564
|
+
logger,
|
|
565
|
+
metricsClient,
|
|
566
|
+
...config,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
await model.load(transferId);
|
|
570
|
+
|
|
571
|
+
// check the model loaded to the correct state
|
|
572
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
573
|
+
|
|
574
|
+
// now run the model again. this should trigger transition to quote request
|
|
575
|
+
resultPromise = model.run({ acceptParty: true });
|
|
576
|
+
// now we started the model running we simulate a callback with the quote response
|
|
577
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
578
|
+
|
|
579
|
+
// wait for the model to reach a terminal state
|
|
580
|
+
result = await resultPromise;
|
|
581
|
+
|
|
582
|
+
// check we stopped at payeeResolved state
|
|
583
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
584
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('Allows change of transferAmount at accept party phase', async () => {
|
|
588
|
+
config.autoAcceptParty = false;
|
|
589
|
+
config.autoAcceptQuotes = false;
|
|
590
|
+
|
|
591
|
+
let model = new Model({
|
|
592
|
+
cache,
|
|
593
|
+
logger,
|
|
594
|
+
metricsClient,
|
|
595
|
+
...config,
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
599
|
+
|
|
600
|
+
// record the initial requested transfer amount
|
|
601
|
+
const initialAmount = req.amount;
|
|
602
|
+
|
|
603
|
+
await model.initialize(req);
|
|
604
|
+
|
|
605
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
606
|
+
|
|
607
|
+
// start the model running
|
|
608
|
+
let resultPromise = model.run();
|
|
609
|
+
|
|
610
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
611
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
612
|
+
|
|
613
|
+
// wait for the model to reach a terminal state
|
|
614
|
+
let result = await resultPromise;
|
|
615
|
+
|
|
616
|
+
// check we stopped at payeeResolved state
|
|
617
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
618
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
619
|
+
|
|
620
|
+
expect(result.amount).toEqual(initialAmount);
|
|
621
|
+
|
|
622
|
+
const transferId = result.transferId;
|
|
623
|
+
|
|
624
|
+
// load a new model from the saved state
|
|
625
|
+
model = new Model({
|
|
626
|
+
cache,
|
|
627
|
+
logger,
|
|
628
|
+
metricsClient,
|
|
629
|
+
...config,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
await model.load(transferId);
|
|
633
|
+
|
|
634
|
+
// check the model loaded to the correct state
|
|
635
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
636
|
+
|
|
637
|
+
const resume = {
|
|
638
|
+
amount: 999,
|
|
639
|
+
acceptParty: true,
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// now run the model again. this should trigger transition to quote request
|
|
643
|
+
resultPromise = model.run(resume);
|
|
644
|
+
|
|
645
|
+
// now we started the model running we simulate a callback with the quote response
|
|
646
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
647
|
+
|
|
648
|
+
// wait for the model to reach a terminal state
|
|
649
|
+
result = await resultPromise;
|
|
650
|
+
|
|
651
|
+
// check we stopped at quoteReceived state
|
|
652
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
653
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
654
|
+
|
|
655
|
+
// check the accept party key got merged to the state
|
|
656
|
+
expect(result.acceptParty).toEqual(true);
|
|
657
|
+
|
|
658
|
+
// check the amount key got changed
|
|
659
|
+
expect(result.amount).toEqual(resume.amount);
|
|
660
|
+
|
|
661
|
+
// check the quote request amount is the NEW amount, not the initial amount
|
|
662
|
+
expect(result.quoteRequest.body.amount.amount).toStrictEqual(resume.amount);
|
|
663
|
+
expect(result.quoteRequest.body.amount.amount).not.toEqual(initialAmount);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('Allows change of payee party at accept party phase (round-robin support)', async () => {
|
|
667
|
+
config.autoAcceptParty = false;
|
|
668
|
+
config.autoAcceptQuotes = false;
|
|
669
|
+
|
|
670
|
+
let model = new Model({
|
|
671
|
+
cache,
|
|
672
|
+
logger,
|
|
673
|
+
metricsClient,
|
|
674
|
+
...config,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
678
|
+
|
|
679
|
+
// record the initial requested transfer amount
|
|
680
|
+
const initialAmount = req.amount;
|
|
681
|
+
|
|
682
|
+
await model.initialize(req);
|
|
683
|
+
|
|
684
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
685
|
+
|
|
686
|
+
// start the model running
|
|
687
|
+
let resultPromise = model.run();
|
|
688
|
+
|
|
689
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
690
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
691
|
+
|
|
692
|
+
// wait for the model to reach a terminal state
|
|
693
|
+
let result = await resultPromise;
|
|
694
|
+
|
|
695
|
+
// check we stopped at payeeResolved state
|
|
696
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
697
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
698
|
+
|
|
699
|
+
expect(result.amount).toEqual(initialAmount);
|
|
700
|
+
|
|
701
|
+
const transferId = result.transferId;
|
|
702
|
+
|
|
703
|
+
// load a new model from the saved state
|
|
704
|
+
model = new Model({
|
|
705
|
+
cache,
|
|
706
|
+
logger,
|
|
707
|
+
metricsClient,
|
|
708
|
+
...config,
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
await model.load(transferId);
|
|
712
|
+
|
|
713
|
+
// check the model loaded to the correct state
|
|
714
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
715
|
+
|
|
716
|
+
const newPayee = {
|
|
717
|
+
partyIdInfo: {
|
|
718
|
+
partySubIdOrType: undefined,
|
|
719
|
+
partyIdType: 'PASSPORT',
|
|
720
|
+
partyIdentifier: 'AAABBBCCCDDDEEE',
|
|
721
|
+
fspId: 'TESTDFSP'
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
const newPayeeInternal = {
|
|
726
|
+
idType: newPayee.partyIdInfo.partyIdType,
|
|
727
|
+
idValue: newPayee.partyIdInfo.partyIdentifier,
|
|
728
|
+
fspId: newPayee.partyIdInfo.fspId,
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
const resume = {
|
|
732
|
+
acceptParty: true,
|
|
733
|
+
to: newPayeeInternal,
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// now run the model again. this should trigger transition to quote request
|
|
737
|
+
resultPromise = model.run(resume);
|
|
738
|
+
|
|
739
|
+
// now we started the model running we simulate a callback with the quote response
|
|
740
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
741
|
+
|
|
742
|
+
// wait for the model to reach a terminal state
|
|
743
|
+
result = await resultPromise;
|
|
744
|
+
|
|
745
|
+
// check we stopped at quoteReceived state
|
|
746
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
747
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
748
|
+
|
|
749
|
+
// check the accept party key got merged to the state
|
|
750
|
+
expect(result.acceptParty).toEqual(true);
|
|
751
|
+
|
|
752
|
+
// check the "to" passed in to model resume is merged into the model state correctly
|
|
753
|
+
expect(result.to).toStrictEqual(newPayeeInternal);
|
|
754
|
+
|
|
755
|
+
// check the quote request payee party is the NEW one, not the initial one.
|
|
756
|
+
expect(result.quoteRequest.body.payee).toStrictEqual(newPayee);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
test('Does not merge resume data keys into state that are not permitted', async () => {
|
|
760
|
+
config.autoAcceptParty = false;
|
|
761
|
+
config.autoAcceptQuotes = false;
|
|
762
|
+
|
|
763
|
+
let model = new Model({
|
|
764
|
+
cache,
|
|
765
|
+
logger,
|
|
766
|
+
metricsClient,
|
|
767
|
+
...config,
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
const req = JSON.parse(JSON.stringify(transferRequest));
|
|
771
|
+
|
|
772
|
+
// record the initial requested transfer amount
|
|
773
|
+
const initialAmount = req.amount;
|
|
774
|
+
|
|
775
|
+
await model.initialize(req);
|
|
776
|
+
|
|
777
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
778
|
+
|
|
779
|
+
// start the model running
|
|
780
|
+
let resultPromise = model.run();
|
|
781
|
+
|
|
782
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
783
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
784
|
+
|
|
785
|
+
// wait for the model to reach a terminal state
|
|
786
|
+
let result = await resultPromise;
|
|
787
|
+
|
|
788
|
+
// check we stopped at payeeResolved state
|
|
789
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
790
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
791
|
+
|
|
792
|
+
expect(result.amount).toEqual(initialAmount);
|
|
793
|
+
|
|
794
|
+
const transferId = result.transferId;
|
|
795
|
+
|
|
796
|
+
// load a new model from the saved state
|
|
797
|
+
model = new Model({
|
|
798
|
+
cache,
|
|
799
|
+
logger,
|
|
800
|
+
metricsClient,
|
|
801
|
+
...config,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
await model.load(transferId);
|
|
207
805
|
|
|
208
|
-
|
|
806
|
+
// check the model loaded to the correct state
|
|
807
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
209
808
|
|
|
210
|
-
|
|
211
|
-
|
|
809
|
+
const resume = {
|
|
810
|
+
amount: 999,
|
|
811
|
+
acceptParty: true,
|
|
812
|
+
someRandomKey: 'this key name is not permitted',
|
|
813
|
+
};
|
|
212
814
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
815
|
+
// now run the model again. this should trigger transition to quote request
|
|
816
|
+
resultPromise = model.run(resume);
|
|
216
817
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
220
|
-
});
|
|
818
|
+
// now we started the model running we simulate a callback with the quote response
|
|
819
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
221
820
|
|
|
821
|
+
// wait for the model to reach a terminal state
|
|
822
|
+
result = await resultPromise;
|
|
222
823
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
824
|
+
// check we stopped at quoteReceived state
|
|
825
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
826
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
226
827
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return Promise.resolve();
|
|
230
|
-
});
|
|
828
|
+
// check the accept party key got merged to the state
|
|
829
|
+
expect(result.acceptParty).toEqual(true);
|
|
231
830
|
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
quoteResponse.data.transferAmount = {
|
|
235
|
-
currency: 'XYZ',
|
|
236
|
-
amount: '9876543210'
|
|
237
|
-
};
|
|
831
|
+
// check the amount key got changed
|
|
832
|
+
expect(result.amount).toEqual(resume.amount);
|
|
238
833
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
});
|
|
834
|
+
// check the quote request amount is the NEW amount, not the initial amount
|
|
835
|
+
expect(result.quoteRequest.body.amount.amount).toStrictEqual(resume.amount);
|
|
836
|
+
expect(result.quoteRequest.body.amount.amount).not.toEqual(initialAmount);
|
|
243
837
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const extensionList = postQuotesBody.extensionList.extension;
|
|
248
|
-
expect(extensionList).toBeTruthy();
|
|
249
|
-
expect(extensionList.length).toBe(2);
|
|
250
|
-
expect(extensionList[0]).toEqual({ key: 'qkey1', value: 'qvalue1' });
|
|
251
|
-
expect(extensionList[1]).toEqual({ key: 'qkey2', value: 'qvalue2' });
|
|
838
|
+
// check that our disallowed key is not merged to the transfer state
|
|
839
|
+
expect(result.someRandomKey).toBeUndefined();
|
|
840
|
+
});
|
|
252
841
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
842
|
+
test('skips resolving party when to.fspid is specified and skipPartyLookup is truthy', async () => {
|
|
843
|
+
config.autoAcceptParty = false;
|
|
844
|
+
config.autoAcceptQuotes = false;
|
|
845
|
+
|
|
846
|
+
let model = new Model({
|
|
847
|
+
cache,
|
|
848
|
+
logger,
|
|
849
|
+
metricsClient,
|
|
850
|
+
...config,
|
|
256
851
|
});
|
|
257
852
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
853
|
+
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
854
|
+
const testFspId = 'TESTDESTFSPID';
|
|
855
|
+
req.to.fspId = testFspId;
|
|
856
|
+
req.skipPartyLookup = true;
|
|
262
857
|
|
|
263
|
-
|
|
264
|
-
expect(extensionList).toBeTruthy();
|
|
265
|
-
expect(extensionList.length).toBe(2);
|
|
266
|
-
expect(extensionList[0]).toEqual({ key: 'tkey1', value: 'tvalue1' });
|
|
267
|
-
expect(extensionList[1]).toEqual({ key: 'tkey2', value: 'tvalue2' });
|
|
858
|
+
await model.initialize(req);
|
|
268
859
|
|
|
269
|
-
|
|
270
|
-
expect(model.data.to.fspId).toBe(payeeParty.party.partyIdInfo.fspId);
|
|
271
|
-
expect(quoteResponse.headers['fspiop-source']).not.toBe(model.data.to.fspId);
|
|
860
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
272
861
|
|
|
273
|
-
|
|
862
|
+
// start the model running
|
|
863
|
+
let resultPromise = model.run();
|
|
274
864
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return Promise.resolve();
|
|
278
|
-
});
|
|
865
|
+
// now we started the model running we simulate a callback with the quote response
|
|
866
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
279
867
|
|
|
280
|
-
|
|
868
|
+
// wait for the model to reach a terminal state
|
|
869
|
+
let result = await resultPromise;
|
|
870
|
+
|
|
871
|
+
// check we stopped at quoteReceived state
|
|
872
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
873
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
test('aborts after party rejected by backend', async () => {
|
|
877
|
+
config.autoAcceptParty = false;
|
|
878
|
+
config.autoAcceptQuotes = false;
|
|
879
|
+
|
|
880
|
+
let model = new Model({
|
|
281
881
|
cache,
|
|
282
882
|
logger,
|
|
283
883
|
metricsClient,
|
|
@@ -289,56 +889,47 @@ describe('outboundModel', () => {
|
|
|
289
889
|
expect(StateMachine.__instance.state).toBe('start');
|
|
290
890
|
|
|
291
891
|
// start the model running
|
|
292
|
-
|
|
892
|
+
let resultPromise = model.run();
|
|
293
893
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
894
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
895
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
297
896
|
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
expect(StateMachine.__instance.state).toBe('succeeded');
|
|
301
|
-
});
|
|
897
|
+
// wait for the model to reach a terminal state
|
|
898
|
+
let result = await resultPromise;
|
|
302
899
|
|
|
900
|
+
// check we stopped at payeeResolved state
|
|
901
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
902
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
303
903
|
|
|
304
|
-
|
|
305
|
-
MojaloopRequests.__getTransfers = jest.fn((transferId) => {
|
|
306
|
-
emitTransferFulfilCacheMessage(cache, transferId, transferFulfil);
|
|
307
|
-
return Promise.resolve();
|
|
308
|
-
});
|
|
904
|
+
const transferId = result.transferId;
|
|
309
905
|
|
|
310
|
-
|
|
906
|
+
// load a new model from the saved state
|
|
907
|
+
model = new Model({
|
|
311
908
|
cache,
|
|
312
909
|
logger,
|
|
313
910
|
metricsClient,
|
|
314
911
|
...config,
|
|
315
912
|
});
|
|
316
913
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
await model.initialize(JSON.parse(JSON.stringify({
|
|
320
|
-
...transferRequest,
|
|
321
|
-
currentState: 'getTransfer',
|
|
322
|
-
transferId: TRANSFER_ID,
|
|
323
|
-
})));
|
|
324
|
-
|
|
325
|
-
expect(StateMachine.__instance.state).toBe('getTransfer');
|
|
914
|
+
await model.load(transferId);
|
|
326
915
|
|
|
327
|
-
//
|
|
328
|
-
|
|
916
|
+
// check the model loaded to the correct state
|
|
917
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
329
918
|
|
|
330
|
-
|
|
919
|
+
// now run the model again with a party rejection. this should trigger transition to quote request
|
|
920
|
+
result = await model.run({ resume: { acceptParty: false } });
|
|
331
921
|
|
|
332
|
-
// check we stopped at
|
|
333
|
-
expect(result.currentState).toBe('
|
|
334
|
-
expect(
|
|
922
|
+
// check we stopped at quoteReceived state
|
|
923
|
+
expect(result.currentState).toBe('ABORTED');
|
|
924
|
+
expect(result.abortedReason).toBe('Payee rejected by backend');
|
|
925
|
+
expect(StateMachine.__instance.state).toBe('aborted');
|
|
335
926
|
});
|
|
336
927
|
|
|
337
|
-
|
|
338
|
-
test('resolves payee and halts when AUTO_ACCEPT_PARTY is false', async () => {
|
|
928
|
+
test('aborts after quote rejected by backend', async () => {
|
|
339
929
|
config.autoAcceptParty = false;
|
|
930
|
+
config.autoAcceptQuotes = false;
|
|
340
931
|
|
|
341
|
-
|
|
932
|
+
let model = new Model({
|
|
342
933
|
cache,
|
|
343
934
|
logger,
|
|
344
935
|
metricsClient,
|
|
@@ -350,55 +941,81 @@ describe('outboundModel', () => {
|
|
|
350
941
|
expect(StateMachine.__instance.state).toBe('start');
|
|
351
942
|
|
|
352
943
|
// start the model running
|
|
353
|
-
|
|
944
|
+
let resultPromise = model.run();
|
|
354
945
|
|
|
355
946
|
// now we started the model running we simulate a callback with the resolved party
|
|
356
947
|
emitPartyCacheMessage(cache, payeeParty);
|
|
357
948
|
|
|
358
949
|
// wait for the model to reach a terminal state
|
|
359
|
-
|
|
950
|
+
let result = await resultPromise;
|
|
360
951
|
|
|
361
952
|
// check we stopped at payeeResolved state
|
|
362
953
|
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
363
954
|
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
364
|
-
});
|
|
365
955
|
|
|
366
|
-
|
|
367
|
-
config.autoAcceptParty = false;
|
|
956
|
+
const transferId = result.transferId;
|
|
368
957
|
|
|
369
|
-
|
|
958
|
+
// load a new model from the saved state
|
|
959
|
+
model = new Model({
|
|
370
960
|
cache,
|
|
371
961
|
logger,
|
|
372
962
|
metricsClient,
|
|
373
963
|
...config,
|
|
374
964
|
});
|
|
375
965
|
|
|
376
|
-
|
|
377
|
-
const testFspId = 'TESTDESTFSPID';
|
|
378
|
-
req.to.fspId = testFspId;
|
|
379
|
-
|
|
380
|
-
await model.initialize(req);
|
|
966
|
+
await model.load(transferId);
|
|
381
967
|
|
|
382
|
-
|
|
968
|
+
// check the model loaded to the correct state
|
|
969
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
383
970
|
|
|
384
|
-
//
|
|
385
|
-
|
|
971
|
+
// now run the model again. this should trigger transition to quote request
|
|
972
|
+
resultPromise = model.run({ acceptParty: true });
|
|
386
973
|
|
|
387
|
-
// now we started the model running we simulate a callback with the
|
|
388
|
-
|
|
974
|
+
// now we started the model running we simulate a callback with the quote response
|
|
975
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
389
976
|
|
|
390
|
-
// wait for the model to reach
|
|
391
|
-
|
|
977
|
+
// wait for the model to reach quote received
|
|
978
|
+
result = await resultPromise;
|
|
392
979
|
|
|
393
980
|
// check we stopped at payeeResolved state
|
|
394
|
-
expect(result.currentState).toBe('
|
|
395
|
-
expect(StateMachine.__instance.state).toBe('
|
|
981
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
982
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
396
983
|
|
|
397
|
-
//
|
|
398
|
-
|
|
984
|
+
// now run the model again. this should trigger abort as the quote was not accepted
|
|
985
|
+
result = await model.run({ acceptQuote: false });
|
|
986
|
+
|
|
987
|
+
expect(result.currentState).toBe('ABORTED');
|
|
988
|
+
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
989
|
+
expect(StateMachine.__instance.state).toBe('aborted');
|
|
399
990
|
});
|
|
400
991
|
|
|
401
|
-
test('
|
|
992
|
+
test('should handle unknown state with a meaningful error message', async () => {
|
|
993
|
+
config.autoAcceptParty = false;
|
|
994
|
+
config.autoAcceptQuotes = false;
|
|
995
|
+
|
|
996
|
+
let model = new Model({
|
|
997
|
+
cache,
|
|
998
|
+
logger,
|
|
999
|
+
metricsClient,
|
|
1000
|
+
...config,
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
await model.initialize(JSON.parse(JSON.stringify({
|
|
1004
|
+
...transferRequest,
|
|
1005
|
+
currentState: 'abc'
|
|
1006
|
+
})));
|
|
1007
|
+
|
|
1008
|
+
expect(StateMachine.__instance.state).toBe('abc');
|
|
1009
|
+
|
|
1010
|
+
// start the model running
|
|
1011
|
+
let resultPromise = model.run();
|
|
1012
|
+
|
|
1013
|
+
// wait for the model to reach a terminal state
|
|
1014
|
+
let result = await resultPromise;
|
|
1015
|
+
expect(result).toBe(undefined);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test('should handle subsequent put transfer calls incase of aborted transfer', async () => {
|
|
402
1019
|
config.autoAcceptParty = false;
|
|
403
1020
|
config.autoAcceptQuotes = false;
|
|
404
1021
|
|
|
@@ -442,19 +1059,32 @@ describe('outboundModel', () => {
|
|
|
442
1059
|
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
443
1060
|
|
|
444
1061
|
// now run the model again. this should trigger transition to quote request
|
|
445
|
-
resultPromise = model.run();
|
|
1062
|
+
resultPromise = model.run({ acceptParty: true });
|
|
446
1063
|
|
|
447
1064
|
// now we started the model running we simulate a callback with the quote response
|
|
448
1065
|
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
449
1066
|
|
|
450
|
-
// wait for the model to reach
|
|
1067
|
+
// wait for the model to reach quote received
|
|
451
1068
|
result = await resultPromise;
|
|
452
1069
|
|
|
453
1070
|
// check we stopped at payeeResolved state
|
|
454
1071
|
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
455
1072
|
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
456
|
-
});
|
|
457
1073
|
|
|
1074
|
+
// now run the model again. this should trigger abort as the quote was not accepted
|
|
1075
|
+
result = await model.run({ acceptQuote: false });
|
|
1076
|
+
|
|
1077
|
+
expect(result.currentState).toBe('ABORTED');
|
|
1078
|
+
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
1079
|
+
expect(StateMachine.__instance.state).toBe('aborted');
|
|
1080
|
+
|
|
1081
|
+
// now run the model again. this should get the same result as previous one
|
|
1082
|
+
result = await model.run({ acceptQuote: false });
|
|
1083
|
+
|
|
1084
|
+
expect(result.currentState).toBe('ABORTED');
|
|
1085
|
+
expect(result.abortedReason).toBe('Quote rejected by backend');
|
|
1086
|
+
expect(StateMachine.__instance.state).toBe('aborted');
|
|
1087
|
+
});
|
|
458
1088
|
|
|
459
1089
|
test('halts and resumes after parties and quotes stages when AUTO_ACCEPT_PARTY is false and AUTO_ACCEPT_QUOTES is false', async () => {
|
|
460
1090
|
config.autoAcceptParty = false;
|
|
@@ -500,7 +1130,7 @@ describe('outboundModel', () => {
|
|
|
500
1130
|
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
501
1131
|
|
|
502
1132
|
// now run the model again. this should trigger transition to quote request
|
|
503
|
-
resultPromise = model.run();
|
|
1133
|
+
resultPromise = model.run({ acceptParty: true });
|
|
504
1134
|
|
|
505
1135
|
// now we started the model running we simulate a callback with the quote response
|
|
506
1136
|
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
@@ -526,7 +1156,7 @@ describe('outboundModel', () => {
|
|
|
526
1156
|
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
527
1157
|
|
|
528
1158
|
// now run the model again. this should trigger transition to quote request
|
|
529
|
-
resultPromise = model.run();
|
|
1159
|
+
resultPromise = model.run({ acceptQuote: true });
|
|
530
1160
|
|
|
531
1161
|
// now we started the model running we simulate a callback with the transfer fulfilment
|
|
532
1162
|
cache.publish(`tf_${model.data.transferId}`, JSON.stringify(transferFulfil));
|
|
@@ -547,26 +1177,26 @@ describe('outboundModel', () => {
|
|
|
547
1177
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
548
1178
|
// simulate a callback with the resolved party
|
|
549
1179
|
emitPartyCacheMessage(cache, payeeParty);
|
|
550
|
-
return Promise.resolve();
|
|
1180
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
551
1181
|
});
|
|
552
1182
|
|
|
553
1183
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
554
1184
|
// simulate a callback with the quote response
|
|
555
1185
|
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
556
|
-
return Promise.resolve();
|
|
1186
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
557
1187
|
});
|
|
558
1188
|
|
|
559
1189
|
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
560
1190
|
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
561
1191
|
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
562
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
1192
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
563
1193
|
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
564
1194
|
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
565
|
-
expect(payeeFsp).toEqual(payeeParty.party.partyIdInfo.fspId);
|
|
1195
|
+
expect(payeeFsp).toEqual(payeeParty.body.party.partyIdInfo.fspId);
|
|
566
1196
|
|
|
567
1197
|
// simulate a callback with the transfer fulfilment
|
|
568
1198
|
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
569
|
-
return Promise.resolve();
|
|
1199
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
570
1200
|
});
|
|
571
1201
|
|
|
572
1202
|
const model = new Model({
|
|
@@ -599,26 +1229,26 @@ describe('outboundModel', () => {
|
|
|
599
1229
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
600
1230
|
// simulate a callback with the resolved party
|
|
601
1231
|
emitPartyCacheMessage(cache, payeeParty);
|
|
602
|
-
return Promise.resolve();
|
|
1232
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
603
1233
|
});
|
|
604
1234
|
|
|
605
1235
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
606
1236
|
// simulate a callback with the quote response
|
|
607
1237
|
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
608
|
-
return Promise.resolve();
|
|
1238
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
609
1239
|
});
|
|
610
1240
|
|
|
611
1241
|
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
612
1242
|
//ensure that the `MojaloopRequests.postTransfers` method has been called with the correct arguments
|
|
613
1243
|
// set as the destination FSPID, picked up from the header's value `fspiop-source`
|
|
614
|
-
expect(model.data.quoteResponseSource).toBe(quoteResponse.headers['fspiop-source']);
|
|
1244
|
+
expect(model.data.quoteResponseSource).toBe(quoteResponse.data.headers['fspiop-source']);
|
|
615
1245
|
expect(MojaloopRequests.__postTransfers).toHaveBeenCalledTimes(1);
|
|
616
1246
|
const payeeFsp = MojaloopRequests.__postTransfers.mock.calls[0][0].payeeFsp;
|
|
617
|
-
expect(payeeFsp).toEqual(quoteResponse.headers['fspiop-source']);
|
|
1247
|
+
expect(payeeFsp).toEqual(quoteResponse.data.headers['fspiop-source']);
|
|
618
1248
|
|
|
619
1249
|
// simulate a callback with the transfer fulfilment
|
|
620
1250
|
emitTransferFulfilCacheMessage(cache, postTransfersBody.transferId, transferFulfil);
|
|
621
|
-
return Promise.resolve();
|
|
1251
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
622
1252
|
});
|
|
623
1253
|
|
|
624
1254
|
const model = new Model({
|
|
@@ -712,7 +1342,7 @@ describe('outboundModel', () => {
|
|
|
712
1342
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
713
1343
|
// simulate a callback with the resolved party
|
|
714
1344
|
cache.publish(genPartyId(payeeParty), JSON.stringify(expectError));
|
|
715
|
-
return Promise.resolve();
|
|
1345
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
716
1346
|
});
|
|
717
1347
|
|
|
718
1348
|
const model = new Model({
|
|
@@ -727,9 +1357,11 @@ describe('outboundModel', () => {
|
|
|
727
1357
|
expect(StateMachine.__instance.state).toBe('start');
|
|
728
1358
|
|
|
729
1359
|
const expectError = {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1360
|
+
body: {
|
|
1361
|
+
errorInformation: {
|
|
1362
|
+
errorCode: '3204',
|
|
1363
|
+
errorDescription: 'Party not found'
|
|
1364
|
+
}
|
|
733
1365
|
}
|
|
734
1366
|
};
|
|
735
1367
|
|
|
@@ -742,7 +1374,7 @@ describe('outboundModel', () => {
|
|
|
742
1374
|
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
743
1375
|
expect(err.transferState).toBeTruthy();
|
|
744
1376
|
expect(err.transferState.lastError).toBeTruthy();
|
|
745
|
-
expect(err.transferState.lastError.mojaloopError).toEqual(expectError);
|
|
1377
|
+
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.body);
|
|
746
1378
|
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
747
1379
|
return;
|
|
748
1380
|
}
|
|
@@ -769,13 +1401,13 @@ describe('outboundModel', () => {
|
|
|
769
1401
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
770
1402
|
// simulate a callback with the resolved party
|
|
771
1403
|
emitPartyCacheMessage(cache, payeeParty);
|
|
772
|
-
return Promise.resolve();
|
|
1404
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
773
1405
|
});
|
|
774
1406
|
|
|
775
1407
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
776
1408
|
// simulate a callback with the quote response
|
|
777
1409
|
cache.publish(`qt_${postQuotesBody.quoteId}`, JSON.stringify(expectError));
|
|
778
|
-
return Promise.resolve();
|
|
1410
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
779
1411
|
});
|
|
780
1412
|
|
|
781
1413
|
const model = new Model({
|
|
@@ -814,9 +1446,11 @@ describe('outboundModel', () => {
|
|
|
814
1446
|
const expectError = {
|
|
815
1447
|
type: 'transferError',
|
|
816
1448
|
data: {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1449
|
+
body: {
|
|
1450
|
+
errorInformation: {
|
|
1451
|
+
errorCode: '4001',
|
|
1452
|
+
errorDescription: 'Payer FSP insufficient liquidity'
|
|
1453
|
+
}
|
|
820
1454
|
}
|
|
821
1455
|
}
|
|
822
1456
|
};
|
|
@@ -824,19 +1458,19 @@ describe('outboundModel', () => {
|
|
|
824
1458
|
MojaloopRequests.__getParties = jest.fn(() => {
|
|
825
1459
|
// simulate a callback with the resolved party
|
|
826
1460
|
emitPartyCacheMessage(cache, payeeParty);
|
|
827
|
-
return Promise.resolve();
|
|
1461
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
828
1462
|
});
|
|
829
1463
|
|
|
830
1464
|
MojaloopRequests.__postQuotes = jest.fn((postQuotesBody) => {
|
|
831
1465
|
// simulate a callback with the quote response
|
|
832
1466
|
emitQuoteResponseCacheMessage(cache, postQuotesBody.quoteId, quoteResponse);
|
|
833
|
-
return Promise.resolve();
|
|
1467
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
834
1468
|
});
|
|
835
1469
|
|
|
836
1470
|
MojaloopRequests.__postTransfers = jest.fn((postTransfersBody) => {
|
|
837
1471
|
// simulate an error callback with the transfer fulfilment
|
|
838
1472
|
cache.publish(`tf_${postTransfersBody.transferId}`, JSON.stringify(expectError));
|
|
839
|
-
return Promise.resolve();
|
|
1473
|
+
return Promise.resolve(dummyRequestsModuleResponse);
|
|
840
1474
|
});
|
|
841
1475
|
|
|
842
1476
|
const model = new Model({
|
|
@@ -859,7 +1493,7 @@ describe('outboundModel', () => {
|
|
|
859
1493
|
expect(err.message.replace(/[ \n]/g,'')).toEqual(errMsg.replace(/[ \n]/g,''));
|
|
860
1494
|
expect(err.transferState).toBeTruthy();
|
|
861
1495
|
expect(err.transferState.lastError).toBeTruthy();
|
|
862
|
-
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data);
|
|
1496
|
+
expect(err.transferState.lastError.mojaloopError).toEqual(expectError.data.body);
|
|
863
1497
|
expect(err.transferState.lastError.transferState).toBe(undefined);
|
|
864
1498
|
return;
|
|
865
1499
|
}
|
|
@@ -902,4 +1536,39 @@ describe('outboundModel', () => {
|
|
|
902
1536
|
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_transfer_latency'));
|
|
903
1537
|
expect(metrics).toEqual(expect.stringContaining('mojaloop_connector_outbound_party_lookup_latency'));
|
|
904
1538
|
});
|
|
1539
|
+
|
|
1540
|
+
test('skips resolving party when to.fspid is specified and skipPartyLookup is truthy', async () => {
|
|
1541
|
+
config.autoAcceptParty = false;
|
|
1542
|
+
config.autoAcceptQuotes = false;
|
|
1543
|
+
|
|
1544
|
+
let model = new Model({
|
|
1545
|
+
cache,
|
|
1546
|
+
logger,
|
|
1547
|
+
metricsClient,
|
|
1548
|
+
...config,
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
1552
|
+
const testFspId = 'TESTDESTFSPID';
|
|
1553
|
+
req.to.fspId = testFspId;
|
|
1554
|
+
req.skipPartyLookup = true;
|
|
1555
|
+
|
|
1556
|
+
await model.initialize(req);
|
|
1557
|
+
|
|
1558
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
1559
|
+
|
|
1560
|
+
// start the model running
|
|
1561
|
+
let resultPromise = model.run();
|
|
1562
|
+
|
|
1563
|
+
// now we started the model running we simulate a callback with the quote response
|
|
1564
|
+
cache.publish(`qt_${model.data.quoteId}`, JSON.stringify(quoteResponse));
|
|
1565
|
+
|
|
1566
|
+
// wait for the model to reach a terminal state
|
|
1567
|
+
let result = await resultPromise;
|
|
1568
|
+
|
|
1569
|
+
// check we stopped at quoteReceived state
|
|
1570
|
+
expect(result.currentState).toBe('WAITING_FOR_QUOTE_ACCEPTANCE');
|
|
1571
|
+
expect(StateMachine.__instance.state).toBe('quoteReceived');
|
|
1572
|
+
});
|
|
1573
|
+
|
|
905
1574
|
});
|