orator 2.0.4 → 2.0.10

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/.travis.yml CHANGED
@@ -3,6 +3,10 @@ services:
3
3
  language: node_js
4
4
  node_js:
5
5
  - "8"
6
+ - "10"
7
+ - "12"
8
+ - "14"
9
+ - "15"
6
10
  addons:
7
11
  code_climate:
8
12
  repo_token: 63ba2b0cf307133c314376df4b05fa8f0aa5e98919eb76488ad27a07f163cb77
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orator",
3
- "version": "2.0.4",
3
+ "version": "2.0.10",
4
4
  "description": "Restful web API server. Using restify 6.",
5
5
  "main": "source/Orator.js",
6
6
  "scripts": {
@@ -10,6 +10,7 @@
10
10
  "coverage-cluster": "./node_modules/istanbul/lib/cli.js cover --dir ./coverage/cluster ./node_modules/mocha/bin/_mocha -- --exit -u tdd -R spec ./test/Orator_cluster_test.js.deferred",
11
11
  "coverage-report": "./node_modules/istanbul/lib/cli.js report",
12
12
  "test": "npm run test-normal && npm run test-cluster",
13
+ "tests": "mocha --exit -u tdd -R spec -g",
13
14
  "test-normal": "./node_modules/mocha/bin/_mocha --exit -u tdd -R spec",
14
15
  "test-cluster": "./node_modules/mocha/bin/_mocha --exit -u tdd -R spec ./test/Orator_cluster_test.js.deferred"
15
16
  },
@@ -27,6 +28,24 @@
27
28
  "bugs": {
28
29
  "url": "https://github.com/stevenvelozo/orator/issues"
29
30
  },
31
+ "mocha": {
32
+ "diff": true,
33
+ "extension": [
34
+ "js"
35
+ ],
36
+ "package": "./package.json",
37
+ "reporter": "spec",
38
+ "slow": "75",
39
+ "timeout": "5000",
40
+ "ui": "tdd",
41
+ "watch-files": [
42
+ "source/**/*.js",
43
+ "test/**/*.js"
44
+ ],
45
+ "watch-ignore": [
46
+ "lib/vendor"
47
+ ]
48
+ },
30
49
  "homepage": "https://github.com/stevenvelozo/orator",
31
50
  "devDependencies": {
32
51
  "async": "2.6.1",
@@ -37,18 +56,15 @@
37
56
  "mocha": "5.2.0",
38
57
  "supertest": "3.1.0"
39
58
  },
40
- "optionalDependencies": {
41
- "v8-profiler": "^5.7.0"
42
- },
43
59
  "dependencies": {
44
60
  "cachetrax": "^1.0.0",
45
61
  "cluster": "^0.7.7",
46
62
  "fable": "~1.0.2",
47
63
  "fable-uuid": "~1.0.2",
48
64
  "http-forward": "^0.1.3",
49
- "request": "^2.69.0",
65
+ "request": "^2.88.2",
50
66
  "restify": "^6.4.0",
51
- "restify-await-promise": "^1.0.0",
67
+ "restify-await-promise": "^2.2.0",
52
68
  "restify-cors-middleware": "^1.1.1"
53
69
  }
54
70
  }
package/source/Orator.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  const libRestifyCORS = require('restify-cors-middleware');
8
8
  const restifyPromise = require('restify-await-promise');
9
+ const RestifyErrors = require('restify-errors');
9
10
 
