orator 2.0.3 → 2.0.8

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
@@ -2,7 +2,11 @@ services:
2
2
  - mongodb
3
3
  language: node_js
4
4
  node_js:
5
- - "6"
5
+ - "8"
6
+ - "10"
7
+ - "12"
8
+ - "14"
9
+ - "15"
6
10
  addons:
7
11
  code_climate:
8
12
  repo_token: 63ba2b0cf307133c314376df4b05fa8f0aa5e98919eb76488ad27a07f163cb77
@@ -12,4 +16,4 @@ after_script:
12
16
  - cat coverage/lcov.info | ./node_modules/codeclimate-test-reporter/bin/codeclimate.js
13
17
  - cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
14
18
  notifications:
15
- slack: paviateam:C1q99hL9XXpiPpau2PUrVZPC
19
+ slack: paviateam:C1q99hL9XXpiPpau2PUrVZPC
@@ -0,0 +1,24 @@
1
+ // Load the orator module with a few settings
2
+ var libOrator = require(__dirname+'/source/Orator.js').new(
3
+ {
4
+ Product: 'MyMicroserviceHash',
5
+ ProductVersion: '9.8.7',
6
+
7
+ "APIServerPort": 8080
8
+ });
9
+
10
+ // Add an API endpoint
11
+ libOrator.webServer.get
12
+ (
13
+ '/test/:hash',
14
+ function(pRequest, pResponse, fNext)
15
+ {
16
+ // Send back whatever was sent as "name" in the URI
17
+ pResponse.send(pRequest.params);
18
+ libOrator.fable.log.info('WTF');
19
+ return fNext();
20
+ }
21
+ );
22
+
23
+ // Start the web service
24
+ libOrator.startWebServer();
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Orator API Server, meant to interact well with Fable, Meadow and FoxHound.
4
4
 
