impulse-api 3.0.10 → 3.0.11

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/package.json CHANGED
@@ -10,11 +10,11 @@
10
10
  "scripts": {
11
11
  "lint": "eslint --ignore-path .gitignore .",
12
12
  "test": "npm run test:unit && npm run test:integration",
13
- "test:unit": "nyc --reporter=lcov --reporter=text-summary mocha test/unit/**/*.js",
14
- "test:integration": "nyc --reporter=lcov --reporter=text-summary mocha 'test/integration/**/*-test.js'",
13
+ "test:unit": "mocha test/unit/**/*.js",
14
+ "test:integration": "mocha test/integration/**/*-test.js",
15
15
  "test-server": "node ./test/integration/test-server.js"
16
16
  },
17
- "version": "3.0.10",
17
+ "version": "3.0.11",
18
18
  "engines": {
19
19
  "node": ">=22"
20
20
  },
@@ -24,7 +24,7 @@
24
24
  "express": "4.21.2",
25
25
  "express-fileupload": "1.5.1",
26
26
  "http-errors": "2.0.0",
27
- "jsonwebtoken": "8.5.1",
27
+ "jsonwebtoken": "^9.0.2",
28
28
  "lodash": "4.17.21",
29
29
  "morgan": "1.10.0",
30
30
  "path-to-regexp": "0.1.12"
@@ -32,7 +32,7 @@
32
32
  "devDependencies": {
33
33
  "eslint": "^9.23.0",
34
34
  "eslint-plugin-import": "^2.31.0",
35
- "nyc": "^17.1.0",
35
+ "mocha": "^10.2.0",
36
36
  "pre-commit": "^1.2.2",
37
37
  "sinon": "4.4.6"
38
38
  },
package/src/server.js CHANGED
@@ -71,8 +71,8 @@ class Server {
71
71
  return;
72
72
  }
73
73
 
74
- // Start the HTTP server
75
- this.http.listen(this.port, () => {
74
+ // Start the HTTP server - store the server instance so we can close it later
75
+ this.server = this.http.listen(this.port, () => {
76
76
  console.log('Server initialized:');
77
77
  console.log(`-- name: ${this.name}`);
78
78
  console.log(`-- version: ${this.version}`);
@@ -176,7 +176,13 @@ class Server {
176
176
  }).bind(req)
177
177
  };
178
178
 
179
- function sendResponse(error, response) {
179
+ function sendResponse(error, response, headers) {
180
+ // Set headers if provided (for rawResponse mode)
181
+ if (headers && typeof headers === 'object') {
182
+ Object.keys(headers).forEach(key => {
183
+ res.setHeader(key, headers[key]);
184
+ });
185
+ }
180
186
  // Handle raw responses - send Buffer/string as-is without JSON serialization
181
187
  // When rawResponse is true, only Buffer and string are sent raw via res.end().
182
188
  // All other types fall through to default JSON serialization below.
@@ -249,7 +255,7 @@ class Server {
249
255
  success: false,
250
256
  error: err
251
257
  });
252
- return;
258
+ return Promise.resolve();
253
259
  }
254
260
 
255
261
  try {
@@ -278,7 +284,7 @@ class Server {
278
284
  success: false,
279
285
  error
280
286
  });
281
- return;
287
+ return Promise.resolve();
282
288
  }
283
289
  }
284
290
 
@@ -289,7 +295,7 @@ class Server {
289
295
  if (route.applicationAuth) {
290
296
  if (!key || key !== this.appKey) {
291
297
  sendResponse(this.errors.UnauthorizedUser());
292
- return;
298
+ return Promise.resolve();
293
299
  }
294
300
  }
295
301
 
@@ -307,7 +313,7 @@ class Server {
307
313
  data = this.buildParameters(paramsForInputs, route.inputs);
308
314
  } catch (error) {
309
315
  sendResponse(error);
310
- return;
316
+ return Promise.resolve();
311
317
  }
312
318
  }
313
319
 
@@ -321,7 +327,7 @@ class Server {
321
327
  data.headers = this.buildParameters(req.headers, route.headers);
322
328
  } catch (error) {
323
329
  sendResponse(error);
324
- return;
330
+ return Promise.resolve();
325
331
  }
326
332
  }
327
333
 
@@ -332,10 +338,19 @@ class Server {
332
338
  data.decoded = decoded;
333
339
  data._host = req.get('origin') || req.get('host')
334
340
 
335
- route.run.bind(routeContext)(this.container, data, ((responseError, response) => {
336
- sendResponse(responseError, response);
337
- return;
338
- }).bind(this));
341
+ return new Promise((resolve) => {
342
+ try {
343
+ route.run.bind(routeContext)(this.container, data, ((responseError, response, headers) => {
344
+ sendResponse(responseError, response, headers);
345
+ resolve();
346
+ return;
347
+ }).bind(this));
348
+ } catch (error) {
349
+ // If route.run throws synchronously, handle it
350
+ sendResponse(error);
351
+ resolve();
352
+ }
353
+ });
339
354
  }
340
355
 