10
11
  /**
11
12
  * Orator Web API Server
@@ -25,15 +26,13 @@ var Orator = function()
25
26
 
26
27
  // Restify for the routing and API serving
27
28
  var libRestify = require('restify');
28
- // NodeGrind for request profiling
29
- var libV8Profiler = false;
30
29
  // FS for writing out profiling information
31
30
  var libFS = require('fs');
32
31
  // Cluster API for spawning multiple worker processes
33
32
  var libCluster = require('cluster');
34
33
  // HTTP Forward Proxy
35
34
  var libHttpForward = require('http-forward');
36
-
35
+
37
36
  var _ProxyRoutes = [];
38
37
 
39
38
  // This state is used to lazily initialize the Native Restify Modules on route creation the first time
@@ -113,7 +112,7 @@ var Orator = function()
113
112
  // This is used as the base object for instantiating the server. You can add custom parsers and formatters safely with lambdas here.
114
113
  RawServerParameters: {},
115
114
 
116
- // Turning these on decreases speed dramatically, and generates a chrome profiling file for each request.
115
+ // Enable request lifecycle logging if desired, for debugging
117
116
  Profiling: (
118
117
  {
119
118
  // Tracelog is just log-based request timing encapsulation.
@@ -121,16 +120,7 @@ var Orator = function()
121
120
 
122
121
  // Requestlog is to log each request ID and Session ID.
123
122
  RequestLog: false,
124
-
125
- // These profiling settings determine if we generate cpu or call graphs
126
- Enabled: false,
127
- Folder: '/tmp/',
128
- // Currently supported profile types: ChromeCPU
129
- Type: 'ChromeCPU'
130
123
  }),
131
-
132
- // Turning this on logs stack traces
133
- LogStackTraces: true
134
124
  });
135
125
 
136
126
  var _Fable = require('fable').new(pSettings);
@@ -252,7 +242,7 @@ var Orator = function()
252
242
  // Lazily initialize the Restify parsers the first time we access this object.
253
243
  // This creates a behavior where changing the "enabledModules" property does not
254
244
  // do anything after routes have been created. We may want to eventually
255
- // throw a warning (and ignroe the change) if someone accesses the property
245
+ // throw a warning (and ignore the change) if someone accesses the property
256
246
  // after _RestifyParsersInitialized is true.
257
247
  if (!_RestifyParsersInitialized)
258
248
  {
@@ -260,21 +250,6 @@ var Orator = function()
260
250
  initializeHeaderParsers(_Fable.settings, _WebServer);
261
251
  initializeContentParsers(_Fable.settings, _WebServer);
262
252
  initializeLogicParsers(_Fable.settings, _WebServer);
263
- _WebServer.on
264
- (
265
- 'uncaughtException',
266
- function (pRequest, pResponse, pRoute, pError)
267
- {
268
- if (typeof(_Fable.settings.UncaughtExceptionHook) === 'function')
269
- {
270
- _Fable.settings.UncaughtExceptionHook(pRequest, pResponse, pRoute, pError);
271
- }
272
- if (_Fable.settings.LogStackTraces)
273
- {
274
- _Fable.log.error('Request error', {Error:true, Stack:pError.stack});
275
- }
276
- }
277
- );
278
253
  }
279
254
  };
280
255
 
@@ -300,18 +275,6 @@ var Orator = function()
300
275
  _Fable.log.trace('Request start...',{RequestUUID: pRequest.RequestUUID});
301
276
  }
302
277
 
303
- if (_Fable.settings.Profiling.Enabled)
304
- {
305
- // Lazily load NodeGrind
306
- if (!libV8Profiler)
307
- {
308
- libV8Profiler = require('v8-profiler');
309
- }
310
- // If profiling is enabled, build a callgrind file
311
- _Fable.log.debug('Request '+pRequest.RequestUUID+' starting with full profiling...');
312
- pRequest.ProfilerName = _Fable.settings.Product+'-'+_Fable.settings.ProductVersion+'-'+pRequest.RequestUUID;
313
- libV8Profiler.startProfiling(pRequest.RequestUUID, true);
314
- }
315
278
 
316
279
  return fNext();
317
280
  }
@@ -326,28 +289,6 @@ var Orator = function()
326
289
  {
327
290
  _Fable.log.trace("... Request finished.",{RequestUUID: pRequest.RequestUUID, ResponseCode: pResponse.code, ResponseLength: pResponse.contentLength});
328
291
  }
329
-
330
- if (typeof(pRequest.ProfilerName) === 'string')
331
- {
332
- var tmpRequestProfilePrefix = '';
333
- var tmpRequestProfilePostfix = '';
334
-
335
- // Get a Chrome *.cpuprofile.json that you can load into the Chrome
336
- // profiler (right-click on 'Profiles' in left pane in the 'Profiles' tab)
337
- var tmpChromeCPUProfiler = libV8Profiler.stopProfiling();
338
- tmpRequestProfilePostfix = '.cpuprofile.json';
339
-
340
- tmpChromeCPUProfiler.export(function(pError, pProfileData)
341
- {
342
- var tmpProfilerFileName = _Fable.settings.Profiling.Folder+tmpRequestProfilePrefix+pRequest.ProfilerName+tmpRequestProfilePostfix;
343
- libFS.writeFileSync(tmpProfilerFileName, pProfileData);
344
- pRequest.CPUProfiler.delete();
345
- if (_Fable.settings.Profiling.TraceLog)
346
- {
347
- _Fable.log.trace('... Request '+pRequest.RequestUUID+' profile written to: '+tmpProfilerFileName);
348
- }
349
- });
350
- }
351
292
  }
352
293
  );
353
294
  };
@@ -361,16 +302,16 @@ var Orator = function()
361
302
  *
362
303
  * @method startWorkers
363
304
  */