5
- [![Code Climate](https://codeclimate.com/github/stevenvelozo/orator/badges/gpa.svg)](https://codeclimate.com/github/stevenvelozo/orator) [![Coverage Status](https://coveralls.io/repos/stevenvelozo/orator/badge.svg?branch=master)](https://coveralls.io/r/stevenvelozo/orator?branch=master) [![Build Status](https://travis-ci.org/stevenvelozo/orator.svg?branch=master)](https://travis-ci.org/stevenvelozo/orator) [![Dependency Status](https://david-dm.org/stevenvelozo/orator.svg)](https://david-dm.org/stevenvelozo/orator) [![devDependency Status](https://david-dm.org/stevenvelozo/orator/dev-status.svg)](https://david-dm.org/stevenvelozo/orator#info=devDependencies)
5
+ [![Coverage Status](https://coveralls.io/repos/stevenvelozo/orator/badge.svg?branch=master)](https://coveralls.io/r/stevenvelozo/orator?branch=master) [![Build Status](https://travis-ci.org/stevenvelozo/orator.svg?branch=master)](https://travis-ci.org/stevenvelozo/orator) [![Dependency Status](https://david-dm.org/stevenvelozo/orator.svg)](https://david-dm.org/stevenvelozo/orator) [![devDependency Status](https://david-dm.org/stevenvelozo/orator/dev-status.svg)](https://david-dm.org/stevenvelozo/orator#info=devDependencies)
6
6
 
7
7
  This is not an attempt to reinvent the wheel. Nor do we want to make a car with five of them.
8
8
 
@@ -29,7 +29,7 @@ var libOrator = require('orator').new(
29
29
  });
30
30
 
31
31
  // Add an API endpoint
32
- pOrator.webServer.post
32
+ libOrator.webServer.post
33
33
  (
34
34
  '/echo/:name',
35
35
  function(pRequest, pResponse, fNext)
@@ -61,7 +61,7 @@ var libOrator = require('orator').new(
61
61
  });
62
62
 
63
63
  // Add an API endpoint
64
- pOrator.webServer.post
64
+ libOrator.webServer.post
65
65
  (
66
66
  '/echo/:name',
67
67
  function(pRequest, pResponse, fNext)
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "orator",
3
- "version": "2.0.3",
3
+ "version": "2.0.8",
4
4
  "description": "Restful web API server. Using restify 6.",
5
5
  "main": "source/Orator.js",
6
6
  "scripts": {
7
7
  "start": "node source/Orator.js",
8
8
  "coverage": "npm run coverage-normal && npm run coverage-cluster && npm run coverage-report",
9
- "coverage-normal": "./node_modules/istanbul/lib/cli.js cover --dir ./coverage/normal ./node_modules/mocha/bin/_mocha -- -u tdd -R spec",
10
- "coverage-cluster": "./node_modules/istanbul/lib/cli.js cover --dir ./coverage/cluster ./node_modules/mocha/bin/_mocha -- -u tdd -R spec ./test/Orator_cluster_test.js.deferred",
9
+ "coverage-normal": "./node_modules/istanbul/lib/cli.js cover --dir ./coverage/normal ./node_modules/mocha/bin/_mocha -- --exit -u tdd -R spec",
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
- "test-normal": "./node_modules/mocha/bin/_mocha -u tdd -R spec",
14
- "test-cluster": "./node_modules/mocha/bin/_mocha -u tdd -R spec ./test/Orator_cluster_test.js.deferred"
13
+ "tests": "mocha --exit -u tdd -R spec -g",
14
+ "test-normal": "./node_modules/mocha/bin/_mocha --exit -u tdd -R spec",
15
+ "test-cluster": "./node_modules/mocha/bin/_mocha --exit -u tdd -R spec ./test/Orator_cluster_test.js.deferred"
15
16
  },
16
17
  "repository": {
17
18
  "type": "git",
@@ -29,25 +30,23 @@
29
30
  },
30
31
  "homepage": "https://github.com/stevenvelozo/orator",
31
32
  "devDependencies": {
32
- "async": "1.5.0",
33
- "chai": "3.4.1",
34
- "codeclimate-test-reporter": "0.1.1",
35
- "coveralls": "2.11.4",
36
- "istanbul": "0.4.1",
37
- "mocha": "^5.2.0",
38
- "supertest": "1.1.0"
39
- },
40
- "optionalDependencies": {
41
- "nodegrind": "git+https://github.com/stevenvelozo/nodegrind.git"
33
+ "async": "2.6.1",
34
+ "chai": "4.1.2",
35
+ "codeclimate-test-reporter": "0.5.0",
36
+ "coveralls": "3.0.2",
37
+ "istanbul": "0.4.5",
38
+ "mocha": "5.2.0",
39
+ "supertest": "3.1.0"
42
40
  },
43
41
  "dependencies": {
42
+ "cachetrax": "^1.0.0",
44
43
  "cluster": "^0.7.7",
45
- "fable": "~1.0.1",
46
- "fable-uuid": "~1.0.1",
44
+ "fable": "~1.0.2",
45
+ "fable-uuid": "~1.0.2",
47
46
  "http-forward": "^0.1.3",
48
- "request": "^2.69.0",
47
+ "request": "^2.88.2",
49
48
  "restify": "^6.4.0",
50
- "restify-await-promise": "^1.0.0",
49
+ "restify-await-promise": "^2.2.0",
51
50
  "restify-cors-middleware": "^1.1.1"
52
51
  }
53
52
  }
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,14 +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 libNodegrind = 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');
35
+
36
36
  var _ProxyRoutes = [];
37
37
 
38
38
  // This state is used to lazily initialize the Native Restify Modules on route creation the first time
@@ -112,7 +112,7 @@ var Orator = function()
112
112
  // This is used as the base object for instantiating the server. You can add custom parsers and formatters safely with lambdas here.
113
113
  RawServerParameters: {},
114
114
 
115
- // Turning these on decreases speed dramatically, and generates a cachegrind file for each request.
115
+ // Enable request lifecycle logging if desired, for debugging
116
116
  Profiling: (
117
117
  {
118
118
  // Tracelog is just log-based request timing encapsulation.
@@ -120,16 +120,7 @@ var Orator = function()
120
120
 
121
121
  // Requestlog is to log each request ID and Session ID.
122
122
  RequestLog: false,
123
-
124
- // These profiling settings determine if we generate cpu or call graphs
125
- Enabled: false,
126
- Folder: '/tmp/',
127
- // Currently supported profile types: CallGrinder or ChromeCPU
128
- Type: 'CallGrinder'
129
123
  }),
130
-
131
- // Turning this on logs stack traces
132
- LogStackTraces: true
133
124
  });
134
125
 
135
126
  var _Fable = require('fable').new(pSettings);
@@ -251,7 +242,7 @@ var Orator = function()
251
242
  // Lazily initialize the Restify parsers the first time we access this object.
252
243
  // This creates a behavior where changing the "enabledModules" property does not
253
244
  // do anything after routes have been created. We may want to eventually
254
- // 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
255
246
  // after _RestifyParsersInitialized is true.
256
247
  if (!_RestifyParsersInitialized)
257
248
  {
@@ -259,21 +250,6 @@ var Orator = function()
259
250
  initializeHeaderParsers(_Fable.settings, _WebServer);
260
251
  initializeContentParsers(_Fable.settings, _WebServer);
261
252
  initializeLogicParsers(_Fable.settings, _WebServer);
262
- _WebServer.on
263
- (
264
- 'uncaughtException',
265
- function (pRequest, pResponse, pRoute, pError)
266
- {
267
- if (typeof(_Fable.settings.UncaughtExceptionHook) === 'function')
268
- {
269
- _Fable.settings.UncaughtExceptionHook(pRequest, pResponse, pRoute, pError);
270
- }
271
- if (_Fable.settings.LogStackTraces)
272
- {
273
- _Fable.log.error('Request error', {Error:true, Stack:pError.stack});
274
- }
275
- }
276
- );
277
253
  }
278
254
  };
