@mojaloop/sdk-scheme-adapter 12.0.2 → 12.2.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/.env.example +13 -0
- package/CHANGELOG.md +29 -0
- package/audit-resolve.json +20 -0
- package/package.json +5 -1
- package/src/ControlAgent/index.js +221 -0
- package/src/ControlServer/handlers.js +63 -0
- package/src/ControlServer/index.js +294 -0
- package/src/InboundServer/index.js +33 -1
- package/src/OutboundServer/index.js +15 -0
- package/src/TestServer/index.js +31 -0
- package/src/config.js +6 -0
- package/src/lib/model/OutboundBulkQuotesModel.js +3 -3
- package/src/lib/model/OutboundBulkTransfersModel.js +1 -1
- package/src/lib/model/OutboundRequestToPayModel.js +1 -1
- package/src/lib/model/OutboundRequestToPayTransferModel.js +9 -9
- package/src/lib/model/OutboundTransfersModel.js +1 -1
- package/src/lib/model/lib/shared.js +6 -5
- package/test/config/integration.env +9 -0
- package/test/unit/ControlClient.test.js +113 -0
- package/test/unit/ControlServer/events.js +41 -0
- package/test/unit/ControlServer/index.js +231 -0
- package/test/unit/ControlServer.test.js +126 -0
- package/test/unit/config.test.js +1 -0
- package/test/unit/index.test.js +1 -0
- package/test/unit/lib/model/OutboundTransfersModel.test.js +33 -0
- package/junit.xml +0 -515
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
const ControlAgent = require('~/ControlAgent');
|
|
3
|
+
const TestControlServer = require('./ControlServer');
|
|
4
|
+
const InboundServer = require('~/InboundServer');
|
|
5
|
+
const OutboundServer = require('~/OutboundServer');
|
|
6
|
+
const TestServer = require('~/TestServer');
|
|
7
|
+
const defaultConfig = require('./data/defaultConfig.json');
|
|
8
|
+
const { Logger } = require('@mojaloop/sdk-standard-components');
|
|
9
|
+
|
|
10
|
+
jest.mock('~/lib/cache');
|
|
11
|
+
const Cache = require('~/lib/cache');
|
|
12
|
+
|
|
13
|
+
// TODO:
|
|
14
|
+
// - diff against master to determine what else needs testing
|
|
15
|
+
// - especially look for assertions in the code
|
|
16
|
+
// - err.. grep the code for TODO
|
|
17
|
+
|
|
18
|
+
describe('ControlAgent', () => {
|
|
19
|
+
it('exposes a valid message API', () => {
|
|
20
|
+
expect(Object.keys(ControlAgent.build).sort()).toEqual(
|
|
21
|
+
Object.keys(ControlAgent.MESSAGE).sort(),
|
|
22
|
+
'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.'
|
|
23
|
+
);
|
|
24
|
+
Object.entries(ControlAgent.build).forEach(([messageType, builders]) => {
|
|
25
|
+
expect(Object.keys(ControlAgent.VERB)).toEqual(
|
|
26
|
+
expect.arrayContaining(Object.keys(builders)),
|
|
27
|
+
`For message type '${messageType}' every builder must correspond to a verb. Check that ControlAgent.build.${messageType} has the same keys as ControlAgent.VERB.`
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
expect(Object.keys(ControlAgent.build.ERROR.NOTIFY).sort()).toEqual(
|
|
31
|
+
Object.keys(ControlAgent.ERROR).sort(),
|
|
32
|
+
'ControlAgent.ERROR.NOTIFY should contain the same keys as ControlAgent.ERROR'
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('API', () => {
|
|
37
|
+
let server, logger, client;
|
|
38
|
+
const appConfig = { control: { port: 4005 }, what: 'ever' };
|
|
39
|
+
const changedConfig = { ...appConfig, some: 'thing' };
|
|
40
|
+
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
logger = new Logger.Logger({ stringify: () => '' });
|
|
43
|
+
server = new TestControlServer.Server({ logger, appConfig });
|
|
44
|
+
client = await ControlAgent.Client.Create({
|
|
45
|
+
address: 'localhost',
|
|
46
|
+
port: server.address().port,
|
|
47
|
+
logger,
|
|
48
|
+
appConfig
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(async () => {
|
|
53
|
+
await client.stop();
|
|
54
|
+
await server.stop();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('receives config when requested', async () => {
|
|
58
|
+
await client.send(ControlAgent.build.CONFIGURATION.READ());
|
|
59
|
+
const response = await client.receive();
|
|
60
|
+
expect(response).toEqual({
|
|
61
|
+
...JSON.parse(ControlAgent.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('emits new config when received', async () => {
|
|
66
|
+
const newConfigEvent = new Promise(
|
|
67
|
+
(resolve) => client.on(ControlAgent.EVENT.RECONFIGURE, resolve)
|
|
68
|
+
);
|
|
69
|
+
await server.broadcastConfigChange(changedConfig);
|
|
70
|
+
const newConfEventData = await newConfigEvent;
|
|
71
|
+
expect(newConfEventData).toEqual(changedConfig);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('Server reconfigure methods', () => {
|
|
77
|
+
let conf, logger, cache;
|
|
78
|
+
|
|
79
|
+
const isPromise = (o) => Promise.resolve(o) === o;
|
|
80
|
+
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
conf = JSON.parse(JSON.stringify(defaultConfig));
|
|
83
|
+
logger = new Logger.Logger({ stringify: () => '' });
|
|
84
|
+
cache = new Cache({ ...conf.cacheConfig, logger: logger.push({ component: 'cache' }) });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('InboundServer reconfigure method returns sync function', async () => {
|
|
88
|
+
const server = new InboundServer(conf, logger, cache);
|
|
89
|
+
const res = await server.reconfigure(conf, logger, cache);
|
|
90
|
+
expect(isPromise(res)).toEqual(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('OutboundServer reconfigure method returns sync function', async () => {
|
|
94
|
+
const server = new OutboundServer(conf, logger, cache);
|
|
95
|
+
const res = await server.reconfigure(conf, logger, cache);
|
|
96
|
+
expect(isPromise(res)).toEqual(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('TestServer reconfigure method returns sync function', async () => {
|
|
100
|
+
const server = new TestServer({ logger, cache });
|
|
101
|
+
const res = await server.reconfigure({ logger, cache });
|
|
102
|
+
expect(isPromise(res)).toEqual(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('ControlClient reconfigure method returns sync function', async () => {
|
|
106
|
+
const server = new TestControlServer.Server({ logger, appConfig: { ...conf, control: { port: 4005 }}});
|
|
107
|
+
const client = await ControlAgent.Client.Create({ port: 4005, logger, appConfig: {} });
|
|
108
|
+
const res = await client.reconfigure({ logger, port: 4005, appConfig: {} });
|
|
109
|
+
expect(isPromise(res)).toEqual(false);
|
|
110
|
+
await client.close();
|
|
111
|
+
await server.close();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
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 randomPhrase = require('~/lib/randomphrase');
|
|
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 = randomPhrase()) => 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
|
+
await new Promise(this.close.bind(this));
|
|
143
|
+
this._logger.log('Control server shutdown complete');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_handle(client, logger) {
|
|
147
|
+
return (data) => {
|
|
148
|
+
// TODO: json-schema validation of received message- should be pretty straight-forward
|
|
149
|
+
// and will allow better documentation of the API
|
|
150
|
+
let msg;
|
|
151
|
+
try {
|
|
152
|
+
msg = deserialise(data);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
logger.push({ data }).log('Couldn\'t parse received message');
|
|
155
|
+
client.send(build.ERROR.NOTIFY.JSON_PARSE_ERROR());
|
|
156
|
+
}
|
|
157
|
+
logger.push({ msg }).log('Handling received message');
|
|
158
|
+
switch (msg.msg) {
|
|
159
|
+
case MESSAGE.CONFIGURATION:
|
|
160
|
+
switch (msg.verb) {
|
|
161
|
+
case VERB.READ:
|
|
162
|
+
(async () => {
|
|
163
|
+
const jwsCerts = await this.populateConfig();
|
|
164
|
+
client.send(build.CONFIGURATION.NOTIFY(jwsCerts, msg.id));
|
|
165
|
+
})();
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
client.send(build.ERROR.NOTIFY.UNSUPPORTED_VERB(msg.id));
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
client.send(build.ERROR.NOTIFY.UNSUPPORTED_MESSAGE(msg.id));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async populateConfig(){
|
|
180
|
+
return this._appConfig;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Register this server instance to receive internal server messages
|
|
186
|
+
* from other modules.
|
|
187
|
+
*/
|
|
188
|
+
registerInternalEvents() {
|
|
189
|
+
ControlServerEventEmitter.on(INTERNAL_EVENTS.SERVER.BROADCAST_CONFIG_CHANGE, (params) => {
|
|
190
|
+
this.broadcastConfigChange(params);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Broadcast configuration change to all connected clients.
|
|
196
|
+
*
|
|
197
|
+
* @param {object} params Updated configuration
|
|
198
|
+
*/
|
|
199
|
+
async broadcastConfigChange(updatedConfig) {
|
|
200
|
+
const updateConfMsg = build.CONFIGURATION.PATCH({}, updatedConfig, randomPhrase());
|
|
201
|
+
const errorLogger = (socket, message) => (err) =>
|
|
202
|
+
this._logger
|
|
203
|
+
.push({ message, ip: this._clientData.get(socket).ip, err })
|
|
204
|
+
.log('Error sending JWS keys notification to client');
|
|
205
|
+
return await this.broadcast(updateConfMsg, errorLogger);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Broadcasts a protocol message to all connected clients.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} msg
|
|
212
|
+
* @param {object} errorLogger
|
|
213
|
+
*/
|
|
214
|
+
async broadcast(msg, errorLogger) {
|
|
215
|
+
const sendToAllClients = (msg, errorLogger) => Promise.all(
|
|
216
|
+
[...this.clients.values()].map((socket) =>
|
|
217
|
+
(new Promise((resolve) => socket.send(msg, resolve))).catch(errorLogger(socket, msg))
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
return await sendToAllClients(msg, errorLogger);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
Server,
|
|
227
|
+
build,
|
|
228
|
+
MESSAGE,
|
|
229
|
+
VERB,
|
|
230
|
+
ERROR,
|
|
231
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
const ControlServer = require('~/ControlServer');
|
|
3
|
+
const InboundServer = require('~/InboundServer');
|
|
4
|
+
const OutboundServer = require('~/OutboundServer');
|
|
5
|
+
const TestServer = require('~/TestServer');
|
|
6
|
+
const defaultConfig = require('./data/defaultConfig.json');
|
|
7
|
+
const { Logger } = require('@mojaloop/sdk-standard-components');
|
|
8
|
+
|
|
9
|
+
jest.mock('~/lib/cache');
|
|
10
|
+
const Cache = require('~/lib/cache');
|
|
11
|
+
|
|
12
|
+
// TODO:
|
|
13
|
+
// - diff against master to determine what else needs testing
|
|
14
|
+
// - especially look for assertions in the code
|
|
15
|
+
// - err.. grep the code for TODO
|
|
16
|
+
|
|
17
|
+
describe('ControlServer', () => {
|
|
18
|
+
it('exposes a valid message API', () => {
|
|
19
|
+
expect(Object.keys(ControlServer.build).sort()).toEqual(
|
|
20
|
+
Object.keys(ControlServer.MESSAGE).sort(),
|
|
21
|
+
'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.'
|
|
22
|
+
);
|
|
23
|
+
Object.entries(ControlServer.build).forEach(([messageType, builders]) => {
|
|
24
|
+
expect(Object.keys(ControlServer.VERB)).toEqual(
|
|
25
|
+
expect.arrayContaining(Object.keys(builders)),
|
|
26
|
+
`For message type '${messageType}' every builder must correspond to a verb. Check that ControlServer.build.${messageType} has the same keys as ControlServer.VERB.`
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
expect(Object.keys(ControlServer.build.ERROR.NOTIFY).sort()).toEqual(
|
|
30
|
+
Object.keys(ControlServer.ERROR).sort(),
|
|
31
|
+
'ControlServer.ERROR.NOTIFY should contain the same keys as ControlServer.ERROR'
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('API', () => {
|
|
36
|
+
let server, logger, client;
|
|
37
|
+
const appConfig = { what: 'ever' };
|
|
38
|
+
const changedConfig = { ...appConfig, some: 'thing' };
|
|
39
|
+
|
|
40
|
+
beforeEach(async () => {
|
|
41
|
+
logger = new Logger.Logger({ stringify: () => '' });
|
|
42
|
+
server = new ControlServer.Server({ logger, appConfig });
|
|
43
|
+
client = await ControlServer.Client.Create({
|
|
44
|
+
address: 'localhost',
|
|
45
|
+
port: server.address().port,
|
|
46
|
+
logger
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(async () => {
|
|
51
|
+
await server.stop();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('supplies config when requested', async () => {
|
|
55
|
+
await client.send(ControlServer.build.CONFIGURATION.READ());
|
|
56
|
+
const response = await client.receive();
|
|
57
|
+
expect(response).toEqual({
|
|
58
|
+
...JSON.parse(ControlServer.build.CONFIGURATION.NOTIFY(appConfig, response.id)),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('emits new config when received', async () => {
|
|
63
|
+
const newConfigEvent = new Promise(
|
|
64
|
+
(resolve) => server.on(ControlServer.EVENT.RECONFIGURE, resolve)
|
|
65
|
+
);
|
|
66
|
+
await client.send(ControlServer.build.CONFIGURATION.PATCH(appConfig, changedConfig));
|
|
67
|
+
const newConfEventData = await newConfigEvent;
|
|
68
|
+
expect(newConfEventData).toEqual(changedConfig);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('sends new config to clients when instructed', async () => {
|
|
72
|
+
const client2 = await ControlServer.Client.Create({
|
|
73
|
+
address: 'localhost',
|
|
74
|
+
port: server.address().port,
|
|
75
|
+
logger
|
|
76
|
+
});
|
|
77
|
+
const changedConfig = { ...appConfig, some: 'thing' };
|
|
78
|
+
await client.send(ControlServer.build.CONFIGURATION.PATCH(appConfig, changedConfig));
|
|
79
|
+
const restart = server.reconfigure({ appConfig: changedConfig });
|
|
80
|
+
restart();
|
|
81
|
+
await server.notifyClientsOfCurrentConfig();
|
|
82
|
+
const [notification, notification2] =
|
|
83
|
+
await Promise.all([client.receive(), client2.receive()]);
|
|
84
|
+
const expected = ControlServer.build.CONFIGURATION.NOTIFY(changedConfig, notification.id);
|
|
85
|
+
expect(JSON.stringify(notification)).toEqual(expected);
|
|
86
|
+
expect(JSON.stringify(notification2)).toEqual(expected);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Server reconfigure methods', () => {
|
|
92
|
+
let conf, logger, cache;
|
|
93
|
+
|
|
94
|
+
const isPromise = (o) => Promise.resolve(o) === o;
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
conf = JSON.parse(JSON.stringify(defaultConfig));
|
|
98
|
+
logger = new Logger.Logger({ stringify: () => '' });
|
|
99
|
+
cache = new Cache({ ...conf.cacheConfig, logger: logger.push({ component: 'cache' }) });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('InboundServer reconfigure method returns sync function', async () => {
|
|
103
|
+
const server = new InboundServer(conf, logger, cache);
|
|
104
|
+
const res = await server.reconfigure(conf, logger, cache);
|
|
105
|
+
expect(isPromise(res)).toEqual(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('OutboundServer reconfigure method returns sync function', async () => {
|
|
109
|
+
const server = new OutboundServer(conf, logger, cache);
|
|
110
|
+
const res = await server.reconfigure(conf, logger, cache);
|
|
111
|
+
expect(isPromise(res)).toEqual(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('TestServer reconfigure method returns sync function', async () => {
|
|
115
|
+
const server = new TestServer({ logger, cache });
|
|
116
|
+
const res = await server.reconfigure({ logger, cache });
|
|
117
|
+
expect(isPromise(res)).toEqual(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('ControlServer reconfigure method returns sync function', async () => {
|
|
121
|
+
const server = new ControlServer.Server({ logger, appConfig: {} });
|
|
122
|
+
const res = await server.reconfigure({ logger, appConfig: {} });
|
|
123
|
+
expect(isPromise(res)).toEqual(false);
|
|
124
|
+
await server.close();
|
|
125
|
+
});
|
|
126
|
+
});
|
package/test/unit/config.test.js
CHANGED
|
@@ -28,6 +28,7 @@ describe('config', () => {
|
|
|
28
28
|
process.env.BACKEND_ENDPOINT = '172.17.0.5:4000';
|
|
29
29
|
process.env.CACHE_HOST = '172.17.0.2';
|
|
30
30
|
process.env.CACHE_PORT = '6379';
|
|
31
|
+
process.env.MGMT_API_WS_URL = '0.0.0.0';
|
|
31
32
|
certDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jest-'));
|
|
32
33
|
});
|
|
33
34
|
|
package/test/unit/index.test.js
CHANGED
|
@@ -21,6 +21,7 @@ process.env.PEER_ENDPOINT = '172.17.0.3:4000';
|
|
|
21
21
|
process.env.BACKEND_ENDPOINT = '172.17.0.5:4000';
|
|
22
22
|
process.env.CACHE_HOST = '172.17.0.2';
|
|
23
23
|
process.env.CACHE_PORT = '6379';
|
|
24
|
+
process.env.MGMT_API_WS_URL = '0.0.0.0';
|
|
24
25
|
|
|
25
26
|
const index = require('~/index.js');
|
|
26
27
|
|
|
@@ -354,6 +354,39 @@ describe('outboundModel', () => {
|
|
|
354
354
|
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
355
355
|
});
|
|
356
356
|
|
|
357
|
+
test('uses payee party fspid as source header when supplied - resolving payee', async () => {
|
|
358
|
+
config.autoAcceptParty = false;
|
|
359
|
+
|
|
360
|
+
const model = new Model({
|
|
361
|
+
cache,
|
|
362
|
+
logger,
|
|
363
|
+
...config,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
let req = JSON.parse(JSON.stringify(transferRequest));
|
|
367
|
+
const testFspId = 'TESTDESTFSPID';
|
|
368
|
+
req.to.fspId = testFspId;
|
|
369
|
+
|
|
370
|
+
await model.initialize(req);
|
|
371
|
+
|
|
372
|
+
expect(StateMachine.__instance.state).toBe('start');
|
|
373
|
+
|
|
374
|
+
// start the model running
|
|
375
|
+
const resultPromise = model.run();
|
|
376
|
+
|
|
377
|
+
// now we started the model running we simulate a callback with the resolved party
|
|
378
|
+
emitPartyCacheMessage(cache, payeeParty);
|
|
379
|
+
|
|
380
|
+
// wait for the model to reach a terminal state
|
|
381
|
+
const result = await resultPromise;
|
|
382
|
+
|
|
383
|
+
// check we stopped at payeeResolved state
|
|
384
|
+
expect(result.currentState).toBe('WAITING_FOR_PARTY_ACCEPTANCE');
|
|
385
|
+
expect(StateMachine.__instance.state).toBe('payeeResolved');
|
|
386
|
+
|
|
387
|
+
// check getParties mojaloop requests method was called with the correct arguments
|
|
388
|
+
expect(MojaloopRequests.__getParties).toHaveBeenCalledWith(req.to.idType, req.to.idValue, req.to.idSubValue, testFspId);
|
|
389
|
+
});
|
|
357
390
|
|
|
358
391
|
test('halts after resolving payee, resumes and then halts after receiving quote response when AUTO_ACCEPT_PARTY is false and AUTO_ACCEPT_QUOTES is false', async () => {
|
|
359
392
|
config.autoAcceptParty = false;
|