364
- var startWorkers = function(pWorkers, fCallback)
365
- {
366
- if (pWorkers === 0)
367
- {
368
- return fCallback();
369
- }
370
- else if (pWorkers < 0)
371
- {
372
- pWorkers = require('os').cpus().length;
373
- }
305
+ var startWorkers = function(pWorkers, fCallback)
306
+ {
307
+ if (pWorkers === 0)
308
+ {
309
+ return fCallback();
310
+ }
311
+ else if (pWorkers < 0)
312
+ {
313
+ pWorkers = require('os').cpus().length;
314
+ }
374
315
 
375
316
  for (var i=0; i<pWorkers; i++)
376
317
  {
@@ -381,25 +322,25 @@ var Orator = function()
381
322
 
382
323
  libCluster.on('message', function(message)
383
324
  {
384
- _Fable.log.trace('Orator Worker ' + message.pid + ' started.');
325
+ _Fable.log.trace('Orator Worker ' + message.pid + ' started.');
385
326
 
386
- if (++tmpActiveWorkers === pWorkers)
387
- {
388
- //The Master process fires the callback when all
389
- // the workers have started. This is used by
390
- // unit tests.
391
- return fCallback();
392
- }
327
+ if (++tmpActiveWorkers === pWorkers)
328
+ {
329
+ //The Master process fires the callback when all
330
+ // the workers have started. This is used by
331
+ // unit tests.
332
+ return fCallback();
333
+ }
393
334
  });
394
335
  libCluster.on('exit', function(worker, code, signal)
395
336
  {
396
- _Fable.log.trace('Orator Worker ' + worker.id + '/' + worker.process.pid + ' died');
337
+ _Fable.log.trace('Orator Worker ' + worker.id + '/' + worker.process.pid + ' died');
397
338
 
398
- // APIWorkerRestart flag auto-restarts a worker if it crashes
399
- if (_Fable.settings.APIWorkerRestart)
400
- {
401
- libCluster.fork();
402
- }
339
+ // APIWorkerRestart flag auto-restarts a worker if it crashes
340
+ if (_Fable.settings.APIWorkerRestart)
341
+ {
342
+ libCluster.fork();
343
+ }
403
344
  });
404
345
  };
405
346
 
@@ -433,7 +374,7 @@ var Orator = function()
433
374
  if (!libCluster.isMaster)
434
375
  {
435
376
  // notify master about the request
436
- process.send({ signal: 'notifyListening', pid: process.pid });
377
+ process.send({ signal: 'notifyListening', pid: process.pid });
437
378
  }
438
379
  return tmpNext();
439
380
  }
@@ -496,7 +437,8 @@ var Orator = function()
496
437
  'application/octet-stream',
497
438
  'application/javascript',
498
439
  'application/json'
499
- ]
440
+ ],
441
+ handleUncaughtExceptions: true,
500
442
  });
501
443
  _Fable.settings.RawServerParameters = _WebServerParameters;
502
444
  };
@@ -599,7 +541,7 @@ var Orator = function()
599
541
  _Fable.log.error('Proxy error', pError);
600
542
  }
601
543
 
602
- return fNext(true);
544
+ return fNext(false);
603
545
  });
604
546
  };
605
547
 
@@ -647,16 +589,45 @@ var Orator = function()
647
589
  );
648
590
  };
649
591
 
592
+ var userErrorTransformer;
593
+ var registerErrorTransformer = function(transformError)
594
+ {
595
+ if (typeof(transformError) === 'function')
596
+ {
597
+ userErrorTransformer = transformError;
598
+ }
599
+ };
600
+
601
+ var userUnhandledErrorHandler;
602
+ var registerUnhandledErrorHandler = function(callback)
603
+ {
604
+ if (typeof(callback) === 'function')
605
+ {
606
+ userUnhandledErrorHandler = callback;
607
+ }
608
+ };
609
+
650
610
  var handleError = function(req, res, err, callback)
651
611
  {
612
+ // allow application to customize this error handling
613
+ if (typeof userUnhandledErrorHandler == 'function' && !userUnhandledErrorHandler(req, res, err, callback))
614
+ {
615
+ return;
616
+ }
652
617
  //the default for a string error is 'Route not found', though it isn't accurate
653
618
  err.message = err.message.replace('Route not found: ', '');
654
619
  //log error
655
620
  let sessionId = '';
656
621
  if (req.UserSession && req.UserSession.SessionID)
622
+ {
657
623
  sessionId = req.UserSession.SessionID;
658
-
659
- _Fable.log.error('REQUEST ERROR: ' + err.message, {SessionID: sessionId, Action: 'APIError'});
624
+ }
625
+
626
+ if (!err.logged)
627
+ {
628
+ _Fable.log.error('REQUEST ERROR: ' + err.message || err,
629
+ { SessionID: sessionId, Action: 'APIError', RequestUUID: req.RequestUUID, Error: err.message || err, Stack: err.stack, });
630
+ }
660
631
  return callback();
661
632
  }
662
633
 
@@ -677,6 +648,9 @@ var Orator = function()
677
648
  serveStatic: libRestify.plugins.serveStatic,
678
649
  getHeader: getHeader,
679
650
 
651
+ registerErrorTransformer: registerErrorTransformer,
652
+ registerUnhandledErrorHandler: registerUnhandledErrorHandler,
653
+
680
654
  new: createNew
681
655
  });
682
656
 
