impulse-api 3.0.9 → 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 +61 -12
- package/test/integration/rawbody-integration-test.js +6 -3
- package/test/integration/rawresponse-integration-test.js +262 -0
- package/test/integration/routes/raw-response.js +65 -0
- package/test/unit/api-test.js +1 -1
- package/test/unit/custom-jwt-validation.js +6 -0
- package/test/unit/server-test.js +347 -1
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,47 @@ 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
|
+
}
|
|
186
|
+
// Handle raw responses - send Buffer/string as-is without JSON serialization
|
|
187
|
+
// When rawResponse is true, only Buffer and string are sent raw via res.end().
|
|
188
|
+
// All other types fall through to default JSON serialization below.
|
|
189
|
+
if (route.rawResponse === true) {
|
|
190
|
+
if (response && !error) {
|
|
191
|
+
// Successful response - send raw if Buffer or string
|
|
192
|
+
if (Buffer.isBuffer(response) || typeof response === 'string') {
|
|
193
|
+
res.status(200).end(response);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Not raw-compatible, fall through to default JSON serialization
|
|
197
|
+
} else if (error && !response) {
|
|
198
|
+
// Error response - send raw if Buffer or string
|
|
199
|
+
if (Buffer.isBuffer(error) || typeof error === 'string') {
|
|
200
|
+
const statusCode = (typeof error === 'number' && !isNaN(error)) ? error : 400;
|
|
201
|
+
res.status(statusCode).end(error);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Not raw-compatible, fall through to default JSON serialization
|
|
205
|
+
} else if (error && response) {
|
|
206
|
+
// Status code with response (redirects, etc.)
|
|
207
|
+
if ((error === 302 || error === 301 || error === 307 || error === 308) && response.Location) {
|
|
208
|
+
res.status(error).header('Location', response.Location).end();
|
|
209
|
+
return;
|
|
210
|
+
} else if (Buffer.isBuffer(response) || typeof response === 'string') {
|
|
211
|
+
res.status(error).end(response);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Not raw-compatible, fall through to default JSON serialization
|
|
215
|
+
}
|
|
216
|
+
// If rawResponse is true but response is not Buffer/string, fall through to default
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Default JSON serialization behavior
|
|
180
220
|
if (error instanceof Error) {
|
|
181
221
|
res.status(error.statusCode ? error.statusCode : 400).send(error);
|
|
182
222
|
} else if (error && !response) {
|
|
@@ -215,7 +255,7 @@ class Server {
|
|
|
215
255
|
success: false,
|
|
216
256
|
error: err
|
|
217
257
|
});
|
|
218
|
-
return;
|
|
258
|
+
return Promise.resolve();
|
|
219
259
|
}
|
|
220
260
|
|
|
221
261
|
try {
|
|
@@ -244,7 +284,7 @@ class Server {
|
|
|
244
284
|
success: false,
|
|
245
285
|
error
|
|
246
286
|
});
|
|
247
|
-
return;
|
|
287
|
+
return Promise.resolve();
|
|
248
288
|
}
|
|
249
289
|
}
|
|
250
290
|
|
|
@@ -255,7 +295,7 @@ class Server {
|
|
|
255
295
|
if (route.applicationAuth) {
|
|
256
296
|
if (!key || key !== this.appKey) {
|
|
257
297
|
sendResponse(this.errors.UnauthorizedUser());
|
|
258
|
-
return;
|
|
298
|
+
return Promise.resolve();
|
|
259
299
|
}
|
|
260
300
|
}
|
|
261
301
|
|
|
@@ -273,7 +313,7 @@ class Server {
|
|
|
273
313
|
data = this.buildParameters(paramsForInputs, route.inputs);
|
|
274
314
|
} catch (error) {
|
|
275
315
|
sendResponse(error);
|
|
276
|
-
return;
|
|
316
|
+
return Promise.resolve();
|
|
277
317
|
}
|
|
278
318
|
}
|
|
279
319
|
|
|
@@ -287,7 +327,7 @@ class Server {
|
|
|
287
327
|
data.headers = this.buildParameters(req.headers, route.headers);
|
|
288
328
|
} catch (error) {
|
|
289
329
|
sendResponse(error);
|
|
290
|
-
return;
|
|
330
|
+
return Promise.resolve();
|
|
291
331
|
}
|
|
292
332
|
}
|
|
293
333
|
|
|
@@ -298,10 +338,19 @@ class Server {
|
|
|
298
338
|
data.decoded = decoded;
|
|
299
339
|
data._host = req.get('origin') || req.get('host')
|
|
300
340
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
});
|
|
305
354
|
}
|
|
306
355
|
|
|
307
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
|
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const Server = require('../../src/server');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
describe('rawResponse HTTP Integration', () => {
|
|
7
|
+
let testServer;
|
|
8
|
+
let testRouteDir;
|
|
9
|
+
let testPort = 9999;
|
|
10
|
+
|
|
11
|
+
before(async () => {
|
|
12
|
+
testRouteDir = path.join(__dirname, 'routes');
|
|
13
|
+
// Create and start test server once for all tests (use 'dev' env so server actually starts)
|
|
14
|
+
testServer = new Server({
|
|
15
|
+
name: 'test-server',
|
|
16
|
+
routeDir: testRouteDir,
|
|
17
|
+
port: testPort,
|
|
18
|
+
env: 'dev',
|
|
19
|
+
services: {}
|
|
20
|
+
});
|
|
21
|
+
await testServer.init();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
after(async () => {
|
|
25
|
+
// Clean up test server
|
|
26
|
+
if (testServer && testServer.server) {
|
|
27
|
+
await new Promise((resolve) => {
|
|
28
|
+
if (testServer.server.listening) {
|
|
29
|
+
testServer.server.closeAllConnections();
|
|
30
|
+
testServer.server.close(() => {
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
resolve();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should send raw Buffer response when rawResponse is true', async () => {
|
|
41
|
+
const response = await new Promise((resolve, reject) => {
|
|
42
|
+
const options = {
|
|
43
|
+
hostname: 'localhost',
|
|
44
|
+
port: testPort,
|
|
45
|
+
path: '/raw-response-buffer',
|
|
46
|
+
method: 'GET'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const req = http.request(options, (res) => {
|
|
50
|
+
let data = Buffer.alloc(0);
|
|
51
|
+
res.on('data', (chunk) => {
|
|
52
|
+
data = Buffer.concat([data, chunk]);
|
|
53
|
+
});
|
|
54
|
+
res.on('end', () => {
|
|
55
|
+
resolve({
|
|
56
|
+
statusCode: res.statusCode,
|
|
57
|
+
body: data,
|
|
58
|
+
headers: res.headers
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
req.end();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
assert.strictEqual(response.statusCode, 200);
|
|
68
|
+
assert.strictEqual(Buffer.isBuffer(response.body), true);
|
|
69
|
+
assert.strictEqual(response.body.toString(), 'raw buffer response data');
|
|
70
|
+
// Should not have JSON content-type since it's raw
|
|
71
|
+
assert.strictEqual(response.headers['content-type'], undefined);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should send raw string response when rawResponse is true', async () => {
|
|
75
|
+
const response = await new Promise((resolve, reject) => {
|
|
76
|
+
const options = {
|
|
77
|
+
hostname: 'localhost',
|
|
78
|
+
port: testPort,
|
|
79
|
+
path: '/raw-response-string',
|
|
80
|
+
method: 'GET'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const req = http.request(options, (res) => {
|
|
84
|
+
let data = '';
|
|
85
|
+
res.on('data', (chunk) => {
|
|
86
|
+
data += chunk;
|
|
87
|
+
});
|
|
88
|
+
res.on('end', () => {
|
|
89
|
+
resolve({
|
|
90
|
+
statusCode: res.statusCode,
|
|
91
|
+
body: data,
|
|
92
|
+
headers: res.headers
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
req.on('error', reject);
|
|
98
|
+
req.end();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.strictEqual(response.statusCode, 200);
|
|
102
|
+
assert.strictEqual(typeof response.body, 'string');
|
|
103
|
+
assert.strictEqual(response.body, 'raw string response data');
|
|
104
|
+
// Should not have JSON content-type since it's raw
|
|
105
|
+
assert.strictEqual(response.headers['content-type'], undefined);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should send JSON serialized response when rawResponse is false', async () => {
|
|
109
|
+
const response = await new Promise((resolve, reject) => {
|
|
110
|
+
const options = {
|
|
111
|
+
hostname: 'localhost',
|
|
112
|
+
port: testPort,
|
|
113
|
+
path: '/json-response',
|
|
114
|
+
method: 'GET'
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const req = http.request(options, (res) => {
|
|
118
|
+
let data = '';
|
|
119
|
+
res.on('data', (chunk) => {
|
|
120
|
+
data += chunk;
|
|
121
|
+
});
|
|
122
|
+
res.on('end', () => {
|
|
123
|
+
resolve({
|
|
124
|
+
statusCode: res.statusCode,
|
|
125
|
+
body: data,
|
|
126
|
+
headers: res.headers
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
req.on('error', reject);
|
|
132
|
+
req.end();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
assert.strictEqual(response.statusCode, 200);
|
|
136
|
+
const responseBody = JSON.parse(response.body);
|
|
137
|
+
assert.strictEqual(responseBody.success, true);
|
|
138
|
+
assert.strictEqual(responseBody.message, 'This is a JSON response');
|
|
139
|
+
assert.deepStrictEqual(responseBody.data, { test: 'value' });
|
|
140
|
+
// Should have JSON content-type when serialized
|
|
141
|
+
assert.ok(response.headers['content-type'].includes('application/json'));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should send JSON serialized response when rawResponse is true but object is returned', async () => {
|
|
145
|
+
const response = await new Promise((resolve, reject) => {
|
|
146
|
+
const options = {
|
|
147
|
+
hostname: 'localhost',
|
|
148
|
+
port: testPort,
|
|
149
|
+
path: '/raw-response-object',
|
|
150
|
+
method: 'GET'
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const req = http.request(options, (res) => {
|
|
154
|
+
let data = '';
|
|
155
|
+
res.on('data', (chunk) => {
|
|
156
|
+
data += chunk;
|
|
157
|
+
});
|
|
158
|
+
res.on('end', () => {
|
|
159
|
+
resolve({
|
|
160
|
+
statusCode: res.statusCode,
|
|
161
|
+
body: data,
|
|
162
|
+
headers: res.headers
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
req.on('error', reject);
|
|
168
|
+
req.end();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
assert.strictEqual(response.statusCode, 200);
|
|
172
|
+
// Even with rawResponse: true, objects should fall through to JSON serialization
|
|
173
|
+
const responseBody = JSON.parse(response.body);
|
|
174
|
+
assert.strictEqual(responseBody.success, true);
|
|
175
|
+
assert.strictEqual(responseBody.message, 'This should be JSON serialized');
|
|
176
|
+
// Should have JSON content-type when serialized
|
|
177
|
+
assert.ok(response.headers['content-type'].includes('application/json'));
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle raw response with route parameters', async () => {
|
|
181
|
+
const testBody = JSON.stringify({ prefix: 'PREFIX' });
|
|
182
|
+
const response = await new Promise((resolve, reject) => {
|
|
183
|
+
const options = {
|
|
184
|
+
hostname: 'localhost',
|
|
185
|
+
port: testPort,
|
|
186
|
+
path: '/raw-response-params',
|
|
187
|
+
method: 'POST',
|
|
188
|
+
headers: {
|
|
189
|
+
'Content-Type': 'application/json',
|
|
190
|
+
'Content-Length': Buffer.byteLength(testBody)
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const req = http.request(options, (res) => {
|
|
195
|
+
let data = '';
|
|
196
|
+
res.on('data', (chunk) => {
|
|
197
|
+
data += chunk;
|
|
198
|
+
});
|
|
199
|
+
res.on('end', () => {
|
|
200
|
+
resolve({
|
|
201
|
+
statusCode: res.statusCode,
|
|
202
|
+
body: data,
|
|
203
|
+
headers: res.headers
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
req.on('error', reject);
|
|
209
|
+
req.write(testBody);
|
|
210
|
+
req.end();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
assert.strictEqual(response.statusCode, 200);
|
|
214
|
+
assert.strictEqual(typeof response.body, 'string');
|
|
215
|
+
assert.strictEqual(response.body, 'PREFIX: raw response with params');
|
|
216
|
+
// Should not have JSON content-type since it's raw
|
|
217
|
+
assert.strictEqual(response.headers['content-type'], undefined);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle both rawBody and rawResponse together', async () => {
|
|
221
|
+
const testBody = 'raw request body';
|
|
222
|
+
const response = await new Promise((resolve, reject) => {
|
|
223
|
+
const options = {
|
|
224
|
+
hostname: 'localhost',
|
|
225
|
+
port: testPort,
|
|
226
|
+
path: '/webhook',
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: {
|
|
229
|
+
'Content-Type': 'application/json',
|
|
230
|
+
'Content-Length': Buffer.byteLength(testBody)
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const req = http.request(options, (res) => {
|
|
235
|
+
let data = '';
|
|
236
|
+
res.on('data', (chunk) => {
|
|
237
|
+
data += chunk;
|
|
238
|
+
});
|
|
239
|
+
res.on('end', () => {
|
|
240
|
+
resolve({
|
|
241
|
+
statusCode: res.statusCode,
|
|
242
|
+
body: data,
|
|
243
|
+
headers: res.headers
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
req.on('error', reject);
|
|
249
|
+
req.write(testBody);
|
|
250
|
+
req.end();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
assert.strictEqual(response.statusCode, 200);
|
|
254
|
+
// The webhook route returns JSON (doesn't have rawResponse), so verify JSON
|
|
255
|
+
const responseBody = JSON.parse(response.body);
|
|
256
|
+
assert.strictEqual(responseBody.success, true);
|
|
257
|
+
assert.strictEqual(responseBody.isBuffer, true);
|
|
258
|
+
// Original functionality: rawBody works for requests
|
|
259
|
+
assert.strictEqual(responseBody.rawBodyString, testBody);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
exports.rawResponseBuffer = {
|
|
2
|
+
name: 'rawResponseBuffer',
|
|
3
|
+
method: 'get',
|
|
4
|
+
endpoint: '/raw-response-buffer',
|
|
5
|
+
rawResponse: true,
|
|
6
|
+
run: (services, inputs, next) => {
|
|
7
|
+
const buffer = Buffer.from('raw buffer response data');
|
|
8
|
+
next(null, buffer);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
exports.rawResponseString = {
|
|
13
|
+
name: 'rawResponseString',
|
|
14
|
+
method: 'get',
|
|
15
|
+
endpoint: '/raw-response-string',
|
|
16
|
+
rawResponse: true,
|
|
17
|
+
run: (services, inputs, next) => {
|
|
18
|
+
next(null, 'raw string response data');
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
exports.rawResponseWithParams = {
|
|
23
|
+
name: 'rawResponseWithParams',
|
|
24
|
+
method: 'post',
|
|
25
|
+
endpoint: '/raw-response-params',
|
|
26
|
+
rawResponse: true,
|
|
27
|
+
inputs: {
|
|
28
|
+
prefix: {
|
|
29
|
+
required: true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
run: (services, inputs, next) => {
|
|
33
|
+
const response = `${inputs.prefix}: raw response with params`;
|
|
34
|
+
next(null, response);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
exports.jsonResponse = {
|
|
39
|
+
name: 'jsonResponse',
|
|
40
|
+
method: 'get',
|
|
41
|
+
endpoint: '/json-response',
|
|
42
|
+
rawResponse: false,
|
|
43
|
+
run: (services, inputs, next) => {
|
|
44
|
+
next(null, {
|
|
45
|
+
success: true,
|
|
46
|
+
message: 'This is a JSON response',
|
|
47
|
+
data: { test: 'value' }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
exports.rawResponseObject = {
|
|
53
|
+
name: 'rawResponseObject',
|
|
54
|
+
method: 'get',
|
|
55
|
+
endpoint: '/raw-response-object',
|
|
56
|
+
rawResponse: true,
|
|
57
|
+
run: (services, inputs, next) => {
|
|
58
|
+
// Even with rawResponse: true, objects should fall through to JSON serialization
|
|
59
|
+
next(null, {
|
|
60
|
+
success: true,
|
|
61
|
+
message: 'This should be JSON serialized'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
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
|
@@ -596,7 +596,166 @@ describe('server-test', () => {
|
|
|
596
596
|
assert.strictEqual(res.statusCode, 200);
|
|
597
597
|
});
|
|
598
598
|
|
|
599
|
-
it('should
|
|
599
|
+
it('should send raw response as Buffer when route.rawResponse is true', async () => {
|
|
600
|
+
const Api = new Server({
|
|
601
|
+
name: 'test-Server',
|
|
602
|
+
routeDir: './test-routes',
|
|
603
|
+
port: 4000,
|
|
604
|
+
env: 'test',
|
|
605
|
+
services: {}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const testBody = { test: 'data' };
|
|
609
|
+
const testResponse = Buffer.from('raw response data');
|
|
610
|
+
const req = {
|
|
611
|
+
body: testBody,
|
|
612
|
+
query: {},
|
|
613
|
+
params: {},
|
|
614
|
+
files: {},
|
|
615
|
+
headers: {},
|
|
616
|
+
get: (header) => {
|
|
617
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
618
|
+
if (header === 'host') return 'localhost:4000';
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
const res = {
|
|
623
|
+
statusCode: 200,
|
|
624
|
+
status: (code) => {
|
|
625
|
+
res.statusCode = code;
|
|
626
|
+
return res;
|
|
627
|
+
},
|
|
628
|
+
send: (data) => {
|
|
629
|
+
res.sentData = data;
|
|
630
|
+
},
|
|
631
|
+
end: (data) => {
|
|
632
|
+
res.sentData = data;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const route = {
|
|
637
|
+
name: 'test-raw-response',
|
|
638
|
+
method: 'post',
|
|
639
|
+
endpoint: '/test',
|
|
640
|
+
rawResponse: true,
|
|
641
|
+
run: (services, inputs, next) => {
|
|
642
|
+
next(null, testResponse);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
await Api.preprocessor(route, req, res);
|
|
647
|
+
assert.strictEqual(res.statusCode, 200);
|
|
648
|
+
assert.strictEqual(Buffer.isBuffer(res.sentData), true);
|
|
649
|
+
assert.deepStrictEqual(res.sentData, testResponse);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('should send raw response as string when route.rawResponse is true', async () => {
|
|
653
|
+
const Api = new Server({
|
|
654
|
+
name: 'test-Server',
|
|
655
|
+
routeDir: './test-routes',
|
|
656
|
+
port: 4000,
|
|
657
|
+
env: 'test',
|
|
658
|
+
services: {}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
const testBody = { test: 'data' };
|
|
662
|
+
const testResponse = 'raw string response';
|
|
663
|
+
const req = {
|
|
664
|
+
body: testBody,
|
|
665
|
+
query: {},
|
|
666
|
+
params: {},
|
|
667
|
+
files: {},
|
|
668
|
+
headers: {},
|
|
669
|
+
get: (header) => {
|
|
670
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
671
|
+
if (header === 'host') return 'localhost:4000';
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
const res = {
|
|
676
|
+
statusCode: 200,
|
|
677
|
+
status: (code) => {
|
|
678
|
+
res.statusCode = code;
|
|
679
|
+
return res;
|
|
680
|
+
},
|
|
681
|
+
send: (data) => {
|
|
682
|
+
res.sentData = data;
|
|
683
|
+
},
|
|
684
|
+
end: (data) => {
|
|
685
|
+
res.sentData = data;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const route = {
|
|
690
|
+
name: 'test-raw-response-string',
|
|
691
|
+
method: 'post',
|
|
692
|
+
endpoint: '/test',
|
|
693
|
+
rawResponse: true,
|
|
694
|
+
run: (services, inputs, next) => {
|
|
695
|
+
next(null, testResponse);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
await Api.preprocessor(route, req, res);
|
|
700
|
+
assert.strictEqual(res.statusCode, 200);
|
|
701
|
+
assert.strictEqual(typeof res.sentData, 'string');
|
|
702
|
+
assert.strictEqual(res.sentData, testResponse);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should JSON serialize response when route.rawResponse is false', async () => {
|
|
706
|
+
const Api = new Server({
|
|
707
|
+
name: 'test-Server',
|
|
708
|
+
routeDir: './test-routes',
|
|
709
|
+
port: 4000,
|
|
710
|
+
env: 'test',
|
|
711
|
+
services: {}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
const testBody = { test: 'data' };
|
|
715
|
+
const testResponse = { success: true, data: 'test' };
|
|
716
|
+
const req = {
|
|
717
|
+
body: testBody,
|
|
718
|
+
query: {},
|
|
719
|
+
params: {},
|
|
720
|
+
files: {},
|
|
721
|
+
headers: {},
|
|
722
|
+
get: (header) => {
|
|
723
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
724
|
+
if (header === 'host') return 'localhost:4000';
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const res = {
|
|
729
|
+
statusCode: 200,
|
|
730
|
+
status: (code) => {
|
|
731
|
+
res.statusCode = code;
|
|
732
|
+
return res;
|
|
733
|
+
},
|
|
734
|
+
send: (data) => {
|
|
735
|
+
res.sentData = data;
|
|
736
|
+
},
|
|
737
|
+
end: (data) => {
|
|
738
|
+
res.sentData = data;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
const route = {
|
|
743
|
+
name: 'test-json-response',
|
|
744
|
+
method: 'post',
|
|
745
|
+
endpoint: '/test',
|
|
746
|
+
rawResponse: false,
|
|
747
|
+
run: (services, inputs, next) => {
|
|
748
|
+
next(null, testResponse);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
await Api.preprocessor(route, req, res);
|
|
753
|
+
assert.strictEqual(res.statusCode, 200);
|
|
754
|
+
// When rawResponse is false, Express will JSON serialize objects
|
|
755
|
+
assert.deepStrictEqual(res.sentData, testResponse);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('should process inputs alongside rawResponse when both are present', async () => {
|
|
600
759
|
const Api = new Server({
|
|
601
760
|
name: 'test-Server',
|
|
602
761
|
routeDir: './test-routes',
|
|
@@ -776,4 +935,191 @@ describe('server-test', () => {
|
|
|
776
935
|
assert.strictEqual(res.statusCode, 200);
|
|
777
936
|
});
|
|
778
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
|
+
});
|
|
779
1125
|
});
|