279
255
 
@@ -299,18 +275,6 @@ var Orator = function()
299
275
  _Fable.log.trace('Request start...',{RequestUUID: pRequest.RequestUUID});
300
276
  }
301
277
 
302
- if (_Fable.settings.Profiling.Enabled)
303
- {
304
- // Lazily load NodeGrind
305
- if (!libNodegrind)
306
- {
307
- libNodegrind = require('nodegrind');
308
- }
309
- // If profiling is enabled, build a callgrind file
310
- _Fable.log.debug('Request '+pRequest.RequestUUID+' starting with full profiling...');
311
- pRequest.ProfilerName = _Fable.settings.Product+'-'+_Fable.settings.ProductVersion+'-'+pRequest.RequestUUID;
312
- libNodegrind.startCPU(pRequest.RequestUUID);
313
- }
314
278
 
315
279
  return fNext();
316
280
  }
@@ -325,34 +289,6 @@ var Orator = function()
325
289
  {
326
290
  _Fable.log.trace("... Request finished.",{RequestUUID: pRequest.RequestUUID, ResponseCode: pResponse.code, ResponseLength: pResponse.contentLength});
327
291
  }
328
-
329
- if (typeof(pRequest.ProfilerName) === 'string')
330
- {
331
- var tmpRequestProfile = '';
332
- var tmpRequestProfilePrefix = '';
333
- var tmpRequestProfilePostfix = '';
334
-
335
- if (_Fable.settings.Profiling.Type === 'CallGrinder')
336
- {
337
- // Get the callgrind profile as a string
338
- tmpRequestProfile = libNodegrind.stopCPU(pRequest.RequestUUID);
339
- tmpRequestProfilePrefix = 'callgrind.';
340
- }
341
- else
342
- {
343
- // Alternatively, get a Chrome *.cpuprofile that you can load into the Chrome
344
- // profiler (right-click on 'Profiles' in left pane in the 'Profiles' tab)
345
- tmpRequestProfile = libNodegrind.stopCPU(pRequest.RequestUUID, 'cpuprofile');
346
- tmpRequestProfilePostfix = '.cpuprofile';
347
- }
348
-
349
- libFS.writeFileSync(_Fable.settings.Profiling.Folder+tmpRequestProfilePrefix+pRequest.ProfilerName+tmpRequestProfilePostfix, tmpRequestProfile);
350
-
351
- if (_Fable.settings.Profiling.TraceLog)
352
- {
353
- _Fable.log.trace('... Request '+pRequest.RequestUUID+' profile written to: '+_Fable.settings.Profiling.Folder+pRequest.ProfilerName);
354
- }
355
- }
356
292
  }