@@ -686,7 +660,7 @@ var Orator = function()
686
660
  * @property webServer
687
661
  * @type Object
688
662
  */
689
- var getWebServer = function()
663
+ var getWebServer = function()
690
664
  {
691
665
  // Lazily load the webserver the first time it is accessed
692
666
  if (typeof(_WebServer) === 'undefined')
@@ -696,7 +670,35 @@ var Orator = function()
696
670
  _WebServerParameters.version = _Fable.settings.ProductVersion;
697
671
  _WebServer = libRestify.createServer(_WebServerParameters);
698
672
  //enable support for Promise endpoint methods
699
- restifyPromise.install(_WebServer);
673
+ restifyPromise.install(_WebServer,
674
+ {
675
+ /*
676
+ * We provide an error transformer here to handle uncaught exceptions, as the
677
+ * old code path is broken by this plugin. This at least allows us to send back
678
+ * some kind of error to the caller. Without this, you get a 500 with a body of {}
679
+ */
680
+ errorTransformer:
681
+ {
682
+ transform: (originalError) =>
683
+ {
684
+ let error = originalError;
685
+ if (typeof userErrorTransformer == 'function')
686
+ {
687
+ error = userErrorTransformer(error);
688
+ }
689
+ // duck typing for error objects; means an exception was thrown; but status code means it was handled upstream
690
+ if (error && error.message && error.stack && !error.statusCode)
691
+ {
692
+ _Fable.log.fatal('Unhandled request error', { Error: error.message || error, Stack: error.stack, });
693
+
694
+ const wrappedError = new RestifyErrors.InternalError(error, error.message || 'Unhandled error');
695
+ wrappedError.logged = true; // dumb, but prevents double logging in handleError
696
+ return wrappedError;
697
+ }
698
+ return error || new Error('fail request'); // returning no error here is bad news; request can get stuck, so at least return something
699
+ },
700
+ },
701
+ });
700
702
  //handle errors - DOES NOT HANDLE uncaught exceptions! (DOES work with Promises however)
701
703
  _WebServer.on('restifyError', handleError);
702
704
  initializeInstrumentation();
@@ -16,12 +16,12 @@ var _MockSettings = (
16
16
  {
17
17
  Product: 'MockOratorAlternate',
18
18
  ProductVersion: '0.0.0',
19
- APIServerPort: 8181
19
+ APIServerPort: 8181,
20
20
  });
21
21
 
22
22
  suite
23
23
  (
24
- 'Orator',
24
+ 'Orator Proxy',
25
25
  function()
26
26
  {
27
27
  var _Orator;
@@ -34,6 +34,14 @@ suite
34
34
  }
35
35
  );
36
36
 
37
+ suiteTeardown
38
+ (
39
+ function()
40
+ {
41
+ _Orator.stopWebServer();
42
+ }
43
+ );
44
+
37
45
  suite
38
46
  (
39
47
  'Object Sanity',
@@ -99,7 +107,7 @@ suite
99
107
  fNext();
100
108
  }
101
109
  );
102
-
110
+
103
111
  // Create a route to proxy HTTP requests to google, dropping the prefix for the remote request
104
112
  _Orator.addProxyRoute('google/', 'https://www.google.com/');
105
113
 
@@ -112,7 +120,7 @@ suite
112
120
  _Orator.addStaticRoute(__dirname+'/../', 'LICENSE', /\/content\/(.*)/, '/content/');
113
121
  // You should be able to host files just with a path
114
122
  _Orator.addStaticRoute(__dirname+'/');
115
-
123
+
116
124
  _Orator.startWebServer(fDone);
117
125
  }
118
126
  );
@@ -138,13 +146,13 @@ suite
138
146
  function(fDone)
139
147
  {
140
148
  libSuperTest('http://localhost:' + _MockSettings.APIServerPort + '/')
141
- .get('google/search?q=laser+shark')
149
+ .get('google/a')
142
150
  .end(
143
151
  function (pError, pResponse)
144
152
  {
145
153
  //check for google search result
146
154
  Expect(pResponse.text)
147
- .to.contain('laser shark');
155
+ .to.contain('google');
148
156
  return fDone();
149
157
  }
150
158
  );
@@ -184,7 +192,7 @@ suite
184
192
  //check for not found
185
193
  Expect(pResponse.statusCode)
186
194
  .to.equal(404);
187
-
195
+
188
196
  return fDone();
189
197
  }
190
198
  );
@@ -196,6 +204,7 @@ suite
196
204
  'Shutdown Orator web server',
197
205
  function()
198
206
  {
207
+ //TODO: this test assumes the order of test execution; is that safe?
199
208
  _Orator.stopWebServer();
200
209
  }
201
210
  );
@@ -16,21 +16,49 @@ var _MockSettings = (
16
16
  {
17
17
  Product: 'MockOratorAlternate',
18
18
  ProductVersion: '0.0.0',
19
- APIServerPort: 8080
19
+ APIServerPort: 8999,
20
20
  });