341
356
  buildParameters(params, inputs) {
@@ -23,10 +23,13 @@ describe('rawBody HTTP Integration', () => {
23
23
 
24
24
  after(async () => {
25
25
  // Clean up test server
26
- if (testServer && testServer.http) {
26
+ if (testServer && testServer.server) {
27
27
  await new Promise((resolve) => {
28
- if (testServer.http.listening) {
29
- testServer.http.close(() => resolve());
28
+ if (testServer.server.listening) {
29
+ testServer.server.closeAllConnections();
30
+ testServer.server.close(() => {
31
+ resolve();
32
+ });
30
33
  } else {
31
34
  resolve();
32
35
  }
@@ -23,10 +23,13 @@ describe('rawResponse HTTP Integration', () => {
23
23
 
24
24
  after(async () => {
25
25
  // Clean up test server
26
- if (testServer && testServer.http) {
26
+ if (testServer && testServer.server) {
27
27
  await new Promise((resolve) => {
28
- if (testServer.http.listening) {
29
- testServer.http.close(() => resolve());
28
+ if (testServer.server.listening) {
29
+ testServer.server.closeAllConnections();
30
+ testServer.server.close(() => {
31
+ resolve();
32
+ });
30
33
  } else {
31
34
  resolve();
32
35
  }
@@ -3,7 +3,7 @@ const assert = require('assert');
3
3
 
4
4
  describe('server-test', () => {
5
5
  describe('instantiation', () => {
6
- it('should expoes the Errors functions', () => {
6
+ it('should expose the Errors functions', () => {
7
7
  assert(Api.Errors !== undefined);
8
8
  });
9
9
  });
@@ -37,6 +37,7 @@ describe('Custom JWT Validation', () => {
37
37
  routeDir: testRouteDir,
38
38
  secretKey: 'test-secret',
39
39
  tokenValidator: customValidator,
40
+ env: 'test',
40
41
  services: {
41
42
  testService: { name: 'test' }
42
43
  }
@@ -77,6 +78,7 @@ exports.testRoute = {
77
78
  name: 'test-api',
78
79
  routeDir: testRouteDir,
79
80
  secretKey: 'test-secret',
81
+ env: 'test',
80
82
  services: {}
81
83
  };
82
84
 
@@ -128,6 +130,7 @@ exports.testRoute = {
128
130
  routeDir: testRouteDir,
129
131
  secretKey: 'test-secret',
130
132
  tokenValidator: serverValidator,
133
+ env: 'test',
131
134
  services: {}
132
135
  };
133
136
 
@@ -181,6 +184,7 @@ exports.testRoute = {
181
184
  routeDir: testRouteDir,
182
185
  secretKey: 'test-secret',
183
186
  tokenValidator: serverValidator,
187
+ env: 'test',
184
188
  services: {}
185
189
  };
186
190
 
@@ -222,6 +226,7 @@ exports.testRoute = {
222
226
  routeDir: testRouteDir,
223
227
  secretKey: 'test-secret',
224
228
  tokenValidator: failingValidator,
229
+ env: 'test',
225
230
  services: {}
226
231
  };
227
232
 
@@ -270,6 +275,7 @@ exports.testRoute = {
270
275
  name: 'test-api',
271
276
  routeDir: testRouteDir,
272
277
  secretKey: 'test-secret',
278
+ env: 'test',
273
279
  services: {}
274
280
  };
275
281
 
@@ -935,4 +935,191 @@ describe('server-test', () => {
935
935
  assert.strictEqual(res.statusCode, 200);
936
936
  });
937
937
  });
938
+
939
+ describe('response headers functionality', () => {
940
+ it('should set headers when passed via next() with rawResponse: true', async () => {
941
+ const Api = new Server({
942
+ name: 'test-Server',
943
+ routeDir: './test-routes',
944
+ port: 4000,
945
+ env: 'test',
946
+ services: {}
947
+ });
948
+
949
+ const testBody = { test: 'data' };
950
+ const testResponse = Buffer.from('raw response data');
951
+ const testHeaders = {
952
+ 'Content-Type': 'application/octet-stream',
953
+ 'X-Custom-Header': 'custom-value',
954
+ 'Cache-Control': 'no-cache'
955
+ };
956
+ const req = {
957
+ body: testBody,
958
+ query: {},
959
+ params: {},
960
+ files: {},
961
+ headers: {},
962
+ get: (header) => {
963
+ if (header === 'origin') return 'http://localhost:4000';
964
+ if (header === 'host') return 'localhost:4000';
965
+ return null;
966
+ }
967
+ };
968
+ const res = {
969
+ statusCode: 200,
970
+ headers: {},
971
+ setHeader: (key, value) => {
972
+ res.headers[key] = value;
973
+ },
974
+ status: (code) => {
975
+ res.statusCode = code;
976
+ return res;
977
+ },
978
+ send: (data) => {
979
+ res.sentData = data;
980
+ },
981
+ end: (data) => {
982
+ res.sentData = data;
983
+ }
984
+ };
985
+
986
+ const route = {
987
+ name: 'test-headers-raw-response',
988
+ method: 'post',
989
+ endpoint: '/test',
990
+ rawResponse: true,
991
+ run: (services, inputs, next) => {
992
+ next(null, testResponse, testHeaders);
993
+ }
994
+ };
995
+
996
+ await Api.preprocessor(route, req, res);
997
+ assert.strictEqual(res.statusCode, 200);
998
+ assert.strictEqual(Buffer.isBuffer(res.sentData), true);
999
+ assert.deepStrictEqual(res.sentData, testResponse);
1000
+ assert.strictEqual(res.headers['Content-Type'], 'application/octet-stream');
1001
+ assert.strictEqual(res.headers['X-Custom-Header'], 'custom-value');
1002
+ assert.strictEqual(res.headers['Cache-Control'], 'no-cache');
1003
+ });
1004
+
1005
+ it('should set headers when passed via next() with regular JSON response', async () => {
1006
+ const Api = new Server({
1007
+ name: 'test-Server',
1008
+ routeDir: './test-routes',
1009
+ port: 4000,
1010
+ env: 'test',
1011
+ services: {}
1012
+ });
1013
+
1014
+ const testBody = { test: 'data' };
1015
+ const testResponse = { success: true, message: 'test response' };
1016
+ const testHeaders = {
1017
+ 'Content-Type': 'application/json',
1018
+ 'X-Request-ID': '12345',
1019
+ 'X-Rate-Limit': '100'
1020
+ };
1021
+ const req = {
1022
+ body: testBody,
1023
+ query: {},
1024
+ params: {},
1025
+ files: {},
1026
+ headers: {},
1027
+ get: (header) => {
1028
+ if (header === 'origin') return 'http://localhost:4000';
1029
+ if (header === 'host') return 'localhost:4000';
1030
+ return null;
1031
+ }
1032
+ };
1033
+ const res = {
1034
+ statusCode: 200,
1035
+ headers: {},
1036
+ setHeader: (key, value) => {
1037
+ res.headers[key] = value;
1038
+ },
1039
+ status: (code) => {
1040
+ res.statusCode = code;
1041
+ return res;
1042
+ },
1043
+ send: (data) => {
1044
+ res.sentData = data;
1045
+ },
1046
+ end: (data) => {
1047
+ res.sentData = data;
1048
+ }
1049
+ };
1050
+
1051
+ const route = {
1052
+ name: 'test-headers-json-response',
1053
+ method: 'post',
1054
+ endpoint: '/test',
1055
+ rawResponse: false,
1056
+ run: (services, inputs, next) => {
1057
+ next(null, testResponse, testHeaders);
1058
+ }
1059
+ };
1060
+
1061
+ await Api.preprocessor(route, req, res);
1062
+ assert.strictEqual(res.statusCode, 200);
1063
+ assert.deepStrictEqual(res.sentData, testResponse);
1064
+ assert.strictEqual(res.headers['Content-Type'], 'application/json');
1065
+ assert.strictEqual(res.headers['X-Request-ID'], '12345');
1066
+ assert.strictEqual(res.headers['X-Rate-Limit'], '100');
1067
+ });
1068
+
1069
+ it('should handle routes without headers parameter', async () => {
1070
+ const Api = new Server({
1071
+ name: 'test-Server',
1072
+ routeDir: './test-routes',
1073
+ port: 4000,
1074
+ env: 'test',
1075
+ services: {}
1076
+ });
1077
+
1078
+ const testBody = { test: 'data' };
1079
+ const testResponse = { success: true };
1080
+ const req = {
1081
+ body: testBody,
1082
+ query: {},
1083
+ params: {},
1084
+ files: {},
1085
+ headers: {},
1086
+ get: (header) => {
1087
+ if (header === 'origin') return 'http://localhost:4000';
1088
+ if (header === 'host') return 'localhost:4000';
1089
+ return null;
1090
+ }
1091
+ };
1092
+ const res = {
1093
+ statusCode: 200,
1094
+ headers: {},
1095
+ setHeader: (key, value) => {
1096
+ res.headers[key] = value;
1097
+ },
1098
+ status: (code) => {
1099
+ res.statusCode = code;
1100
+ return res;
1101
+ },
1102
+ send: (data) => {
1103
+ res.sentData = data;
1104
+ }
1105
+ };
1106
+
1107
+ const route = {
1108
+ name: 'test-no-headers',
1109
+ method: 'post',
1110
+ endpoint: '/test',
1111
+ rawResponse: false,
1112
+ run: (services, inputs, next) => {
1113
+ // Call next without headers parameter (backward compatibility)
1114
+ next(null, testResponse);
1115
+ }
1116
+ };
1117
+
1118
+ await Api.preprocessor(route, req, res);
1119
+ assert.strictEqual(res.statusCode, 200);
1120
+ assert.deepStrictEqual(res.sentData, testResponse);
1121
+ // No headers should be set
1122
+ assert.strictEqual(Object.keys(res.headers).length, 0);
1123
+ });
1124
+ });
938
1125
  });