357
293
  );
358
294
  };
@@ -366,16 +302,16 @@ var Orator = function()
366
302
  *
367
303
  * @method startWorkers
368
304
  */
369
- var startWorkers = function(pWorkers, fCallback)
370
- {
371
- if (pWorkers === 0)
372
- {
373
- return fCallback();
374
- }
375
- else if (pWorkers < 0)
376
- {
377
- pWorkers = require('os').cpus().length;
378
- }
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
+ }
379
315
 
380
316
  for (var i=0; i<pWorkers; i++)
381
317
  {
@@ -386,25 +322,25 @@ var Orator = function()
386
322
 
387
323
  libCluster.on('message', function(message)
388
324
  {
389
- _Fable.log.trace('Orator Worker ' + message.pid + ' started.');
325
+ _Fable.log.trace('Orator Worker ' + message.pid + ' started.');
390
326
 
391
- if (++tmpActiveWorkers === pWorkers)
392
- {
393
- //The Master process fires the callback when all
394
- // the workers have started. This is used by
395
- // unit tests.
396
- return fCallback();
397
- }
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
+ }
398
334
  });
399
335
  libCluster.on('exit', function(worker, code, signal)
400
336
  {
401
- _Fable.log.trace('Orator Worker ' + worker.id + '/' + worker.process.pid + ' died');
337
+ _Fable.log.trace('Orator Worker ' + worker.id + '/' + worker.process.pid + ' died');
402
338
 
403
- // APIWorkerRestart flag auto-restarts a worker if it crashes
404
- if (_Fable.settings.APIWorkerRestart)
405
- {
406
- libCluster.fork();
407
- }
339
+ // APIWorkerRestart flag auto-restarts a worker if it crashes
340
+ if (_Fable.settings.APIWorkerRestart)
341
+ {
342
+ libCluster.fork();
343
+ }
408
344
  });
409
345
  };
410
346
 
@@ -438,7 +374,7 @@ var Orator = function()
438
374
  if (!libCluster.isMaster)
439
375
  {
440
376
  // notify master about the request
441
- process.send({ signal: 'notifyListening', pid: process.pid });
377
+ process.send({ signal: 'notifyListening', pid: process.pid });
442
378
  }
443
379
  return tmpNext();
444
380
  }
@@ -501,7 +437,8 @@ var Orator = function()
501
437
  'application/octet-stream',
502
438
  'application/javascript',
503
439
  'application/json'
504
- ]
440
+ ],
441
+ handleUncaughtExceptions: true,
505
442
  });
506
443
  _Fable.settings.RawServerParameters = _WebServerParameters;
507
444
  };
@@ -604,7 +541,7 @@ var Orator = function()
604
541
  _Fable.log.error('Proxy error', pError);
605
542
  }
606
543
 
607
- return fNext(true);
544
+ return fNext(false);
608
545
  });
609
546
  };
610
547
 
@@ -652,16 +589,45 @@ var Orator = function()
652
589
  );
653
590
  };
654
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
+
655
610
  var handleError = function(req, res, err, callback)
656
611
  {
612
+ // allow application to customize this error handling
613
+ if (typeof userUnhandledErrorHandler == 'function' && !userUnhandledErrorHandler(req, res, err))
614
+ {
615
+ return callback();
616
+ }
657
617
  //the default for a string error is 'Route not found', though it isn't accurate
658
618
  err.message = err.message.replace('Route not found: ', '');
659
619
  //log error
660
620
  let sessionId = '';
661
621
  if (req.UserSession && req.UserSession.SessionID)
622
+ {
662
623
  sessionId = req.UserSession.SessionID;
663
-
664
- _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
+ }
665
631
  return callback();
666
632
  }
667
633
 
@@ -682,6 +648,9 @@ var Orator = function()
682
648
  serveStatic: libRestify.plugins.serveStatic,
683
649
  getHeader: getHeader,
684
650
 
651
+ registerErrorTransformer: registerErrorTransformer,
652
+ registerUnhandledErrorHandler: registerUnhandledErrorHandler,
653
+
685
654
  new: createNew
686
655
  });
687
656
 