21
21
 
22
22
  suite
23
23
  (
24
- 'Orator',
24
+ 'Orator basic',
25
25
  function()
26
26
  {
27
27
  var _Orator;
28
+ let capturedOriginalErr = null;
29
+ let capturedReq = null;
30
+ let capturedRes = null;
31
+ let capturedErr = null;
32
+ let capturedCallback = null;
28
33
 
29
34
  setup
30
35
  (
31
36
  function()
32
37
  {
33
38
  _Orator = require('../source/Orator.js').new(_MockSettings);
39
+ _Orator.registerUnhandledErrorHandler((req, res, err, callback) =>
40
+ {
41
+ capturedReq = req;
42
+ capturedRes = res;
43
+ capturedErr = err;
44
+ capturedCallback = callback;
45
+ return err.statusCode > 0;
46
+ });
47
+ _Orator.registerErrorTransformer((err) =>
48
+ {
49
+ capturedOriginalErr = err;
50
+ return err;
51
+ });
52
+ }
53
+ );
54
+
55
+ teardown(() => { capturedReq = undefined; capturedRes = undefined; capturedErr = undefined; capturedOriginalErr = undefined; });
56
+
57
+ suiteTeardown
58
+ (
59
+ function()
60
+ {
61
+ _Orator.stopWebServer();
34
62
  }
35
63
  );
36
64
 
@@ -95,8 +123,8 @@ suite
95
123
  '/ThirdAPI',
96
124
  function (pRequest, pResponse, fNext)
97
125
  {
98
- pResponse.send('RAWR');
99
- return fNext('The server should give a nice stack trace');
126
+ throw new Error('The server should give a nice stack trace');
127
+ fNext();
100
128
  }
101
129
  );
102
130
  _Orator.webServer.get (
@@ -113,6 +141,23 @@ suite
113
141
  return Promise.reject('error promise response');
114
142
  }
115
143
  );
144
+ _Orator.webServer.get (
145
+ '/SyncBug',
146
+ function (pRequest, pResponse, fNext)
147
+ {
148
+ const cat = null;
149
+ cat.dog();
150
+ }
151
+ );
152
+ _Orator.webServer.get (
153
+ '/AsyncBug',
154
+ async function (pRequest, pResponse)
155
+ {
156
+ await new Promise((resolve) => setTimeout(() => resolve()), 1);
157
+ const cat = null;
158
+ cat.dog();
159
+ }
160
+ );
116
161
  // Expect this to fail
117
162
  _Orator.addStaticRoute();
118
163
  // And you can specify a path for bonus
