@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,70 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const axios = require('axios');
|
|
4
|
-
const { uuid } = require('uuidv4');
|
|
5
|
-
const env = require('../../testEnv');
|
|
6
|
-
const transfersPostRequest = require('./data/transfersPostRequest.json');
|
|
7
|
-
const { SDKStateEnum } = require('../../../../src/lib/model/common');
|
|
8
|
-
|
|
9
|
-
jest.dontMock('redis');
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
"TRANSFERS_VALIDATION_WITH_PREVIOUS_QUOTES": false,
|
|
13
|
-
"TRANSFERS_VALIDATION_ILP_PACKET": false,
|
|
14
|
-
"TRANSFERS_VALIDATION_CONDITION": false,
|
|
15
|
-
|
|
16
|
-
Ensure these values in the TTK `user_config.json` file are set to false.
|
|
17
|
-
Since we are testing the /transfers endpoint in isolation without a prior
|
|
18
|
-
quote and a fake `ilpPacket` and `condition`.
|
|
19
|
-
*/
|
|
20
|
-
describe('/simpleTransfers', () => {
|
|
21
|
-
|
|
22
|
-
test('post - happy flow', async () => {
|
|
23
|
-
const postTransfersURI = `${env.OutboundHostURI}/simpleTransfers`;
|
|
24
|
-
const transferId = uuid();
|
|
25
|
-
const res = await axios({
|
|
26
|
-
method: 'POST',
|
|
27
|
-
url: postTransfersURI,
|
|
28
|
-
data: {
|
|
29
|
-
fspId: 'switch',
|
|
30
|
-
transfersPostRequest: {
|
|
31
|
-
...transfersPostRequest,
|
|
32
|
-
transferId
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
headers: {
|
|
36
|
-
'access-control-allow-origin': '*'
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
expect(res.status).toEqual(200);
|
|
41
|
-
expect(res.data.currentState).toEqual(SDKStateEnum.COMPLETED);
|
|
42
|
-
expect(typeof res.data.transfer).toEqual('object');
|
|
43
|
-
expect(typeof res.data.transfer.body).toEqual('object');
|
|
44
|
-
expect(typeof res.data.transfer.headers).toEqual('object');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('post - timeout', async () => {
|
|
48
|
-
const postTransfersURI = `${env.OutboundHostURI}/simpleTransfers`;
|
|
49
|
-
const transferId = uuid();
|
|
50
|
-
try {
|
|
51
|
-
await axios({
|
|
52
|
-
method: 'POST',
|
|
53
|
-
url: postTransfersURI,
|
|
54
|
-
data: {
|
|
55
|
-
fspId: 'timeout-fsp-id-transfer',
|
|
56
|
-
transfersPostRequest: {
|
|
57
|
-
...transfersPostRequest,
|
|
58
|
-
transferId
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
headers: {
|
|
62
|
-
'access-control-allow-origin': '*'
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
} catch (err) {
|
|
66
|
-
expect(err.response.status).toEqual(500);
|
|
67
|
-
expect(err.response.data.message).toEqual('Timeout');
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/*****
|
|
2
|
-
License
|
|
3
|
-
--------------
|
|
4
|
-
Copyright © 2017 Bill & Melinda Gates Foundation
|
|
5
|
-
The Mojaloop files are made available by the Bill & Melinda Gates Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at
|
|
6
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
-
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
|
8
|
-
|
|
9
|
-
Initial contribution
|
|
10
|
-
--------------------
|
|
11
|
-
The initial functionality and code base was donated by the Mowali project working in conjunction with MTN and Orange as service provides.
|
|
12
|
-
* Project: Mowali
|
|
13
|
-
|
|
14
|
-
Contributors
|
|
15
|
-
--------------
|
|
16
|
-
This is the official list of the Mojaloop project contributors for this file.
|
|
17
|
-
Names of the original copyright holders (individuals or organizations)
|
|
18
|
-
should be listed with a '*' in the first column. People who have
|
|
19
|
-
contributed from an organization can be listed under the organization
|
|
20
|
-
that actually holds the copyright for their contributions (see the
|
|
21
|
-
Gates Foundation organization for an example). Those individuals should have
|
|
22
|
-
their names indented and be marked with a '-'. Email address can be added
|
|
23
|
-
optionally within square brackets <email>.
|
|
24
|
-
* Gates Foundation
|
|
25
|
-
- Name Surname <name.surname@gatesfoundation.com>
|
|
26
|
-
|
|
27
|
-
* Crosslake
|
|
28
|
-
- Lewis Daly <lewisd@crosslaketech.com>
|
|
29
|
-
--------------
|
|
30
|
-
******/
|
|
31
|
-
'use strict';
|
|
32
|
-
|
|
33
|
-
jest.dontMock('redis');
|
|
34
|
-
|
|
35
|
-
const Cache = require('~/lib/cache');
|
|
36
|
-
const { Logger } = require('@mojaloop/sdk-standard-components');
|
|
37
|
-
const env = require('../testEnv');
|
|
38
|
-
|
|
39
|
-
const defaultCacheConfig = {
|
|
40
|
-
cacheUrl: env.redisUrl,
|
|
41
|
-
logger: null
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const createCache = async (config) => {
|
|
45
|
-
config.logger = new Logger.Logger({
|
|
46
|
-
context: {
|
|
47
|
-
app: 'mojaloop-sdk-inboundCache'
|
|
48
|
-
},
|
|
49
|
-
stringify: Logger.buildStringify({ space: 4 }),
|
|
50
|
-
});
|
|
51
|
-
const cache = new Cache(config);
|
|
52
|
-
await cache.connect();
|
|
53
|
-
|
|
54
|
-
return cache;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
describe('Cache', () => {
|
|
58
|
-
let cache;
|
|
59
|
-
|
|
60
|
-
beforeEach(async () => {
|
|
61
|
-
cache = await createCache(defaultCacheConfig);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
afterEach(async () => {
|
|
65
|
-
await cache.disconnect();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('Sets and retrieves an object in the cache', async () => {
|
|
69
|
-
// Arrange
|
|
70
|
-
const value = {test: true};
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
await cache.set('keyA', JSON.stringify(value));
|
|
74
|
-
const result = await cache.get('keyA');
|
|
75
|
-
|
|
76
|
-
// Assert
|
|
77
|
-
expect(result).toStrictEqual(value);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const ControlAgent = require('~/ControlAgent');
|
|
3
|
-
const TestControlServer = require('./ControlServer');
|
|
4
|
-
const { Logger } = require('@mojaloop/sdk-standard-components');
|
|
5
|
-
|
|
6
|
-
jest.mock('~/lib/cache');
|
|
7
|
-
|
|
8
|
-
// TODO:
|
|
9
|
-
// - diff against master to determine what else needs testing
|
|
10
|
-
// - especially look for assertions in the code
|
|
11
|
-
// - err.. grep the code for TODO
|
|
12
|
-
|
|
13
|
-
describe('ControlAgent', () => {
|
|
14
|
-
it('exposes a valid message API', () => {
|
|
15
|
-
expect(Object.keys(ControlAgent.build).sort()).toEqual(
|
|
16
|
-
Object.keys(ControlAgent.MESSAGE).sort(),
|
|
17
|
-
'The API exposed by the builder object must contain as top-level keys all of the message types exposed in the MESSAGE constant. Check that ControlAgent.MESSAGE has the same keys as ControlAgent.build.'
|
|
18
|
-
);
|
|
19
|
-
Object.entries(ControlAgent.build).forEach(([messageType, builders]) => {
|
|
20
|
-
expect(Object.keys(ControlAgent.VERB)).toEqual(
|
|
21
|
-
expect.arrayContaining(Object.keys(builders)),
|
|
22
|
-
`For message type '${messageType}' every builder must correspond to a verb. Check that ControlAgent.build.${messageType} has the same keys as ControlAgent.VERB.`
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
expect(Object.keys(ControlAgent.build.ERROR.NOTIFY).sort()).toEqual(
|
|
26
|
-
Object.keys(ControlAgent.ERROR).sort(),
|
|
27
|
-
'ControlAgent.ERROR.NOTIFY should contain the same keys as ControlAgent.ERROR'
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('API', () => {
|
|
32
|
-
let server, logger, client;
|
|
33
|
-
const appConfig = { control: { port: 4005 }, what: 'ever' };
|
|
34
|
-
const changedConfig = { ...appConfig, some: 'thing' };
|
|
35
|
-
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
logger = new Logger.Logger({ stringify: () => '' });
|
|
38
|
-
server = new TestControlServer.Server({ logger, appConfig });
|
|
39
|
-
client = await ControlAgent.Client.Create({
|
|
40
|
-
address: 'localhost',
|
|
41
|
-
port: server.address().port,
|
|
42
|
-
logger,
|
|
43
|
-
appConfig
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
afterEach(async () => {
|
|
48
|
-
await client.stop();
|
|
49
|
-
await server.stop();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('receives config when requested', async () => {
|
|
53
|
-
await client.send(ControlAgent.build.CONFIGURATION.READ());
|
|
54
|
-
const response = await client.receive();
|
|
55
|
-
expect(response).toEqual({
|
|
56
|
-
...JSON.parse(ControlAgent.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('emits new config when received', async () => {
|
|
61
|
-
const newConfigEvent = new Promise(
|
|
62
|
-
(resolve) => client.on(ControlAgent.EVENT.RECONFIGURE, resolve)
|
|
63
|
-
);
|
|
64
|
-
server.broadcastConfigChange(changedConfig);
|
|
65
|
-
const newConfEventData = await newConfigEvent;
|
|
66
|
-
expect(newConfEventData).toEqual(changedConfig);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**************************************************************************
|
|
2
|
-
* (C) Copyright ModusBox Inc. 2020 - 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
|
-
* Steven Oderayi - steven.oderayi@modusbox.com *
|
|
9
|
-
**************************************************************************/
|
|
10
|
-
|
|
11
|
-
const { EventEmitter } = require('events');
|
|
12
|
-
|
|
13
|
-
/**************************************************************************
|
|
14
|
-
* Internal events received by the control server via the exposed internal
|
|
15
|
-
* event emitter.
|
|
16
|
-
*************************************************************************/
|
|
17
|
-
const INTERNAL_EVENTS = {
|
|
18
|
-
SERVER: {
|
|
19
|
-
BROADCAST_CONFIG_CHANGE: 'BROADCAST_CONFIG_CHANGE',
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
const internalEventEmitter = new EventEmitter();
|
|
23
|
-
|
|
24
|
-
/**************************************************************************
|
|
25
|
-
* getInternalEventEmitter
|
|
26
|
-
*
|
|
27
|
-
* Returns an EventEmmitter that can be used to exchange internal events with
|
|
28
|
-
* either the control server or the client from other modules within this service.
|
|
29
|
-
* This prevents the need to pass down references to either the server or the client
|
|
30
|
-
* from one module to another in order to use their interfaces.
|
|
31
|
-
*
|
|
32
|
-
* @returns {events.EventEmitter}
|
|
33
|
-
*************************************************************************/
|
|
34
|
-
const getInternalEventEmitter = () => {
|
|
35
|
-
return internalEventEmitter;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
getInternalEventEmitter,
|
|
40
|
-
INTERNAL_EVENTS
|
|
41
|
-
};
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**************************************************************************
|
|
2
|
-
* (C) Copyright ModusBox Inc. 2020 - 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
|
-
* Matt Kingston - matt.kingston@modusbox.com *
|
|
9
|
-
**************************************************************************/
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
const ws = require('ws');
|
|
13
|
-
const jsonPatch = require('fast-json-patch');
|
|
14
|
-
const { generateSlug } = require('random-word-slugs');
|
|
15
|
-
const { getInternalEventEmitter, INTERNAL_EVENTS } = require('./events');
|
|
16
|
-
|
|
17
|
-
const ControlServerEventEmitter = getInternalEventEmitter();
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**************************************************************************
|
|
21
|
-
* The message protocol messages, verbs, and errors
|
|
22
|
-
*************************************************************************/
|
|
23
|
-
const MESSAGE = {
|
|
24
|
-
CONFIGURATION: 'CONFIGURATION',
|
|
25
|
-
ERROR: 'ERROR',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const VERB = {
|
|
29
|
-
READ: 'READ',
|
|
30
|
-
NOTIFY: 'NOTIFY',
|
|
31
|
-
PATCH: 'PATCH'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const ERROR = {
|
|
35
|
-
UNSUPPORTED_MESSAGE: 'UNSUPPORTED_MESSAGE',
|
|
36
|
-
UNSUPPORTED_VERB: 'UNSUPPORTED_VERB',
|
|
37
|
-
JSON_PARSE_ERROR: 'JSON_PARSE_ERROR',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/**************************************************************************
|
|
41
|
-
* Private convenience functions
|
|
42
|
-
*************************************************************************/
|
|
43
|
-
const serialise = JSON.stringify;
|
|
44
|
-
const deserialise = (msg) => {
|
|
45
|
-
//reviver function
|
|
46
|
-
return JSON.parse(msg.toString(), (k, v) => {
|
|
47
|
-
if (
|
|
48
|
-
v !== null &&
|
|
49
|
-
typeof v === 'object' &&
|
|
50
|
-
'type' in v &&
|
|
51
|
-
v.type === 'Buffer' &&
|
|
52
|
-
'data' in v &&
|
|
53
|
-
Array.isArray(v.data)) {
|
|
54
|
-
return new Buffer(v.data);
|
|
55
|
-
}
|
|
56
|
-
return v;
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const buildMsg = (verb, msg, data, id = generateSlug(4)) => serialise({
|
|
62
|
-
verb,
|
|
63
|
-
msg,
|
|
64
|
-
data,
|
|
65
|
-
id,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const buildPatchConfiguration = (oldConf, newConf, id) => {
|
|
69
|
-
const patches = jsonPatch.compare(oldConf, newConf);
|
|
70
|
-
return buildMsg(VERB.PATCH, MESSAGE.CONFIGURATION, patches, id);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**************************************************************************
|
|
74
|
-
* build
|
|
75
|
-
*
|
|
76
|
-
* Public object exposing an API to build valid protocol messages.
|
|
77
|
-
* It is not the only way to build valid messages within the protocol.
|
|
78
|
-
*************************************************************************/
|
|
79
|
-
const build = {
|
|
80
|
-
CONFIGURATION: {
|
|
81
|
-
PATCH: buildPatchConfiguration,
|
|
82
|
-
READ: (id) => buildMsg(VERB.READ, MESSAGE.CONFIGURATION, {}, id),
|
|
83
|
-
NOTIFY: (config, id) => buildMsg(VERB.NOTIFY, MESSAGE.CONFIGURATION, config, id),
|
|
84
|
-
},
|
|
85
|
-
ERROR: {
|
|
86
|
-
NOTIFY: {
|
|
87
|
-
UNSUPPORTED_MESSAGE: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.UNSUPPORTED_MESSAGE, id),
|
|
88
|
-
UNSUPPORTED_VERB: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.UNSUPPORTED_VERB, id),
|
|
89
|
-
JSON_PARSE_ERROR: (id) => buildMsg(VERB.NOTIFY, MESSAGE.ERROR, ERROR.JSON_PARSE_ERROR, id),
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**************************************************************************
|
|
95
|
-
* Server
|
|
96
|
-
*
|
|
97
|
-
* The Control Server. Exposes a websocket control API.
|
|
98
|
-
* Used to hot-restart the SDK.
|
|
99
|
-
*
|
|
100
|
-
* logger - Logger- see SDK logger used elsewhere
|
|
101
|
-
* port - HTTP port to host on
|
|
102
|
-
* appConfig - The configuration for the entire application- supplied here as this class uses it to
|
|
103
|
-
* validate reconfiguration requests- it is not used for configuration here, however
|
|
104
|
-
* server - optional HTTP/S server on which to serve the websocket
|
|
105
|
-
*************************************************************************/
|
|
106
|
-
class Server extends ws.Server {
|
|
107
|
-
constructor({ logger, appConfig = {} }) {
|
|
108
|
-
super({ clientTracking: true, port: appConfig.control.port });
|
|
109
|
-
|
|
110
|
-
this._logger = logger;
|
|
111
|
-
this._port = appConfig.control.port;
|
|
112
|
-
this._appConfig = appConfig;
|
|
113
|
-
this._clientData = new Map();
|
|
114
|
-
|
|
115
|
-
this.on('error', err => {
|
|
116
|
-
this._logger.push({ err })
|
|
117
|
-
.log('Unhandled websocket error occurred. Shutting down.');
|
|
118
|
-
process.exit(1);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
this.on('connection', (socket, req) => {
|
|
122
|
-
const logger = this._logger.push({
|
|
123
|
-
url: req.url,
|
|
124
|
-
ip: 'localhost',
|
|
125
|
-
remoteAddress: req.socket.remoteAddress,
|
|
126
|
-
});
|
|
127
|
-
logger.log('Websocket connection received');
|
|
128
|
-
this._clientData.set(socket, { ip: req.connection.remoteAddress, logger });
|
|
129
|
-
|
|
130
|
-
socket.on('close', (code, reason) => {
|
|
131
|
-
logger.push({ code, reason }).log('Websocket connection closed');
|
|
132
|
-
this._clientData.delete(socket);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
socket.on('message', this._handle(socket, logger));
|
|
136
|
-
});
|
|
137
|
-
this._logger.push(this.address()).log('running on');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Close the server then wait for all the client sockets to close
|
|
141
|
-
async stop() {
|
|
142
|
-
const closing = new Promise(resolve => this.close(resolve));
|
|
143
|
-
for (const client of this.clients) {
|
|
144
|
-
client.terminate();
|
|
145
|
-
}
|
|
146
|
-
await closing;
|
|
147
|
-
this._logger.log('Control server shutdown complete');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
_handle(client, logger) {
|
|
151
|
-
return (data) => {
|
|
152
|
-
// TODO: json-schema validation of received message- should be pretty straight-forward
|
|
153
|
-
// and will allow better documentation of the API
|
|
154
|
-
let msg;
|
|
155
|
-
try {
|
|
156
|
-
msg = deserialise(data);
|
|
157
|
-
} catch (err) {
|
|
158
|
-
logger.push({ data }).log('Couldn\'t parse received message');
|
|
159
|
-
client.send(build.ERROR.NOTIFY.JSON_PARSE_ERROR());
|
|
160
|
-
}
|
|
161
|
-
logger.push({ msg }).log('Handling received message');
|
|
162
|
-
switch (msg.msg) {
|
|
163
|
-
case MESSAGE.CONFIGURATION:
|
|
164
|
-
switch (msg.verb) {
|
|
165
|
-
case VERB.READ:
|
|
166
|
-
(async () => {
|
|
167
|
-
const jwsCerts = await this.populateConfig();
|
|
168
|
-
client.send(build.CONFIGURATION.NOTIFY(jwsCerts, msg.id));
|
|
169
|
-
})();
|
|
170
|
-
break;
|
|
171
|
-
default:
|
|
172
|
-
client.send(build.ERROR.NOTIFY.UNSUPPORTED_VERB(msg.id));
|
|
173
|
-
break;
|
|
174
|
-
}
|
|
175
|
-
break;
|
|
176
|
-
default:
|
|
177
|
-
client.send(build.ERROR.NOTIFY.UNSUPPORTED_MESSAGE(msg.id));
|
|
178
|
-
break;
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async populateConfig(){
|
|
184
|
-
return this._appConfig;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Register this server instance to receive internal server messages
|
|
190
|
-
* from other modules.
|
|
191
|
-
*/
|
|
192
|
-
registerInternalEvents() {
|
|
193
|
-
ControlServerEventEmitter.on(INTERNAL_EVENTS.SERVER.BROADCAST_CONFIG_CHANGE, (params) => this.broadcastConfigChange(params));
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Broadcast configuration change to all connected clients.
|
|
198
|
-
*
|
|
199
|
-
* @param {object} params Updated configuration
|
|
200
|
-
*/
|
|
201
|
-
broadcastConfigChange(updatedConfig) {
|
|
202
|
-
const updateConfMsg = build.CONFIGURATION.PATCH({}, updatedConfig, generateSlug(4));
|
|
203
|
-
this.broadcast(updateConfMsg);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Broadcasts a protocol message to all connected clients.
|
|
208
|
-
*
|
|
209
|
-
* @param {string} msg
|
|
210
|
-
*/
|
|
211
|
-
broadcast(msg) {
|
|
212
|
-
this.clients.forEach((client) => {
|
|
213
|
-
if (client.readyState === ws.WebSocket.OPEN) {
|
|
214
|
-
client.send(msg);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
module.exports = {
|
|
222
|
-
Server,
|
|
223
|
-
build,
|
|
224
|
-
MESSAGE,
|
|
225
|
-
VERB,
|
|
226
|
-
ERROR,
|
|
227
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const ControlServer = require('~/ControlServer');
|
|
3
|
-
const { Logger } = require('@mojaloop/sdk-standard-components');
|
|
4
|
-
|
|
5
|
-
jest.mock('~/lib/cache');
|
|
6
|
-
|
|
7
|
-
// TODO:
|
|
8
|
-
// - diff against master to determine what else needs testing
|
|
9
|
-
// - especially look for assertions in the code
|
|
10
|
-
// - err.. grep the code for TODO
|
|
11
|
-
|
|
12
|
-
describe('ControlServer', () => {
|
|
13
|
-
it('exposes a valid message API', () => {
|
|
14
|
-
expect(Object.keys(ControlServer.build).sort()).toEqual(
|
|
15
|
-
Object.keys(ControlServer.MESSAGE).sort(),
|
|
16
|
-
'The API exposed by the builder object must contain as top-level keys all of the message types exposed in the MESSAGE constant. Check that ControlServer.MESSAGE has the same keys as ControlServer.build.'
|
|
17
|
-
);
|
|
18
|
-
Object.entries(ControlServer.build).forEach(([messageType, builders]) => {
|
|
19
|
-
expect(Object.keys(ControlServer.VERB)).toEqual(
|
|
20
|
-
expect.arrayContaining(Object.keys(builders)),
|
|
21
|
-
`For message type '${messageType}' every builder must correspond to a verb. Check that ControlServer.build.${messageType} has the same keys as ControlServer.VERB.`
|
|
22
|
-
);
|
|
23
|
-
});
|
|
24
|
-
expect(Object.keys(ControlServer.build.ERROR.NOTIFY).sort()).toEqual(
|
|
25
|
-
Object.keys(ControlServer.ERROR).sort(),
|
|
26
|
-
'ControlServer.ERROR.NOTIFY should contain the same keys as ControlServer.ERROR'
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('API', () => {
|
|
31
|
-
let server, logger, client;
|
|
32
|
-
const appConfig = { what: 'ever' };
|
|
33
|
-
const changedConfig = { ...appConfig, some: 'thing' };
|
|
34
|
-
|
|
35
|
-
beforeEach(async () => {
|
|
36
|
-
logger = new Logger.Logger({ stringify: () => '' });
|
|
37
|
-
server = new ControlServer.Server({ logger, appConfig });
|
|
38
|
-
client = await ControlServer.Client.Create({
|
|
39
|
-
address: 'localhost',
|
|
40
|
-
port: server.address().port,
|
|
41
|
-
logger
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
afterEach(async () => {
|
|
46
|
-
await server.stop();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('supplies config when requested', async () => {
|
|
50
|
-
await client.send(ControlServer.build.CONFIGURATION.READ());
|
|
51
|
-
const response = await client.receive();
|
|
52
|
-
expect(response).toEqual({
|
|
53
|
-
...JSON.parse(ControlServer.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('emits new config when received', async () => {
|
|
58
|
-
const newConfigEvent = new Promise(
|
|
59
|
-
(resolve) => server.on(ControlServer.EVENT.RECONFIGURE, resolve)
|
|
60
|
-
);
|
|
61
|
-
await client.send(ControlServer.build.CONFIGURATION.PATCH(appConfig, changedConfig));
|
|
62
|
-
const newConfEventData = await newConfigEvent;
|
|
63
|
-
expect(newConfEventData).toEqual(changedConfig);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|