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 +5 -5
- package/src/server.js +27 -12
- package/test/integration/rawbody-integration-test.js +6 -3
- package/test/integration/rawresponse-integration-test.js +6 -3
- package/test/unit/api-test.js +1 -1
- package/test/unit/custom-jwt-validation.js +6 -0
- package/test/unit/server-test.js +187 -0
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": "
|
|
14
|
-
"test:integration": "
|
|
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.
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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.
|
|
26
|
+
if (testServer && testServer.server) {
|
|
27
27
|
await new Promise((resolve) => {
|
|
28
|
-
if (testServer.
|
|
29
|
-
testServer.
|
|
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.
|
|
26
|
+
if (testServer && testServer.server) {
|
|
27
27
|
await new Promise((resolve) => {
|
|
28
|
-
if (testServer.
|
|
29
|
-
testServer.
|
|
28
|
+
if (testServer.server.listening) {
|
|
29
|
+
testServer.server.closeAllConnections();
|
|
30
|
+
testServer.server.close(() => {
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
30
33
|
} else {
|
|
31
34
|
resolve();
|
|
32
35
|
}
|
package/test/unit/api-test.js
CHANGED
|
@@ -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
|
|
package/test/unit/server-test.js
CHANGED
|
@@ -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
|
});
|