orator-http-proxy 1.0.1 → 1.0.4
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/CONTRIBUTING.md +50 -0
- package/README.md +109 -3
- package/docs/.nojekyll +0 -0
- package/docs/README.md +67 -0
- package/docs/_sidebar.md +8 -0
- package/docs/configuration.md +87 -0
- package/docs/cover.md +11 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/getting-started.md +106 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +41 -0
- package/docs/retold-keyword-index.json +19 -0
- package/package.json +6 -6
- package/source/Orator-HTTP-Proxy.js +1 -1
- package/test/Orator-HTTP-Proxy_tests.js +1003 -0
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Orator HTTP Proxy
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const Chai = require("chai");
|
|
10
|
+
const Expect = Chai.expect;
|
|
11
|
+
|
|
12
|
+
const libFable = require('fable');
|
|
13
|
+
const libOrator = require('orator');
|
|
14
|
+
const libOratorHTTPProxy = require('../source/Orator-HTTP-Proxy.js');
|
|
15
|
+
|
|
16
|
+
const defaultFableSettings = (
|
|
17
|
+
{
|
|
18
|
+
Product: 'OratorHTTPProxy-Tests',
|
|
19
|
+
ProductVersion: '0.0.0',
|
|
20
|
+
APIServerPort: 0
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper that creates a Fable + Orator + Proxy harness, starts the service, then calls back.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} pFableSettings - Fable settings to merge with defaults.
|
|
27
|
+
* @param {object} pProxyOptions - Options for the proxy instance.
|
|
28
|
+
* @param {Function} fCallback - Called with the harness object after the service starts.
|
|
29
|
+
*/
|
|
30
|
+
function createStartedHarness(pFableSettings, pProxyOptions, fCallback)
|
|
31
|
+
{
|
|
32
|
+
let tmpFableSettings = Object.assign({}, defaultFableSettings, pFableSettings || {});
|
|
33
|
+
let tmpFable = new libFable(tmpFableSettings);
|
|
34
|
+
|
|
35
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
36
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
37
|
+
|
|
38
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
39
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', pProxyOptions || {});
|
|
40
|
+
|
|
41
|
+
let tmpResult = (
|
|
42
|
+
{
|
|
43
|
+
fable: tmpFable,
|
|
44
|
+
orator: tmpOrator,
|
|
45
|
+
proxy: tmpProxy
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
tmpOrator.startService(
|
|
49
|
+
() =>
|
|
50
|
+
{
|
|
51
|
+
return fCallback(tmpResult);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
suite
|
|
56
|
+
(
|
|
57
|
+
'Orator HTTP Proxy',
|
|
58
|
+
() =>
|
|
59
|
+
{
|
|
60
|
+
suite
|
|
61
|
+
(
|
|
62
|
+
'Object Sanity',
|
|
63
|
+
() =>
|
|
64
|
+
{
|
|
65
|
+
test
|
|
66
|
+
(
|
|
67
|
+
'the class should initialize itself into a happy little object',
|
|
68
|
+
(fDone) =>
|
|
69
|
+
{
|
|
70
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
71
|
+
|
|
72
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
73
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
74
|
+
|
|
75
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
76
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
77
|
+
|
|
78
|
+
Expect(tmpProxy).to.be.an('object', 'OratorHTTPProxy should initialize as an object.');
|
|
79
|
+
Expect(tmpProxy).to.have.a.property('connectProxyRoutes');
|
|
80
|
+
Expect(tmpProxy.connectProxyRoutes).to.be.a('function');
|
|
81
|
+
Expect(tmpProxy).to.have.a.property('httpProxyServer');
|
|
82
|
+
Expect(tmpProxy.httpProxyServer).to.be.an('object');
|
|
83
|
+
|
|
84
|
+
return fDone();
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
test
|
|
89
|
+
(
|
|
90
|
+
'the proxy should have default configuration values when no options are provided',
|
|
91
|
+
(fDone) =>
|
|
92
|
+
{
|
|
93
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
94
|
+
|
|
95
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
96
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
97
|
+
|
|
98
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
99
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
100
|
+
|
|
101
|
+
// Default log level should be 0
|
|
102
|
+
Expect(tmpProxy.LogLevel).to.equal(0);
|
|
103
|
+
|
|
104
|
+
// Default proxy destination URL should be loopback
|
|
105
|
+
Expect(tmpProxy.proxyDestinationURL).to.equal('http://127.0.0.1/');
|
|
106
|
+
|
|
107
|
+
// Default request prefix list should be the meadow default
|
|
108
|
+
Expect(tmpProxy.requestPrefixList).to.be.an('array');
|
|
109
|
+
Expect(tmpProxy.requestPrefixList).to.deep.equal(['/1.0/*']);
|
|
110
|
+
|
|
111
|
+
return fDone();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
test
|
|
116
|
+
(
|
|
117
|
+
'the proxy should accept DestinationURL and LogLevel via options',
|
|
118
|
+
(fDone) =>
|
|
119
|
+
{
|
|
120
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
121
|
+
|
|
122
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
123
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
124
|
+
|
|
125
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
126
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
127
|
+
{
|
|
128
|
+
LogLevel: 3,
|
|
129
|
+
DestinationURL: 'http://api.example.com:3000/'
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
Expect(tmpProxy.LogLevel).to.equal(3);
|
|
133
|
+
Expect(tmpProxy.proxyDestinationURL).to.equal('http://api.example.com:3000/');
|
|
134
|
+
|
|
135
|
+
return fDone();
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
test
|
|
140
|
+
(
|
|
141
|
+
'the proxy should accept configuration via fable settings fallback',
|
|
142
|
+
(fDone) =>
|
|
143
|
+
{
|
|
144
|
+
let tmpFableSettings = Object.assign({}, defaultFableSettings,
|
|
145
|
+
{
|
|
146
|
+
OratorHTTPProxyLogLevel: 2,
|
|
147
|
+
OratorHTTPProxyDestinationURL: 'http://backend.local:8080/',
|
|
148
|
+
OratorHTTPProxyRequestPrefixList: ['/api/v1/*', '/api/v2/*']
|
|
149
|
+
});
|
|
150
|
+
let tmpFable = new libFable(tmpFableSettings);
|
|
151
|
+
|
|
152
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
153
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
154
|
+
|
|
155
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
156
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
157
|
+
|
|
158
|
+
Expect(tmpProxy.LogLevel).to.equal(2);
|
|
159
|
+
Expect(tmpProxy.proxyDestinationURL).to.equal('http://backend.local:8080/');
|
|
160
|
+
Expect(tmpProxy.requestPrefixList).to.be.an('array');
|
|
161
|
+
Expect(tmpProxy.requestPrefixList).to.deep.equal(['/api/v1/*', '/api/v2/*']);
|
|
162
|
+
|
|
163
|
+
return fDone();
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
test
|
|
168
|
+
(
|
|
169
|
+
'options should take precedence over fable settings',
|
|
170
|
+
(fDone) =>
|
|
171
|
+
{
|
|
172
|
+
let tmpFableSettings = Object.assign({}, defaultFableSettings,
|
|
173
|
+
{
|
|
174
|
+
OratorHTTPProxyLogLevel: 2,
|
|
175
|
+
OratorHTTPProxyDestinationURL: 'http://fallback.local/'
|
|
176
|
+
});
|
|
177
|
+
let tmpFable = new libFable(tmpFableSettings);
|
|
178
|
+
|
|
179
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
180
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
181
|
+
|
|
182
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
183
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
184
|
+
{
|
|
185
|
+
LogLevel: 5,
|
|
186
|
+
DestinationURL: 'http://primary.local/'
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Options values should win over fable settings
|
|
190
|
+
Expect(tmpProxy.LogLevel).to.equal(5);
|
|
191
|
+
Expect(tmpProxy.proxyDestinationURL).to.equal('http://primary.local/');
|
|
192
|
+
|
|
193
|
+
return fDone();
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
test
|
|
198
|
+
(
|
|
199
|
+
'the httpProxyServer should be created as a real http-proxy instance',
|
|
200
|
+
(fDone) =>
|
|
201
|
+
{
|
|
202
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
203
|
+
|
|
204
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
205
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
206
|
+
|
|
207
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
208
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
209
|
+
|
|
210
|
+
Expect(tmpProxy.httpProxyServer).to.be.an('object');
|
|
211
|
+
Expect(tmpProxy.httpProxyServer.web).to.be.a('function');
|
|
212
|
+
Expect(tmpProxy.httpProxyServer.ws).to.be.a('function');
|
|
213
|
+
|
|
214
|
+
return fDone();
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
suite
|
|
221
|
+
(
|
|
222
|
+
'Proxy Route Connection',
|
|
223
|
+
() =>
|
|
224
|
+
{
|
|
225
|
+
test
|
|
226
|
+
(
|
|
227
|
+
'connectProxyRoutes should register routes on the orator service server',
|
|
228
|
+
(fDone) =>
|
|
229
|
+
{
|
|
230
|
+
createStartedHarness(null,
|
|
231
|
+
{
|
|
232
|
+
DestinationURL: 'http://localhost:9999/'
|
|
233
|
+
},
|
|
234
|
+
(pHarness) =>
|
|
235
|
+
{
|
|
236
|
+
pHarness.proxy.connectProxyRoutes(['/api/v1/*']);
|
|
237
|
+
|
|
238
|
+
// Verify the route is registered by checking the IPC router can find it
|
|
239
|
+
let tmpHandler = pHarness.orator.serviceServer.router.find('GET', '/api/v1/users');
|
|
240
|
+
Expect(tmpHandler).to.be.an('object');
|
|
241
|
+
Expect(tmpHandler).to.have.a.property('handler');
|
|
242
|
+
Expect(tmpHandler.handler).to.be.a('function');
|
|
243
|
+
|
|
244
|
+
return fDone();
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
test
|
|
250
|
+
(
|
|
251
|
+
'connectProxyRoutes should register routes for GET, PUT, POST and DELETE',
|
|
252
|
+
(fDone) =>
|
|
253
|
+
{
|
|
254
|
+
createStartedHarness(null,
|
|
255
|
+
{
|
|
256
|
+
DestinationURL: 'http://localhost:9999/'
|
|
257
|
+
},
|
|
258
|
+
(pHarness) =>
|
|
259
|
+
{
|
|
260
|
+
pHarness.proxy.connectProxyRoutes(['/proxy/*']);
|
|
261
|
+
|
|
262
|
+
let tmpExpectedMethods = ['GET', 'PUT', 'POST', 'DELETE'];
|
|
263
|
+
|
|
264
|
+
tmpExpectedMethods.forEach(
|
|
265
|
+
(pMethod) =>
|
|
266
|
+
{
|
|
267
|
+
let tmpHandler = pHarness.orator.serviceServer.router.find(pMethod, '/proxy/resource');
|
|
268
|
+
Expect(tmpHandler).to.be.an('object', `Route for ${pMethod} should be registered.`);
|
|
269
|
+
Expect(tmpHandler).to.have.a.property('handler');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return fDone();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
test
|
|
278
|
+
(
|
|
279
|
+
'connectProxyRoutes should register multiple prefixes at once',
|
|
280
|
+
(fDone) =>
|
|
281
|
+
{
|
|
282
|
+
createStartedHarness(null,
|
|
283
|
+
{
|
|
284
|
+
DestinationURL: 'http://localhost:9999/'
|
|
285
|
+
},
|
|
286
|
+
(pHarness) =>
|
|
287
|
+
{
|
|
288
|
+
pHarness.proxy.connectProxyRoutes(['/api/v1/*', '/api/v2/*', '/legacy/*']);
|
|
289
|
+
|
|
290
|
+
// Verify all three prefixes are registered
|
|
291
|
+
let tmpPrefixes = ['/api/v1/users', '/api/v2/items', '/legacy/data'];
|
|
292
|
+
|
|
293
|
+
tmpPrefixes.forEach(
|
|
294
|
+
(pPrefix) =>
|
|
295
|
+
{
|
|
296
|
+
let tmpHandler = pHarness.orator.serviceServer.router.find('GET', pPrefix);
|
|
297
|
+
Expect(tmpHandler).to.be.an('object', `Route for ${pPrefix} should be registered.`);
|
|
298
|
+
Expect(tmpHandler).to.have.a.property('handler');
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return fDone();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
test
|
|
307
|
+
(
|
|
308
|
+
'connectProxyRoutes should accept a function that returns a prefix list',
|
|
309
|
+
(fDone) =>
|
|
310
|
+
{
|
|
311
|
+
createStartedHarness(null,
|
|
312
|
+
{
|
|
313
|
+
DestinationURL: 'http://localhost:9999/'
|
|
314
|
+
},
|
|
315
|
+
(pHarness) =>
|
|
316
|
+
{
|
|
317
|
+
let tmpPrefixFunction = () =>
|
|
318
|
+
{
|
|
319
|
+
return ['/dynamic/v1/*', '/dynamic/v2/*'];
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
pHarness.proxy.connectProxyRoutes(tmpPrefixFunction);
|
|
323
|
+
|
|
324
|
+
// Verify the dynamic routes are registered
|
|
325
|
+
let tmpHandler1 = pHarness.orator.serviceServer.router.find('GET', '/dynamic/v1/test');
|
|
326
|
+
Expect(tmpHandler1).to.be.an('object');
|
|
327
|
+
Expect(tmpHandler1).to.have.a.property('handler');
|
|
328
|
+
|
|
329
|
+
let tmpHandler2 = pHarness.orator.serviceServer.router.find('GET', '/dynamic/v2/test');
|
|
330
|
+
Expect(tmpHandler2).to.be.an('object');
|
|
331
|
+
Expect(tmpHandler2).to.have.a.property('handler');
|
|
332
|
+
|
|
333
|
+
return fDone();
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
test
|
|
339
|
+
(
|
|
340
|
+
'connectProxyRoutes should fall back to default prefix list when no arguments are passed',
|
|
341
|
+
(fDone) =>
|
|
342
|
+
{
|
|
343
|
+
createStartedHarness(null,
|
|
344
|
+
{
|
|
345
|
+
DestinationURL: 'http://localhost:9999/'
|
|
346
|
+
},
|
|
347
|
+
(pHarness) =>
|
|
348
|
+
{
|
|
349
|
+
// Call with no arguments -- should use the constructor defaults ['/1.0/*']
|
|
350
|
+
pHarness.proxy.connectProxyRoutes();
|
|
351
|
+
|
|
352
|
+
let tmpHandler = pHarness.orator.serviceServer.router.find('GET', '/1.0/SomeEntity');
|
|
353
|
+
Expect(tmpHandler).to.be.an('object');
|
|
354
|
+
Expect(tmpHandler).to.have.a.property('handler');
|
|
355
|
+
|
|
356
|
+
return fDone();
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
test
|
|
362
|
+
(
|
|
363
|
+
'connectProxyRoutes should register routes for a custom proxy URL parameter',
|
|
364
|
+
(fDone) =>
|
|
365
|
+
{
|
|
366
|
+
createStartedHarness(null,
|
|
367
|
+
{
|
|
368
|
+
DestinationURL: 'http://default-backend.local/'
|
|
369
|
+
},
|
|
370
|
+
(pHarness) =>
|
|
371
|
+
{
|
|
372
|
+
// Pass a custom proxy URL as the second parameter
|
|
373
|
+
pHarness.proxy.connectProxyRoutes(['/custom/*'], 'http://custom-backend.local:5000/');
|
|
374
|
+
|
|
375
|
+
// Verify the route is registered
|
|
376
|
+
let tmpHandler = pHarness.orator.serviceServer.router.find('GET', '/custom/resource');
|
|
377
|
+
Expect(tmpHandler).to.be.an('object');
|
|
378
|
+
Expect(tmpHandler).to.have.a.property('handler');
|
|
379
|
+
|
|
380
|
+
return fDone();
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
test
|
|
386
|
+
(
|
|
387
|
+
'connectProxyRoutes should be callable multiple times to add routes incrementally',
|
|
388
|
+
(fDone) =>
|
|
389
|
+
{
|
|
390
|
+
createStartedHarness(null,
|
|
391
|
+
{
|
|
392
|
+
DestinationURL: 'http://localhost:9999/'
|
|
393
|
+
},
|
|
394
|
+
(pHarness) =>
|
|
395
|
+
{
|
|
396
|
+
// Register routes in two separate calls
|
|
397
|
+
pHarness.proxy.connectProxyRoutes(['/first-batch/*']);
|
|
398
|
+
pHarness.proxy.connectProxyRoutes(['/second-batch/*']);
|
|
399
|
+
|
|
400
|
+
let tmpHandler1 = pHarness.orator.serviceServer.router.find('GET', '/first-batch/resource');
|
|
401
|
+
Expect(tmpHandler1).to.be.an('object');
|
|
402
|
+
Expect(tmpHandler1).to.have.a.property('handler');
|
|
403
|
+
|
|
404
|
+
let tmpHandler2 = pHarness.orator.serviceServer.router.find('GET', '/second-batch/resource');
|
|
405
|
+
Expect(tmpHandler2).to.be.an('object');
|
|
406
|
+
Expect(tmpHandler2).to.have.a.property('handler');
|
|
407
|
+
|
|
408
|
+
return fDone();
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
suite
|
|
416
|
+
(
|
|
417
|
+
'Proxy Request Behavior',
|
|
418
|
+
() =>
|
|
419
|
+
{
|
|
420
|
+
test
|
|
421
|
+
(
|
|
422
|
+
'the proxy handler should call httpProxyServer.web with the correct target',
|
|
423
|
+
(fDone) =>
|
|
424
|
+
{
|
|
425
|
+
createStartedHarness(null,
|
|
426
|
+
{
|
|
427
|
+
DestinationURL: 'http://api.example.com:3000/'
|
|
428
|
+
},
|
|
429
|
+
(pHarness) =>
|
|
430
|
+
{
|
|
431
|
+
let tmpWebCalled = false;
|
|
432
|
+
let tmpCapturedOptions = null;
|
|
433
|
+
|
|
434
|
+
// Mock BEFORE connecting routes — the handler captures `this.httpProxyServer`
|
|
435
|
+
// at call time, so we can replace it before invocation
|
|
436
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
437
|
+
{
|
|
438
|
+
tmpWebCalled = true;
|
|
439
|
+
tmpCapturedOptions = pOptions;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
pHarness.proxy.connectProxyRoutes(['/api/*']);
|
|
443
|
+
|
|
444
|
+
// Invoke the handler via the IPC router directly
|
|
445
|
+
// The proxy handler does NOT call fNext() on success (by design for real HTTP),
|
|
446
|
+
// so the returned promise will never resolve. We use setTimeout to check
|
|
447
|
+
// our mock assertions after the synchronous code has executed.
|
|
448
|
+
let tmpMockRequest = { method: 'GET', url: '/api/users' };
|
|
449
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
450
|
+
|
|
451
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/api/users');
|
|
452
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
453
|
+
|
|
454
|
+
setTimeout(
|
|
455
|
+
() =>
|
|
456
|
+
{
|
|
457
|
+
Expect(tmpWebCalled).to.equal(true);
|
|
458
|
+
Expect(tmpCapturedOptions.target).to.equal('http://api.example.com:3000/');
|
|
459
|
+
Expect(tmpCapturedOptions.secure).to.equal(false);
|
|
460
|
+
return fDone();
|
|
461
|
+
}, 100);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
test
|
|
467
|
+
(
|
|
468
|
+
'the proxy handler should merge custom httpProxyOptions',
|
|
469
|
+
(fDone) =>
|
|
470
|
+
{
|
|
471
|
+
createStartedHarness(null,
|
|
472
|
+
{
|
|
473
|
+
DestinationURL: 'http://localhost:9999/',
|
|
474
|
+
httpProxyOptions:
|
|
475
|
+
{
|
|
476
|
+
changeOrigin: true,
|
|
477
|
+
xfwd: true
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
(pHarness) =>
|
|
481
|
+
{
|
|
482
|
+
// Verify options are stored
|
|
483
|
+
Expect(pHarness.proxy.options.httpProxyOptions).to.be.an('object');
|
|
484
|
+
Expect(pHarness.proxy.options.httpProxyOptions.changeOrigin).to.equal(true);
|
|
485
|
+
|
|
486
|
+
let tmpCapturedOptions = null;
|
|
487
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
488
|
+
{
|
|
489
|
+
tmpCapturedOptions = pOptions;
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
pHarness.proxy.connectProxyRoutes(['/merged/*']);
|
|
493
|
+
|
|
494
|
+
let tmpMockRequest = { method: 'GET', url: '/merged/test' };
|
|
495
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
496
|
+
|
|
497
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/merged/test');
|
|
498
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
499
|
+
|
|
500
|
+
setTimeout(
|
|
501
|
+
() =>
|
|
502
|
+
{
|
|
503
|
+
Expect(tmpCapturedOptions).to.be.an('object');
|
|
504
|
+
Expect(tmpCapturedOptions.target).to.equal('http://localhost:9999/');
|
|
505
|
+
Expect(tmpCapturedOptions.secure).to.equal(false);
|
|
506
|
+
Expect(tmpCapturedOptions.changeOrigin).to.equal(true);
|
|
507
|
+
Expect(tmpCapturedOptions.xfwd).to.equal(true);
|
|
508
|
+
return fDone();
|
|
509
|
+
}, 100);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
test
|
|
515
|
+
(
|
|
516
|
+
'the proxy handler should pass the request URL through to httpProxyServer.web',
|
|
517
|
+
(fDone) =>
|
|
518
|
+
{
|
|
519
|
+
createStartedHarness(null,
|
|
520
|
+
{
|
|
521
|
+
DestinationURL: 'http://localhost:9999/'
|
|
522
|
+
},
|
|
523
|
+
(pHarness) =>
|
|
524
|
+
{
|
|
525
|
+
let tmpCapturedRequest = null;
|
|
526
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
527
|
+
{
|
|
528
|
+
tmpCapturedRequest = pRequest;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
pHarness.proxy.connectProxyRoutes(['/1.0/*']);
|
|
532
|
+
|
|
533
|
+
let tmpMockRequest = { method: 'GET', url: '/1.0/Users/123' };
|
|
534
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
535
|
+
|
|
536
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/1.0/Users/123');
|
|
537
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
538
|
+
|
|
539
|
+
setTimeout(
|
|
540
|
+
() =>
|
|
541
|
+
{
|
|
542
|
+
Expect(tmpCapturedRequest).to.be.an('object');
|
|
543
|
+
Expect(tmpCapturedRequest.url).to.equal('/1.0/Users/123');
|
|
544
|
+
return fDone();
|
|
545
|
+
}, 100);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
test
|
|
551
|
+
(
|
|
552
|
+
'the proxy handler should use the custom URL when one is provided to connectProxyRoutes',
|
|
553
|
+
(fDone) =>
|
|
554
|
+
{
|
|
555
|
+
createStartedHarness(null,
|
|
556
|
+
{
|
|
557
|
+
DestinationURL: 'http://default.local/'
|
|
558
|
+
},
|
|
559
|
+
(pHarness) =>
|
|
560
|
+
{
|
|
561
|
+
let tmpCapturedTarget = null;
|
|
562
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
563
|
+
{
|
|
564
|
+
tmpCapturedTarget = pOptions.target;
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
pHarness.proxy.connectProxyRoutes(['/custom/*'], 'http://override.local:5000/');
|
|
568
|
+
|
|
569
|
+
let tmpMockRequest = { method: 'GET', url: '/custom/endpoint' };
|
|
570
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
571
|
+
|
|
572
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/custom/endpoint');
|
|
573
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
574
|
+
|
|
575
|
+
setTimeout(
|
|
576
|
+
() =>
|
|
577
|
+
{
|
|
578
|
+
Expect(tmpCapturedTarget).to.equal('http://override.local:5000/');
|
|
579
|
+
return fDone();
|
|
580
|
+
}, 100);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
test
|
|
586
|
+
(
|
|
587
|
+
'the proxy handler should use the constructor URL when no URL is provided to connectProxyRoutes',
|
|
588
|
+
(fDone) =>
|
|
589
|
+
{
|
|
590
|
+
createStartedHarness(null,
|
|
591
|
+
{
|
|
592
|
+
DestinationURL: 'http://configured.local:8080/'
|
|
593
|
+
},
|
|
594
|
+
(pHarness) =>
|
|
595
|
+
{
|
|
596
|
+
let tmpCapturedTarget = null;
|
|
597
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
598
|
+
{
|
|
599
|
+
tmpCapturedTarget = pOptions.target;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
pHarness.proxy.connectProxyRoutes(['/default/*']);
|
|
603
|
+
|
|
604
|
+
let tmpMockRequest = { method: 'GET', url: '/default/endpoint' };
|
|
605
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
606
|
+
|
|
607
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/default/endpoint');
|
|
608
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
609
|
+
|
|
610
|
+
setTimeout(
|
|
611
|
+
() =>
|
|
612
|
+
{
|
|
613
|
+
Expect(tmpCapturedTarget).to.equal('http://configured.local:8080/');
|
|
614
|
+
return fDone();
|
|
615
|
+
}, 100);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
test
|
|
621
|
+
(
|
|
622
|
+
'the proxy should always set secure:false for HTTPS targets',
|
|
623
|
+
(fDone) =>
|
|
624
|
+
{
|
|
625
|
+
createStartedHarness(null,
|
|
626
|
+
{
|
|
627
|
+
DestinationURL: 'https://secure-api.example.com/'
|
|
628
|
+
},
|
|
629
|
+
(pHarness) =>
|
|
630
|
+
{
|
|
631
|
+
let tmpCapturedSecure = null;
|
|
632
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
633
|
+
{
|
|
634
|
+
tmpCapturedSecure = pOptions.secure;
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
pHarness.proxy.connectProxyRoutes(['/secure/*']);
|
|
638
|
+
|
|
639
|
+
let tmpMockRequest = { method: 'GET', url: '/secure/endpoint' };
|
|
640
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
641
|
+
|
|
642
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/secure/endpoint');
|
|
643
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {});
|
|
644
|
+
|
|
645
|
+
setTimeout(
|
|
646
|
+
() =>
|
|
647
|
+
{
|
|
648
|
+
Expect(tmpCapturedSecure).to.equal(false);
|
|
649
|
+
return fDone();
|
|
650
|
+
}, 100);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
suite
|
|
658
|
+
(
|
|
659
|
+
'Proxy Error Handling',
|
|
660
|
+
() =>
|
|
661
|
+
{
|
|
662
|
+
test
|
|
663
|
+
(
|
|
664
|
+
'the proxy should catch errors thrown by httpProxyServer.web',
|
|
665
|
+
(fDone) =>
|
|
666
|
+
{
|
|
667
|
+
createStartedHarness(null,
|
|
668
|
+
{
|
|
669
|
+
DestinationURL: 'http://localhost:59999/'
|
|
670
|
+
},
|
|
671
|
+
(pHarness) =>
|
|
672
|
+
{
|
|
673
|
+
pHarness.proxy.connectProxyRoutes(['/error-test/*']);
|
|
674
|
+
|
|
675
|
+
let tmpErrorLogged = false;
|
|
676
|
+
|
|
677
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
678
|
+
{
|
|
679
|
+
throw new Error('Simulated proxy connection failure');
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
let tmpMockRequest = { method: 'GET', url: '/error-test/resource' };
|
|
683
|
+
let tmpMockResponse =
|
|
684
|
+
{
|
|
685
|
+
send: () => {},
|
|
686
|
+
end: (pData) =>
|
|
687
|
+
{
|
|
688
|
+
// The catch block calls pResponse.end(JSON.stringify(pError))
|
|
689
|
+
tmpErrorLogged = true;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/error-test/resource');
|
|
694
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {}).then(
|
|
695
|
+
() =>
|
|
696
|
+
{
|
|
697
|
+
// The error path calls fNext() after end()
|
|
698
|
+
Expect(tmpErrorLogged).to.equal(true);
|
|
699
|
+
return fDone();
|
|
700
|
+
}).catch(
|
|
701
|
+
(pError) =>
|
|
702
|
+
{
|
|
703
|
+
// May propagate depending on IPC internals
|
|
704
|
+
Expect(tmpErrorLogged).to.equal(true);
|
|
705
|
+
return fDone();
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
test
|
|
712
|
+
(
|
|
713
|
+
'the proxy error handler should log the error with the request URL',
|
|
714
|
+
(fDone) =>
|
|
715
|
+
{
|
|
716
|
+
createStartedHarness(null,
|
|
717
|
+
{
|
|
718
|
+
DestinationURL: 'http://localhost:59999/'
|
|
719
|
+
},
|
|
720
|
+
(pHarness) =>
|
|
721
|
+
{
|
|
722
|
+
pHarness.proxy.connectProxyRoutes(['/log-error/*']);
|
|
723
|
+
|
|
724
|
+
let tmpErrorMessage = null;
|
|
725
|
+
let tmpOriginalLogError = pHarness.proxy.log.error.bind(pHarness.proxy.log);
|
|
726
|
+
pHarness.proxy.log.error = (pMessage, pData) =>
|
|
727
|
+
{
|
|
728
|
+
tmpErrorMessage = pMessage;
|
|
729
|
+
tmpOriginalLogError(pMessage, pData);
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
pHarness.proxy.httpProxyServer.web = (pRequest, pResponse, pOptions) =>
|
|
733
|
+
{
|
|
734
|
+
throw new Error('Connection refused');
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
let tmpMockRequest = { method: 'GET', url: '/log-error/resource' };
|
|
738
|
+
let tmpMockResponse = { send: () => {}, end: () => {} };
|
|
739
|
+
|
|
740
|
+
let tmpRouteHandler = pHarness.orator.serviceServer.router.find('GET', '/log-error/resource');
|
|
741
|
+
tmpRouteHandler.handler(tmpMockRequest, tmpMockResponse, {}).then(
|
|
742
|
+
() =>
|
|
743
|
+
{
|
|
744
|
+
Expect(tmpErrorMessage).to.be.a('string');
|
|
745
|
+
Expect(tmpErrorMessage).to.include('/log-error/resource');
|
|
746
|
+
Expect(tmpErrorMessage).to.include('Connection refused');
|
|
747
|
+
return fDone();
|
|
748
|
+
}).catch(
|
|
749
|
+
(pError) =>
|
|
750
|
+
{
|
|
751
|
+
if (tmpErrorMessage)
|
|
752
|
+
{
|
|
753
|
+
Expect(tmpErrorMessage).to.include('/log-error/resource');
|
|
754
|
+
Expect(tmpErrorMessage).to.include('Connection refused');
|
|
755
|
+
return fDone();
|
|
756
|
+
}
|
|
757
|
+
return fDone(pError);
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
suite
|
|
766
|
+
(
|
|
767
|
+
'Multiple Proxy Instances',
|
|
768
|
+
() =>
|
|
769
|
+
{
|
|
770
|
+
test
|
|
771
|
+
(
|
|
772
|
+
'multiple proxy instances should coexist with different configurations',
|
|
773
|
+
(fDone) =>
|
|
774
|
+
{
|
|
775
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
776
|
+
|
|
777
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
778
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
779
|
+
|
|
780
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
781
|
+
let tmpProxyA = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
782
|
+
{
|
|
783
|
+
DestinationURL: 'http://backend-a.local:3000/'
|
|
784
|
+
});
|
|
785
|
+
let tmpProxyB = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
786
|
+
{
|
|
787
|
+
DestinationURL: 'http://backend-b.local:4000/'
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
Expect(tmpProxyA.proxyDestinationURL).to.equal('http://backend-a.local:3000/');
|
|
791
|
+
Expect(tmpProxyB.proxyDestinationURL).to.equal('http://backend-b.local:4000/');
|
|
792
|
+
|
|
793
|
+
// Each instance should have its own proxy server
|
|
794
|
+
Expect(tmpProxyA.httpProxyServer).to.be.an('object');
|
|
795
|
+
Expect(tmpProxyB.httpProxyServer).to.be.an('object');
|
|
796
|
+
Expect(tmpProxyA.httpProxyServer).to.not.equal(tmpProxyB.httpProxyServer);
|
|
797
|
+
|
|
798
|
+
return fDone();
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
test
|
|
803
|
+
(
|
|
804
|
+
'multiple proxy instances can register routes to different backends',
|
|
805
|
+
(fDone) =>
|
|
806
|
+
{
|
|
807
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
808
|
+
|
|
809
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
810
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
811
|
+
|
|
812
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
813
|
+
let tmpAPIProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
814
|
+
{
|
|
815
|
+
DestinationURL: 'http://api-server.local:3000/'
|
|
816
|
+
});
|
|
817
|
+
let tmpAuthProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
818
|
+
{
|
|
819
|
+
DestinationURL: 'http://auth-server.local:4000/'
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
tmpOrator.startService(
|
|
823
|
+
() =>
|
|
824
|
+
{
|
|
825
|
+
tmpAPIProxy.connectProxyRoutes(['/1.0/*']);
|
|
826
|
+
tmpAuthProxy.connectProxyRoutes(['/auth/*', '/oauth/*']);
|
|
827
|
+
|
|
828
|
+
// Verify all routes are registered
|
|
829
|
+
let tmpAPIHandler = tmpFable.Orator.serviceServer.router.find('GET', '/1.0/Users');
|
|
830
|
+
Expect(tmpAPIHandler).to.be.an('object');
|
|
831
|
+
Expect(tmpAPIHandler).to.have.a.property('handler');
|
|
832
|
+
|
|
833
|
+
let tmpAuthHandler = tmpFable.Orator.serviceServer.router.find('POST', '/auth/login');
|
|
834
|
+
Expect(tmpAuthHandler).to.be.an('object');
|
|
835
|
+
Expect(tmpAuthHandler).to.have.a.property('handler');
|
|
836
|
+
|
|
837
|
+
let tmpOAuthHandler = tmpFable.Orator.serviceServer.router.find('GET', '/oauth/callback');
|
|
838
|
+
Expect(tmpOAuthHandler).to.be.an('object');
|
|
839
|
+
Expect(tmpOAuthHandler).to.have.a.property('handler');
|
|
840
|
+
|
|
841
|
+
return fDone();
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
suite
|
|
849
|
+
(
|
|
850
|
+
'Configuration Edge Cases',
|
|
851
|
+
() =>
|
|
852
|
+
{
|
|
853
|
+
test
|
|
854
|
+
(
|
|
855
|
+
'LogLevel zero should be the default when no log level is configured',
|
|
856
|
+
(fDone) =>
|
|
857
|
+
{
|
|
858
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
859
|
+
|
|
860
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
861
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
862
|
+
|
|
863
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
864
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
865
|
+
|
|
866
|
+
Expect(tmpProxy.LogLevel).to.equal(0);
|
|
867
|
+
|
|
868
|
+
return fDone();
|
|
869
|
+
}
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
test
|
|
873
|
+
(
|
|
874
|
+
'LogLevel should be settable to zero explicitly via options without falling to fable settings',
|
|
875
|
+
(fDone) =>
|
|
876
|
+
{
|
|
877
|
+
let tmpFableSettings = Object.assign({}, defaultFableSettings,
|
|
878
|
+
{
|
|
879
|
+
OratorHTTPProxyLogLevel: 5
|
|
880
|
+
});
|
|
881
|
+
let tmpFable = new libFable(tmpFableSettings);
|
|
882
|
+
|
|
883
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
884
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
885
|
+
|
|
886
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
887
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
888
|
+
{
|
|
889
|
+
LogLevel: 0
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// The `in` operator returns true for LogLevel:0 since the key exists
|
|
893
|
+
Expect(tmpProxy.LogLevel).to.equal(0);
|
|
894
|
+
|
|
895
|
+
return fDone();
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
test
|
|
900
|
+
(
|
|
901
|
+
'the proxy destination URL should default to loopback when nothing is configured',
|
|
902
|
+
(fDone) =>
|
|
903
|
+
{
|
|
904
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
905
|
+
|
|
906
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
907
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
908
|
+
|
|
909
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
910
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
911
|
+
|
|
912
|
+
Expect(tmpProxy.proxyDestinationURL).to.equal('http://127.0.0.1/');
|
|
913
|
+
|
|
914
|
+
return fDone();
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
test
|
|
919
|
+
(
|
|
920
|
+
'the httpProxyServer should be an independent instance per proxy',
|
|
921
|
+
(fDone) =>
|
|
922
|
+
{
|
|
923
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
924
|
+
|
|
925
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
926
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
927
|
+
|
|
928
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
929
|
+
let tmpProxyOne = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
930
|
+
{
|
|
931
|
+
DestinationURL: 'http://one.local/'
|
|
932
|
+
});
|
|
933
|
+
let tmpProxyTwo = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
934
|
+
{
|
|
935
|
+
DestinationURL: 'http://two.local/'
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// Each proxy should have its own http-proxy server instance
|
|
939
|
+
Expect(tmpProxyOne.httpProxyServer).to.not.equal(tmpProxyTwo.httpProxyServer);
|
|
940
|
+
// And distinct destination URLs
|
|
941
|
+
Expect(tmpProxyOne.proxyDestinationURL).to.not.equal(tmpProxyTwo.proxyDestinationURL);
|
|
942
|
+
|
|
943
|
+
return fDone();
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
test
|
|
948
|
+
(
|
|
949
|
+
'the RequestPrefixList options key has a known quirk where it reads RequestPrefix instead',
|
|
950
|
+
(fDone) =>
|
|
951
|
+
{
|
|
952
|
+
// This test documents the behavior of the constructor's RequestPrefixList handling.
|
|
953
|
+
// The constructor checks `RequestPrefixList` in options (using the `in` operator),
|
|
954
|
+
// but then reads `this.options.RequestPrefix` (note: singular, not plural).
|
|
955
|
+
// This means when RequestPrefixList is set in options, the value read is
|
|
956
|
+
// this.options.RequestPrefix which is undefined.
|
|
957
|
+
let tmpFable = new libFable(defaultFableSettings);
|
|
958
|
+
|
|
959
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
960
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
961
|
+
|
|
962
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
963
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy',
|
|
964
|
+
{
|
|
965
|
+
RequestPrefixList: ['/should-be-ignored/*']
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
// Because the constructor reads this.options.RequestPrefix (which is undefined),
|
|
969
|
+
// the requestPrefixList ends up as undefined rather than the expected array.
|
|
970
|
+
// This documents the current behavior — the mismatched key name.
|
|
971
|
+
Expect(tmpProxy.requestPrefixList).to.equal(undefined);
|
|
972
|
+
|
|
973
|
+
return fDone();
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
test
|
|
978
|
+
(
|
|
979
|
+
'fable settings should provide the request prefix list when options do not',
|
|
980
|
+
(fDone) =>
|
|
981
|
+
{
|
|
982
|
+
let tmpFableSettings = Object.assign({}, defaultFableSettings,
|
|
983
|
+
{
|
|
984
|
+
OratorHTTPProxyRequestPrefixList: ['/custom/v1/*', '/custom/v2/*']
|
|
985
|
+
});
|
|
986
|
+
let tmpFable = new libFable(tmpFableSettings);
|
|
987
|
+
|
|
988
|
+
tmpFable.serviceManager.addServiceType('Orator', libOrator);
|
|
989
|
+
tmpFable.serviceManager.addServiceType('OratorHTTPProxy', libOratorHTTPProxy);
|
|
990
|
+
|
|
991
|
+
let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator', {});
|
|
992
|
+
let tmpProxy = tmpFable.serviceManager.instantiateServiceProvider('OratorHTTPProxy', {});
|
|
993
|
+
|
|
994
|
+
Expect(tmpProxy.requestPrefixList).to.be.an('array');
|
|
995
|
+
Expect(tmpProxy.requestPrefixList).to.deep.equal(['/custom/v1/*', '/custom/v2/*']);
|
|
996
|
+
|
|
997
|
+
return fDone();
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
);
|