@@ -691,7 +660,7 @@ var Orator = function()
691
660
  * @property webServer
692
661
  * @type Object
693
662
  */
694
- var getWebServer = function()
663
+ var getWebServer = function()
695
664
  {
696
665
  // Lazily load the webserver the first time it is accessed
697
666
  if (typeof(_WebServer) === 'undefined')
@@ -701,7 +670,35 @@ var Orator = function()
701
670
  _WebServerParameters.version = _Fable.settings.ProductVersion;
702
671
  _WebServer = libRestify.createServer(_WebServerParameters);
703
672
  //enable support for Promise endpoint methods
704
- 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
+ });
705
702
  //handle errors - DOES NOT HANDLE uncaught exceptions! (DOES work with Promises however)
706
703
  _WebServer.on('restifyError', handleError);
707
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
  );
@@ -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,47 @@ 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;
28
32
 
29
33
  setup
30
34
  (
31
35
  function()
32
36
  {
33
37
  _Orator = require('../source/Orator.js').new(_MockSettings);
38
+ _Orator.registerUnhandledErrorHandler((req, res, err) =>
39
+ {
40
+ capturedReq = req;
41
+ capturedRes = res;
42
+ capturedErr = err;
43
+ return err.statusCode > 0;
44
+ });
45
+ _Orator.registerErrorTransformer((err) =>
46
+ {
47
+ capturedOriginalErr = err;
48
+ return err;
49
+ });
50
+ }
51
+ );
52
+
53
+ teardown(() => { capturedReq = undefined; capturedRes = undefined; capturedErr = undefined; capturedOriginalErr = undefined; });
54
+
55
+ suiteTeardown
56
+ (
57
+ function()
58
+ {
59
+ _Orator.stopWebServer();
34
60
  }
35
61
  );
36
62
 
@@ -95,8 +121,8 @@ suite
95
121
  '/ThirdAPI',
96
122
  function (pRequest, pResponse, fNext)
97
123
  {
98
- pResponse.send('RAWR');
99
- return fNext('The server should give a nice stack trace');
124
+ throw new Error('The server should give a nice stack trace');
125
+ fNext();
100
126
  }
101
127
  );
102
128
  _Orator.webServer.get (
@@ -113,29 +139,48 @@ suite
113
139
  return Promise.reject('error promise response');
114
140
  }
115
141
  );
142
+ _Orator.webServer.get (
143
+ '/SyncBug',
144
+ function (pRequest, pResponse, fNext)
145
+ {
146
+ const cat = null;
147
+ cat.dog();
148
+ }
149
+ );
150
+ _Orator.webServer.get (
151
+ '/AsyncBug',
152
+ async function (pRequest, pResponse)
153
+ {
154
+ await new Promise((resolve) => setTimeout(() => resolve()), 1);
155
+ const cat = null;
156
+ cat.dog();
157
+ }
158
+ );
116
159
  // Expect this to fail
117
160
  _Orator.addStaticRoute();
118
161
  // And you can specify a path for bonus
119
- _Orator.addStaticRoute(__dirname+'/../', 'LICENSE', /\/content\/(.*)/, '/content/');
162
+ var libPath = require('path');
163
+ var tmpPath = libPath.normalize(__dirname+'/');
164
+ _Orator.addStaticRoute(tmpPath, 'Test.html', /\/content\/(.*)/, '/content/');
120
165
  // You should be able to host files just with a path
121
166
  _Orator.addStaticRoute(__dirname+'/');
122
167
  _Orator.startWebServer