@@ -125,19 +170,19 @@ suite
125
170
  (
126
171
  function ()
127
172
  {
128
- libSuperTest('http://localhost:8080/')
173
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
129
174
  .get('PIN')
130
175
  .end(
131
176
  function (pError, pResponse)
132
177
  {
133
178
  Expect(pResponse.text)
134
179
  .to.contain('PON');
135
- libSuperTest('http://localhost:8080/')
180
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
136
181
  .get('ThirdAPI')
137
182
  .end(
138
183
  function (pError, pResponse)
139
184
  {
140
- libSuperTest('http://localhost:8080/')
185
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
141
186
  .get('Test.css')
142
187
  .end(
143
188
  function (pError, pResponse)
@@ -145,7 +190,7 @@ suite
145
190
  _Orator.settings.Profiling.TraceLog = true;
146
191
  Expect(pResponse.text)
147
192
  .to.contain('50000px');
148
- libSuperTest('http://localhost:8080/')
193
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
149
194
  .get('content/')
150
195
  .end(
151
196
  function (pError, pResponse)
@@ -170,7 +215,7 @@ suite
170
215
  'promise routes should work',
171
216
  function(fDone)
172
217
  {
173
- libSuperTest('http://localhost:8080/')
218
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
174
219
  .get('PromiseAPI')
175
220
  .end(
176
221
  function (pError, pResponse)
@@ -188,14 +233,70 @@ suite
188
233
  'promise routes error handling',
189
234
  function(fDone)
190
235
  {
191
- libSuperTest('http://localhost:8080/')
236
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
192
237
  .get('PromiseAPIError')
193
238
  .end(
194
239
  function (pError, pResponse)
195
240
  {
241
+ Expect(capturedErr).to.exist;
242
+ Expect(capturedReq).to.exist;
243
+ Expect(capturedRes).to.exist;
196
244
  Expect(pResponse.text)
197
245
  .to.contain('error promise response');
198
246
 
247
+ return fDone();
248
+ }
249
+ );
250
+ }
251
+ );
252
+ test
253
+ (
254
+ 'unhandled error interception',
255
+ function(fDone)
256
+ {
257
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
258
+ .get('SyncBug')
259
+ .end(
260
+ function (pError, pResponse)
261
+ {
262
+ Expect(pError).to.not.exist;
263
+ Expect(capturedOriginalErr).to.exist;
264
+ Expect(capturedErr).to.exist;
265
+ Expect(capturedOriginalErr.statusCode).to.not.exist; // raw error
266
+ Expect(capturedErr.statusCode).to.equal(500); // wrapped to give reasonable http response
267
+ Expect(capturedReq).to.exist; // shows we called the custom error handler
268
+ Expect(capturedReq.RequestUUID).to.be.a('string'); // so we can log this from the handler
269
+ Expect(capturedRes).to.exist; // shows we called the custom error handler
270
+ Expect(capturedErr.message).to.contain('Cannot read properties of null (reading \'dog\')');
271
+ Expect(pResponse.text).to.contain('Cannot read property \'dog\'');
272
+
273
+ return fDone();
274
+ }
275
+ );
276
+ }
277
+ );
278
+ test
279
+ (
280
+ 'unhandled error interception async',
281
+ function(fDone)
282
+ {
283
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
284
+ .get('AsyncBug')
285
+ .end(
286
+ function (pError, pResponse)
287
+ {
288
+ Expect(pError).to.not.exist;
289
+ Expect(capturedOriginalErr).to.exist;
290
+ Expect(capturedErr).to.exist;
291
+ Expect(capturedOriginalErr.statusCode).to.not.exist; // raw error
292
+ Expect(capturedErr.statusCode).to.equal(500); // wrapped to give reasonable http response
293
+ Expect(capturedReq).to.exist; // shows we called the custom error handler
294
+ Expect(capturedReq.RequestUUID).to.be.a('string'); // so we can log this from the handler
295
+ Expect(capturedRes).to.exist; // shows we called the custom error handler
296
+ Expect(capturedCallback).to.be.a('function');
297
+ Expect(capturedErr.message).to.contain('Cannot read property \'dog\'');
298
+ Expect(pResponse.text).to.contain('Cannot read property \'dog\'');
299
+
199
300
  return fDone();
200
301
  }
201
302
  );
@@ -217,7 +318,7 @@ suite
217
318
  {
218
319
  Product: 'MockOratorInverted',
219
320
  ProductVersion: '0.0.0',
220
- APIServerPort: 8089,
321
+ APIServerPort: _MockSettings.APIServerPort - 10,
221
322
  LogStackTraces: false
222
323
  });
223
324
  var _OratorInverted = require('../source/Orator.js').new(_MockSettingsInvertedParameters);
@@ -251,11 +352,12 @@ suite
251
352
  }
252
353
  );
253
354
  _OratorInverted.startWebServer();
254
- libSuperTest('http://localhost:8089/')
355
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort - 10}/`)
255
356
  .get('PINGU')
256
357
  .end(
257
358
  function (pError, pResponse)
258
359
  {
360
+ _OratorInverted.stopWebServer();
259
361
  if (pError)
260
362
  {
261
363
  console.log('Error on Inverted Results: '+JSON.stringify(pError));
@@ -282,4 +384,4 @@ suite
282
384
  }
283
385
  );
284
386
  }
285
- );
387
+ );
@@ -17,7 +17,7 @@ var _MockSettings = (
17
17
  {
18
18
  Product: 'MockOratorAlternate',
19
19
  ProductVersion: '0.0.0',
20
- APIServerPort: 8080,
20
+ APIServerPort: 8099,
21
21
  APIWorkers: 3,
22
22
  APIWorkerRestart: false
23
23
  });
@@ -135,19 +135,19 @@ suite
135
135
  function(fDone)
136
136
  {
137
137
 
138
- libSuperTest('http://localhost:8080/')
138
+ libSuperTest('http://localhost:8099/')
139
139
  .get('PIN')
140
140
  .end(
141
141
  function (pError, pResponse)
142
142
  {
143
143
  Expect(pResponse.text)
144
144
  .to.contain('PON');
145
- libSuperTest('http://localhost:8080/')
145
+ libSuperTest('http://localhost:8099/')
146
146
  .get('ThirdAPI')
147
147
  .end(
148
148
  function (pError, pResponse)
149
149
  {
150
- libSuperTest('http://localhost:8080/')
150
+ libSuperTest('http://localhost:8099/')
151
151
  .get('Test.css')
152
152
  .end(
153
153
  function (pError, pResponse)
@@ -155,7 +155,7 @@ suite
155
155
  _Orator.settings.Profiling.TraceLog = true;
156
156
  Expect(pResponse.text)
157
157
  .to.contain('50000px');
158
- libSuperTest('http://localhost:8080/')
158
+ libSuperTest('http://localhost:8099/')
159
159
  .get('content/')
160
160
  .end(
161
161
  function (pError, pResponse)
@@ -191,7 +191,7 @@ suite
191
191
 
192
192
  for(var i=0; i<DISTRIBUTION_LOAD_COUNT; i++)
193
193
  {
194
- libSuperTest('http://localhost:8080/')
194
+ libSuperTest('http://localhost:8099/')
195
195
  .get('GetPID')
196
196
  .end(
197
197
  function (pError, pResponse)
@@ -230,4 +230,4 @@ suite
230
230
  );
231
231
  }
232
232
  }
233
- );
233
+ );
@@ -15,7 +15,7 @@ var libSuperTest = require('supertest');
15
15
  var _MockSettings = (
16
16
  {
17
17
  Product: 'MockOratorRequestLogging',
18
- APIServerPort: 8085,
18
+ APIServerPort: 8985,
19
19
  Profiling: (
20
20
  {
21
21
  // Tracelog is just log-based request timing encapsulation.
@@ -40,6 +40,14 @@ suite
40
40
  }
41
41
  );
42
42
 
43
+ suiteTeardown
44
+ (
45
+ function()
46
+ {
47
+ _Orator.stopWebServer();
48
+ }
49
+ );
50
+
43
51
  suite
44
52
  (
45
53
  'Request Logging Server Start',
@@ -51,22 +59,31 @@ suite
51
59
  function(fDone)
52
60
  {
53
61
  _Orator.webServer.get (
54
- '/PINGOLo',
55
- function (pRequest, pResponse, fNext)
62
+ '/PINGOLo',
63
+ function (pRequest, pResponse, fNext)
56
64
  {
57
65
  pResponse.send('Loggo');
58
66
  fNext();
59
67
  }
60
68
  );
61
69
  _Orator.webServer.get (
62
- '/PINataGOLo',
63
- async function (pRequest, pResponse)
70
+ '/PINataGOLo',
71
+ function (pRequest, pResponse, fNext)
72
+ {
73
+ throw new Error("Something absolutely dire has occurred.");
74
+ pResponse.send('Loggso');
75
+ fNext();
76
+ }
77
+ );
78
+ _Orator.webServer.get (
79
+ '/PINataGOLo_promise',
80
+ async function (pRequest, pResponse)
64
81
  {
65
82
  return Promise.reject("Something absolutely dire has occurred.");
66
83
  }
67
84
  );
68
85
  _Orator.startWebServer();
69
- libSuperTest('http://localhost:8085/')
86
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
70
87
  .get('PINGOLo')
71
88
  .end(
72
89
  function (pError, pResponse)
@@ -81,7 +98,7 @@ suite
81
98
  Expect(pResponse.text)
82
99
  .to.contain('Loggo');
83
100
  }
84
- libSuperTest('http://localhost:8085/')
101
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
85
102
  .get('PINataGOLo')
86
103
  .end(
87
104
  function (pError, pResponse)
@@ -93,6 +110,8 @@ suite
93
110
  }
94
111
  else
95
112
  {
113
+ Expect(pResponse.statusCode)
114
+ .to.equal(500);
96
115
  Expect(pResponse.text)
97
116
  .to.contain('dire');
98
117
  }
@@ -107,11 +126,38 @@ suite
107
126
  );
108
127
  test
109
128
  (
110
- 'rejected promises should throw properly.',
129
+ 'uncaught exceptions should throw properly.',
111
130
  function(fDone)
112
131
  {
113
- libSuperTest('http://localhost:8085/')
132
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
114
133
  .get('PINataGOLo')
134
+ .end(
135
+ function (pError, pResponse)
136
+ {
137
+ if (pError)
138
+ {
139
+ console.log('Error on Logfile Results: '+JSON.stringify(pError));
140
+ Expect('Logged Request Error').to.equal('Nothing');
141
+ }
142
+ else
143
+ {
144
+ Expect(pResponse.statusCode)
145
+ .to.equal(500);
146
+ Expect(pResponse.text)
147
+ .to.contain('dire');
148
+ }
149
+ fDone();
150
+ }
151
+ );
152
+ }
153
+ );
154
+ test
155
+ (
156
+ 'rejected promises should throw properly.',
157
+ function(fDone)
158
+ {
159
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
160
+ .get('PINataGOLo_promise')
115
161
  .end(
116
162
  function (pError, pResponse)
117
163
  {
@@ -133,4 +179,4 @@ suite
133
179
  }
134
180
  );
135
181
  }
136
- );
182
+ );
@@ -1,26 +0,0 @@
1
- {
2
- // Use IntelliSense to learn about possible attributes.
3
- // Hover to view descriptions of existing attributes.
4
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
- "version": "0.2.0",
6
- "configurations": [
7
- {
8
- "type": "node",
9
- "request": "launch",
10
- "name": "Mocha Tests",
11
- "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
12
- "args": [
13
- "-u",
14
- "tdd",
15
- "--bail",
16
- "--timeout",
17
- "999999",
18
- "--colors",
19
- "${workspaceFolder}/test/Orator_basic_tests.js",
20
- "${workspaceFolder}/test/Orator_logging_tests.js",
21
- "${workspaceFolder}/test/Orator-proxy_tests.js"
22
- ],
23
- "internalConsoleOptions": "openOnSessionStart"
24
- }
25
- ]
26
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Unit tests for the Orator Server
3
- *
4
- * @license MIT
5
- *
6
- * @author Steven Velozo <steven@velozo.com>
7
- */
8
-
9
- var Chai = require("chai");
10
- var Expect = Chai.expect;
11
- var Assert = Chai.assert;
12
-
13
- var libSuperTest = require('supertest');
14
-
15
- var _MockSettings = (
16
- {
17
- Product: 'MockOratorCHROMENOTRACE',
18
- APIServerPort: 8083,
19
- Profiling: (
20
- {
21
- // Tracelog is just log-based request timing encapsulation.
22
- TraceLog: false,
23
-
24
- // These profiling settings determine if we generate cpu or call graphs
25
- Enabled: true,
26
- Folder: '/tmp/',
27
- // Currently supported profile types: CallGrinder or ChromeCPU
28
- Type: 'ChromeCPU'
29
- })
30
-
31
- });
32
-
33
- suite
34
- (
35
- 'Orator',
36
- function()
37
- {
38
- var _Orator;
39
-
40
- setup
41
- (
42
- function()
43
- {
44
- _Orator = require('../source/Orator.js').new(_MockSettings);
45
- }
46
- );
47
-
48
- suite
49
- (
50
- 'Alternate (chromecpu) without Trace Server Start',
51
- function()
52
- {
53
- test
54
- (
55
- 'simple routes should work',
56
- function(fDone)
57
- {
58
- _Orator.webServer.get (
59
- '/PI',
60
- function (pRequest, pResponse, fNext)
61
- {
62
- pResponse.send('PO');
63
- fNext();
64
- }
65
- );
66
- _Orator.startWebServer();
67
- libSuperTest('http://localhost:8083/')
68
- .get('PI')
69
- .end(
70
- function (pError, pResponse)
71
- {
72
- if (pError)
73
- {
74
- console.log('Error on Inverted Results: '+JSON.stringify(pError));
75
- Expect('No Trace Request Error').to.equal('Nothing');
76
- }
77
- else
78
- {
79
- Expect(pResponse.text)
80
- .to.contain('PO');
81
- }
82
- fDone();
83
- }
84
- );
85
- }
86
- );
87
- }
88
- );
89
- }
90
- );
@@ -1,95 +0,0 @@
1
- /**
2
- * Unit tests for the Orator Server
3
- *
4
- * @license MIT
5
- *
6
- * @author Steven Velozo <steven@velozo.com>
7
- */
8
-
9
- var Chai = require("chai");
10
- var Expect = Chai.expect;
11
- var Assert = Chai.assert;
12
-
13
- var libSuperTest = require('supertest');
14
-
15
- var _MockSettings = (
16
- {
17
- Product: 'MockOratorCHROME',
18
- APIServerPort: 8081,
19
- Profiling: (
20
- {
21
- // Tracelog is just log-based request timing encapsulation.
22
- TraceLog: true,
23
-
24
- // These profiling settings determine if we generate cpu or call graphs
25
- Enabled: true,
26
- Folder: '/tmp/',
27
- // Currently supported profile types: CallGrinder or ChromeCPU
28
- Type: 'ChromeCPU'
29
- })
30
-
31
- });
32
-
33
- suite
34
- (
35
- 'Orator',
36
- function()
37
- {
38
- var _Orator;
39
-
40
- setup
41
- (
42
- function()
43
- {
44
- _Orator = require('../source/Orator.js').new(_MockSettings);
45
- }
46
- );
47
-
48
- suite
49
- (
50
- 'Alternate (chromecpu) Server Start',
51
- function()
52
- {
53
- test
54
- (
55
- 'simple routes should work',
56
- function(fDone)
57
- {
58
- _Orator.webServer.get (
59
- '/PINGO',
60
- function (pRequest, pResponse, fNext)
61
- {
62
- let tmpMemory = false;
63
- for (var i = 0; i < 1000; i++)
64
- {
65
- tmpMemory = process.memoryUsage();
66
- }
67
- pResponse.send('PONGO');
68
- fNext();
69
- }
70
- );
71
- _Orator.startWebServer();
72
- libSuperTest('http://localhost:8081/')
73
- .get('PINGO')
74
- .end(
75
- function (pError, pResponse)
76
- {
77
- if (pError)
78
- {
79
- console.log('Error on Inverted Results: '+JSON.stringify(pError));
80
- Expect('Chrome Trace Request Error').to.equal('Nothing');
81
- }
82
- else
83
- {
84
- Expect(pResponse.text)
85
- .to.contain('PONGO');
86
- }
87
- fDone();
88
- }
89
- );
90
- }
91
- );
92
- }
93
- );
94
- }
95
- );