123
168
  (
124
169
  function ()
125
170
  {
126
- libSuperTest('http://localhost:8080/')
171
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
127
172
  .get('PIN')
128
173
  .end(
129
174
  function (pError, pResponse)
130
175
  {
131
176
  Expect(pResponse.text)
132
177
  .to.contain('PON');
133
- libSuperTest('http://localhost:8080/')
178
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
134
179
  .get('ThirdAPI')
135
180
  .end(
136
181
  function (pError, pResponse)
137
182
  {
138
- libSuperTest('http://localhost:8080/')
183
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
139
184
  .get('Test.css')
140
185
  .end(
141
186
  function (pError, pResponse)
@@ -143,13 +188,13 @@ suite
143
188
  _Orator.settings.Profiling.TraceLog = true;
144
189
  Expect(pResponse.text)
145
190
  .to.contain('50000px');
146
- libSuperTest('http://localhost:8080/')
191
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
147
192
  .get('content/')
148
193
  .end(
149
194
  function (pError, pResponse)
150
195
  {
151
196
  Expect(pResponse.text)
152
- .to.contain('MIT');
197
+ .to.contain('Um');
153
198
  fDone();
154
199
  }
155
200
  );
@@ -168,7 +213,7 @@ suite
168
213
  'promise routes should work',
169
214
  function(fDone)
170
215
  {
171
- libSuperTest('http://localhost:8080/')
216
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
172
217
  .get('PromiseAPI')
173
218
  .end(
174
219
  function (pError, pResponse)
@@ -186,14 +231,69 @@ suite
186
231
  'promise routes error handling',
187
232
  function(fDone)
188
233
  {
189
- libSuperTest('http://localhost:8080/')
234
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
190
235
  .get('PromiseAPIError')
191
236
  .end(
192
237
  function (pError, pResponse)
193
238
  {
239
+ Expect(capturedErr).to.exist;
240
+ Expect(capturedReq).to.exist;
241
+ Expect(capturedRes).to.exist;
194
242
  Expect(pResponse.text)
195
243
  .to.contain('error promise response');
196
244
 
245
+ return fDone();
246
+ }
247
+ );
248
+ }
249
+ );
250
+ test
251
+ (
252
+ 'unhandled error interception',
253
+ function(fDone)
254
+ {
255
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
256
+ .get('SyncBug')
257
+ .end(
258
+ function (pError, pResponse)
259
+ {
260
+ Expect(pError).to.not.exist;
261
+ Expect(capturedOriginalErr).to.exist;
262
+ Expect(capturedErr).to.exist;
263
+ Expect(capturedOriginalErr.statusCode).to.not.exist; // raw error
264
+ Expect(capturedErr.statusCode).to.equal(500); // wrapped to give reasonable http response
265
+ Expect(capturedReq).to.exist; // shows we called the custom error handler
266
+ Expect(capturedReq.RequestUUID).to.be.a('string'); // so we can log this from the handler
267
+ Expect(capturedRes).to.exist; // shows we called the custom error handler
268
+ Expect(capturedErr.message).to.contain('Cannot read property \'dog\'');
269
+ Expect(pResponse.text).to.contain('Cannot read property \'dog\'');
270
+
271
+ return fDone();
272
+ }
273
+ );
274
+ }
275
+ );
276
+ test
277
+ (
278
+ 'unhandled error interception async',
279
+ function(fDone)
280
+ {
281
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
282
+ .get('AsyncBug')
283
+ .end(
284
+ function (pError, pResponse)
285
+ {
286
+ Expect(pError).to.not.exist;
287
+ Expect(capturedOriginalErr).to.exist;
288
+ Expect(capturedErr).to.exist;
289
+ Expect(capturedOriginalErr.statusCode).to.not.exist; // raw error
290
+ Expect(capturedErr.statusCode).to.equal(500); // wrapped to give reasonable http response
291
+ Expect(capturedReq).to.exist; // shows we called the custom error handler
292
+ Expect(capturedReq.RequestUUID).to.be.a('string'); // so we can log this from the handler
293
+ Expect(capturedRes).to.exist; // shows we called the custom error handler
294
+ Expect(capturedErr.message).to.contain('Cannot read property \'dog\'');
295
+ Expect(pResponse.text).to.contain('Cannot read property \'dog\'');
296
+
197
297
  return fDone();
198
298
  }
199
299
  );
@@ -215,7 +315,7 @@ suite
215
315
  {
216
316
  Product: 'MockOratorInverted',
217
317
  ProductVersion: '0.0.0',
218
- APIServerPort: 8089,
318
+ APIServerPort: _MockSettings.APIServerPort - 10,
219
319
  LogStackTraces: false
220
320
  });
221
321
  var _OratorInverted = require('../source/Orator.js').new(_MockSettingsInvertedParameters);
@@ -249,11 +349,12 @@ suite
249
349
  }
250
350
  );
251
351
  _OratorInverted.startWebServer();
252
- libSuperTest('http://localhost:8089/')
352
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort - 10}/`)
253
353
  .get('PINGU')
254
354
  .end(
255
355
  function (pError, pResponse)
256
356
  {
357
+ _OratorInverted.stopWebServer();
257
358
  if (pError)
258
359
  {
259
360
  console.log('Error on Inverted Results: '+JSON.stringify(pError));
@@ -280,4 +381,4 @@ suite
280
381
  }
281
382
  );
282
383
  }
283
- );
384
+ );
@@ -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
  });
@@ -99,7 +99,9 @@ suite
99
99
  // Expect this to fail
100
100
  _Orator.addStaticRoute();
101
101
  // And you can specify a path for bonus
102
- _Orator.addStaticRoute(__dirname+'/../', 'LICENSE', /\/content\/(.*)/, '/content/');
102
+ var libPath = require('path');
103
+ var tmpPath = libPath.normalize(__dirname+'/');
104
+ _Orator.addStaticRoute(tmpPath, 'Test.html', /\/content\/(.*)/, '/content/');
103
105
  // You should be able to host files just with a path
104
106
  _Orator.addStaticRoute(__dirname+'/');
105
107
 
@@ -133,19 +135,19 @@ suite
133
135
  function(fDone)
134
136
  {
135
137
 
136
- libSuperTest('http://localhost:8080/')
138
+ libSuperTest('http://localhost:8099/')
137
139
  .get('PIN')
138
140
  .end(
139
141
  function (pError, pResponse)
140
142
  {
141
143
  Expect(pResponse.text)
142
144
  .to.contain('PON');
143
- libSuperTest('http://localhost:8080/')
145
+ libSuperTest('http://localhost:8099/')
144
146
  .get('ThirdAPI')
145
147
  .end(
146
148
  function (pError, pResponse)
147
149
  {
148
- libSuperTest('http://localhost:8080/')
150
+ libSuperTest('http://localhost:8099/')
149
151
  .get('Test.css')
150
152
  .end(
151
153
  function (pError, pResponse)
@@ -153,13 +155,13 @@ suite
153
155
  _Orator.settings.Profiling.TraceLog = true;
154
156
  Expect(pResponse.text)
155
157
  .to.contain('50000px');
156
- libSuperTest('http://localhost:8080/')
158
+ libSuperTest('http://localhost:8099/')
157
159
  .get('content/')
158
160
  .end(
159
161
  function (pError, pResponse)
160
162
  {
161
163
  Expect(pResponse.text)
162
- .to.contain('MIT');
164
+ .to.contain('Um');
163
165
 
164
166
  return fDone();
165
167
  }
@@ -189,7 +191,7 @@ suite
189
191
 
190
192
  for(var i=0; i<DISTRIBUTION_LOAD_COUNT; i++)
191
193
  {
192
- libSuperTest('http://localhost:8080/')
194
+ libSuperTest('http://localhost:8099/')
193
195
  .get('GetPID')
194
196
  .end(
195
197
  function (pError, pResponse)
@@ -228,4 +230,4 @@ suite
228
230
  );
229
231
  }
230
232
  }
231
- );
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,24 +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
- function (pRequest, pResponse, fNext)
70
+ '/PINataGOLo',
71
+ function (pRequest, pResponse, fNext)
64
72
  {
65
73
  throw new Error("Something absolutely dire has occurred.");
66
74
  pResponse.send('Loggso');
67
75
  fNext();
68
76
  }
69
77
  );
78
+ _Orator.webServer.get (
79
+ '/PINataGOLo_promise',
80
+ async function (pRequest, pResponse)
81
+ {
82
+ return Promise.reject("Something absolutely dire has occurred.");
83
+ }
84
+ );
70
85
  _Orator.startWebServer();
71
- libSuperTest('http://localhost:8085/')
86
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
72
87
  .get('PINGOLo')
73
88
  .end(
74
89
  function (pError, pResponse)
@@ -83,7 +98,7 @@ suite
83
98
  Expect(pResponse.text)
84
99
  .to.contain('Loggo');
85
100
  }
86
- libSuperTest('http://localhost:8085/')
101
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
87
102
  .get('PINataGOLo')
88
103
  .end(
89
104
  function (pError, pResponse)
@@ -95,6 +110,8 @@ suite
95
110
  }
96
111
  else
97
112
  {
113
+ Expect(pResponse.statusCode)
114
+ .to.equal(500);
98
115
  Expect(pResponse.text)
99
116
  .to.contain('dire');
100
117
  }
@@ -112,8 +129,35 @@ suite
112
129
  'uncaught exceptions should throw properly.',
113
130
  function(fDone)
114
131
  {
115
- libSuperTest('http://localhost:8085/')
132
+ libSuperTest(`http://localhost:${_MockSettings.APIServerPort}/`)
116
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')
117
161
  .end(
118
162
  function (pError, pResponse)
119
163
  {
@@ -135,4 +179,4 @@ suite
135
179
  }
136
180
  );
137
181
  }
138
- );
182
+ );
package/test/Test.html ADDED
@@ -0,0 +1 @@
1
+ <html><head></head><body>Um</body></html>
@@ -1,24 +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
- ],
21
- "internalConsoleOptions": "openOnSessionStart"
22
- }
23
- ]
24
- }
@@ -1,100 +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: 'MockOratorGRINDER',
18
- ProductVersion: '0.0.0',
19
- APIServerPort: 8082,
20
- Profiling: (
21
- {
22
- // Tracelog is just log-based request timing encapsulation.
23
- TraceLog: true,
24
-
25
- // These profiling settings determine if we generate cpu or call graphs
26
- Enabled: true,
27
- Folder: '/tmp/',
28
- // Currently supported profile types: CallGrinder or ChromeCPU
29
- Type: 'CallGrinder'
30
- })
31
-
32
- });
33
-
34
- suite
35
- (
36
- 'Orator',
37
- function()
38
- {
39
- var _Orator;
40
-
41
- setup
42
- (
43
- function()
44
- {
45
- _Orator = require('../source/Orator.js').new(_MockSettings);
46
- }
47
- );
48
-
49
- suite
50
- (
51
- 'Alternate (cachegrinder) Server Start',
52
- function()
53
- {
54
- test
55
- (
56
- 'simple routes should work',
57
- function(fDone)
58
- {
59
- _Orator.webServer.get (
60
- '/PING',
61
- function (pRequest, pResponse, fNext)
62
- {
63
- pResponse.send('PONG');
64
- fNext();
65
- }
66
- );
67
- _Orator.startWebServer(
68
- function()
69
- {
70
- libSuperTest('http://localhost:8082/')
71
- .get('PING')
72
- .end(
73
- function (pError, pResponse)
74
- {
75
- if (pError)
76
- {
77
- console.log('Error on Inverted Results: '+JSON.stringify(pError));
78
- Expect('CacheGrinder Request Error').to.equal('Nothing');
79
- }
80
- else
81
- {
82
- Expect(pResponse.text)
83
- .to.contain('PONG');
84
- }
85
- // This is just to exercise the lazy loaded cachegrinder module
86
- libSuperTest('http://localhost:8082/')
87
- .get('PING')
88
- .end(function (pError, pResponse) {fDone();});
89
- }
90
- );
91
- }
92
- );
93
- Expect(_Orator)
94
- .to.be.an('object', 'Orator should initialize as an object directly from the require statement.');
95
- }
96
- );
97
- }
98
- );
99
- }
100
- );
@@ -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,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: '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
- pResponse.send('PONGO');
63
- fNext();
64
- }
65
- );
66
- _Orator.startWebServer();
67
- libSuperTest('http://localhost:8081/')
68
- .get('PINGO')
69
- .end(
70
- function (pError, pResponse)
71
- {
72
- if (pError)
73
- {
74
- console.log('Error on Inverted Results: '+JSON.stringify(pError));
75
- Expect('Chrome Trace Request Error').to.equal('Nothing');
76
- }
77
- else
78
- {
79
- Expect(pResponse.text)
80
- .to.contain('PONGO');
81
- }
82
- fDone();
83
- }
84
- );
85
- }
86
- );
87
- }
88
- );
89
- }
90
- );