impulse-api 3.0.8 → 3.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/server.js +39 -6
- package/test/integration/rawbody-integration-test.js +113 -0
- package/test/integration/rawresponse-integration-test.js +259 -0
- package/test/integration/routes/normal.js +24 -0
- package/test/integration/routes/raw-response.js +65 -0
- package/test/integration/routes/webhook.js +18 -0
- package/test/{api-test.js → unit/api-test.js} +1 -1
- package/test/{custom-jwt-validation.js → unit/custom-jwt-validation.js} +2 -2
- package/test/unit/server-test.js +938 -0
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
const Server = require('../../src/server');
|
|
2
|
+
const Auth = require('../../src/auth');
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
|
|
6
|
+
const testApi = new Server({
|
|
7
|
+
name: 'test-server',
|
|
8
|
+
routeDir: './test-routes',
|
|
9
|
+
port: 4000,
|
|
10
|
+
env: 'test',
|
|
11
|
+
services: {}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
assert.contains = (orig, key, message) => {
|
|
15
|
+
assert.strictEqual(orig.indexOf(key) >= 0, true, message);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('server-test', () => {
|
|
19
|
+
describe('instantiation', () => {
|
|
20
|
+
it('should throw an error if a configuration object is missing', () => {
|
|
21
|
+
try {
|
|
22
|
+
new Server();
|
|
23
|
+
} catch (e) {
|
|
24
|
+
assert.contains(e.message, 'valid config');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
it('should throw an error if service name is missing', () => {
|
|
28
|
+
try {
|
|
29
|
+
new Server({
|
|
30
|
+
routeDir: './'
|
|
31
|
+
});
|
|
32
|
+
} catch (e) {
|
|
33
|
+
assert.contains(e.message, 'name');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it('should throw an error if route directory is missing', () => {
|
|
37
|
+
try {
|
|
38
|
+
new Server({
|
|
39
|
+
name: 'test-Server'
|
|
40
|
+
});
|
|
41
|
+
} catch (e) {
|
|
42
|
+
assert.contains(e.message, 'route');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it('should property set the port', () => {
|
|
46
|
+
const Api = new Server({
|
|
47
|
+
name: 'test-server',
|
|
48
|
+
routeDir: './test-routes',
|
|
49
|
+
port: 4000,
|
|
50
|
+
env: 'test',
|
|
51
|
+
services: {}
|
|
52
|
+
});
|
|
53
|
+
assert.strictEqual(Api.port, 4000);
|
|
54
|
+
});
|
|
55
|
+
it('should properly set the environment', () => {
|
|
56
|
+
const Api = new Server({
|
|
57
|
+
name: 'test-Server',
|
|
58
|
+
routeDir: './test-routes',
|
|
59
|
+
port: 4000,
|
|
60
|
+
env: 'test',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
assert.strictEqual(Api.env, 'test');
|
|
64
|
+
});
|
|
65
|
+
it('should set the app key to whatever is passed in', () => {
|
|
66
|
+
const Api = new Server({
|
|
67
|
+
name: 'test-Server',
|
|
68
|
+
routeDir: './routes/sample-routes',
|
|
69
|
+
port: 4000,
|
|
70
|
+
env: 'test',
|
|
71
|
+
appKey: 'TestKey'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(Api.appKey, 'TestKey');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('route parameters', () => {
|
|
79
|
+
it('should not crash if there are no inputs provided', () => {
|
|
80
|
+
const Api = new Server({
|
|
81
|
+
name: 'test-Server',
|
|
82
|
+
routeDir: './test-routes',
|
|
83
|
+
port: 4000,
|
|
84
|
+
env: 'test',
|
|
85
|
+
});
|
|
86
|
+
Api.buildParameters({}, null);
|
|
87
|
+
});
|
|
88
|
+
it('should not crash if there are no params provided', () => {
|
|
89
|
+
const Api = new Server({
|
|
90
|
+
name: 'test-Server',
|
|
91
|
+
routeDir: './test-routes',
|
|
92
|
+
port: 4000,
|
|
93
|
+
env: 'test',
|
|
94
|
+
});
|
|
95
|
+
Api.buildParameters(null, {});
|
|
96
|
+
});
|
|
97
|
+
it('should pass a param with a true/false value', () => {
|
|
98
|
+
const Api = new Server({
|
|
99
|
+
name: 'test-Server',
|
|
100
|
+
routeDir: './test-routes',
|
|
101
|
+
port: 4000,
|
|
102
|
+
env: 'test',
|
|
103
|
+
});
|
|
104
|
+
const output = Api.buildParameters({ grade: 0 }, {
|
|
105
|
+
grade: {
|
|
106
|
+
required: true
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
assert.strictEqual(output.grade, 0);
|
|
111
|
+
});
|
|
112
|
+
it('should throw an error if there is a required input that is not in the params', () => {
|
|
113
|
+
const Api = new Server({
|
|
114
|
+
name: 'test-Server',
|
|
115
|
+
routeDir: './test-routes',
|
|
116
|
+
port: 4000,
|
|
117
|
+
env: 'test',
|
|
118
|
+
});
|
|
119
|
+
try {
|
|
120
|
+
Api.buildParameters({
|
|
121
|
+
'test-param': {
|
|
122
|
+
required: true
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{}
|
|
126
|
+
);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.log(e)
|
|
129
|
+
assert.contains(e.message, 'Missing parameter [test-param]');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
it('should apply a formatter if the input provides one', () => {
|
|
133
|
+
const Api = new Server({
|
|
134
|
+
name: 'test-Server',
|
|
135
|
+
routeDir: './test-routes',
|
|
136
|
+
port: 4000,
|
|
137
|
+
env: 'test'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
let output;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
output = Api.buildParameters({
|
|
144
|
+
param1: '1',
|
|
145
|
+
param2: '2'
|
|
146
|
+
}, {
|
|
147
|
+
param1: {
|
|
148
|
+
required: true,
|
|
149
|
+
formatter: (param) => {
|
|
150
|
+
assert.strictEqual(param, '1');
|
|
151
|
+
return parseInt(param, 10) + 1;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
param2: {
|
|
155
|
+
required: true
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
assert.strictEqual(output.param1, 2);
|
|
160
|
+
assert.strictEqual(output.param2, '2');
|
|
161
|
+
} catch (e) {
|
|
162
|
+
assert.ifError(e);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
it('should run parameters through the validator after being formatted', () => {
|
|
166
|
+
const Api = new Server({
|
|
167
|
+
name: 'test-Server',
|
|
168
|
+
routeDir: './test-routes',
|
|
169
|
+
port: 4000,
|
|
170
|
+
env: 'test',
|
|
171
|
+
});
|
|
172
|
+
let output;
|
|
173
|
+
try {
|
|
174
|
+
output = Api.buildParameters({
|
|
175
|
+
param1: '1',
|
|
176
|
+
param2: '2'
|
|
177
|
+
}, {
|
|
178
|
+
param1: {
|
|
179
|
+
required: true,
|
|
180
|
+
formatter: (param) => {
|
|
181
|
+
assert.strictEqual(param, '1');
|
|
182
|
+
return parseInt(param, 10) + 1;
|
|
183
|
+
},
|
|
184
|
+
validator: () => {
|
|
185
|
+
assert.strictEqual(output.param1, 2);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
param2: {
|
|
189
|
+
required: true
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
assert.strictEqual(output.param1, 2);
|
|
194
|
+
assert.strictEqual(output.param2, '2');
|
|
195
|
+
} catch (e) {
|
|
196
|
+
assert.ifError(e);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
it('should return only the data that is defined in the inputs', () => {
|
|
200
|
+
const Api = new Server({
|
|
201
|
+
name: 'test-Server',
|
|
202
|
+
routeDir: './test-routes',
|
|
203
|
+
port: 4000,
|
|
204
|
+
env: 'test',
|
|
205
|
+
});
|
|
206
|
+
let output;
|
|
207
|
+
try {
|
|
208
|
+
output = Api.buildParameters({
|
|
209
|
+
param1: '1',
|
|
210
|
+
param2: '2',
|
|
211
|
+
param3: '3'
|
|
212
|
+
}, {
|
|
213
|
+
param1: {
|
|
214
|
+
required: true
|
|
215
|
+
},
|
|
216
|
+
param2: {
|
|
217
|
+
required: true
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
assert.strictEqual(output.param1, '1');
|
|
222
|
+
assert.strictEqual(output.param2, '2');
|
|
223
|
+
assert.strictEqual(output.param3, undefined);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
assert.ifError(e);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('#loadRoutes', () => {
|
|
231
|
+
it('it should return an error if the route does not have a "run" method', () => {
|
|
232
|
+
const Api = new Server({
|
|
233
|
+
name: 'test-Server',
|
|
234
|
+
routeDir: require('path').join(__dirname, '../routes'),
|
|
235
|
+
port: 4000,
|
|
236
|
+
env: 'test'
|
|
237
|
+
});
|
|
238
|
+
const routeFiles = [
|
|
239
|
+
'missing-run.js'
|
|
240
|
+
];
|
|
241
|
+
Api.loadRoutes(routeFiles).catch((error) => {
|
|
242
|
+
assert.contains(error.message, 'missing a "run" property');
|
|
243
|
+
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
it('it should return an error if the route does not have a name', () => {
|
|
247
|
+
const Api = new Server({
|
|
248
|
+
name: 'test-Server',
|
|
249
|
+
routeDir: require('path').join(__dirname, '../routes'),
|
|
250
|
+
port: 4000,
|
|
251
|
+
env: 'test'
|
|
252
|
+
});
|
|
253
|
+
const routeFiles = [
|
|
254
|
+
'missing-name.js'
|
|
255
|
+
];
|
|
256
|
+
Api.loadRoutes(routeFiles).catch((error) => {
|
|
257
|
+
assert.contains(error.message, 'missing a "name" property');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
it('should return an error if the route does not have an endpoint', () => {
|
|
261
|
+
const Api = new Server({
|
|
262
|
+
name: 'test-Server',
|
|
263
|
+
routeDir: require('path').join(__dirname, '../routes'),
|
|
264
|
+
port: 4000,
|
|
265
|
+
env: 'test'
|
|
266
|
+
});
|
|
267
|
+
const routeFiles = [
|
|
268
|
+
'missing-endpoint.js'
|
|
269
|
+
];
|
|
270
|
+
Api.loadRoutes(routeFiles).catch((error) => {
|
|
271
|
+
assert.contains(error.message, 'missing an endpoint');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should return an error if the input is missing', () => {
|
|
276
|
+
const Api = new Server({
|
|
277
|
+
name: 'test-Server',
|
|
278
|
+
routeDir: require('path').join(__dirname, '../routes'),
|
|
279
|
+
port: 4000,
|
|
280
|
+
env: 'test'
|
|
281
|
+
});
|
|
282
|
+
const routeFiles = [
|
|
283
|
+
'missing-params.js'
|
|
284
|
+
];
|
|
285
|
+
Api.loadRoutes(routeFiles).catch((error) => {
|
|
286
|
+
assert.contains(error.message, 'Missing required input');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should return an error if the route does not have a method', () => {
|
|
291
|
+
const Api = new Server({
|
|
292
|
+
name: 'test-Server',
|
|
293
|
+
routeDir: require('path').join(__dirname, '../routes'),
|
|
294
|
+
port: 4000,
|
|
295
|
+
env: 'test'
|
|
296
|
+
});
|
|
297
|
+
const routeFiles = [
|
|
298
|
+
'missing-method.js'
|
|
299
|
+
];
|
|
300
|
+
Api.loadRoutes(routeFiles).catch((error) => {
|
|
301
|
+
assert.contains(error.message, 'missing http method');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
describe('#loadRouteFiles', () => {
|
|
306
|
+
it('should return an error if given a broken directory', () => {
|
|
307
|
+
const Api = new Server({
|
|
308
|
+
name: 'test-Server',
|
|
309
|
+
routeDir: 'missing',
|
|
310
|
+
port: 4000,
|
|
311
|
+
env: 'test'
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
Api.loadRouteFiles().catch((error) => {
|
|
315
|
+
assert.contains(error.message, 'file or directory');
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('#checkConflictingRoutes', () => {
|
|
321
|
+
it('should not call callback if routes array is empty', () => {
|
|
322
|
+
const spy = sinon.spy();
|
|
323
|
+
testApi.checkConflictingRoutes([], spy);
|
|
324
|
+
sinon.assert.notCalled(spy);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should find conflicting endpoints', () => {
|
|
328
|
+
const spy = sinon.spy();
|
|
329
|
+
const routes = [
|
|
330
|
+
{ method: 'get', endpoint: '/api/user/info' },
|
|
331
|
+
{ method: 'get', endpoint: '/api/user/:id' }
|
|
332
|
+
];
|
|
333
|
+
testApi.checkConflictingRoutes(routes, spy);
|
|
334
|
+
sinon.assert.calledOnce(spy);
|
|
335
|
+
sinon.assert.calledWith(spy, routes[1], routes[0]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should find conflicting endpoints with multiple dynamic parameters', () => {
|
|
339
|
+
const spy = sinon.spy();
|
|
340
|
+
const routes = [
|
|
341
|
+
{ method: 'get', endpoint: '/api/user/info/:test' },
|
|
342
|
+
{ method: 'get', endpoint: '/api/user/:id/:test' }
|
|
343
|
+
];
|
|
344
|
+
testApi.checkConflictingRoutes(routes, spy);
|
|
345
|
+
sinon.assert.calledOnce(spy);
|
|
346
|
+
sinon.assert.calledWith(spy, routes[1], routes[0]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should not find conflicting endpoints when HTTP verbs are different', () => {
|
|
350
|
+
const spy = sinon.spy();
|
|
351
|
+
const routes = [
|
|
352
|
+
{ method: 'post', endpoint: '/api/user/info' },
|
|
353
|
+
{ method: 'get', endpoint: '/api/user/:id' }
|
|
354
|
+
];
|
|
355
|
+
testApi.checkConflictingRoutes(routes, spy);
|
|
356
|
+
sinon.assert.notCalled(spy);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('Auth module', () => {
|
|
361
|
+
describe('instantiation', () => {
|
|
362
|
+
it('should throw an error if secretKey is not provided', () => {
|
|
363
|
+
try {
|
|
364
|
+
new Auth();
|
|
365
|
+
assert.fail('Should have thrown an error');
|
|
366
|
+
} catch (e) {
|
|
367
|
+
assert.contains(e.message, 'must be initialized with secretKey');
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should throw an error if secretKey is null', () => {
|
|
372
|
+
try {
|
|
373
|
+
new Auth(null);
|
|
374
|
+
assert.fail('Should have thrown an error');
|
|
375
|
+
} catch (e) {
|
|
376
|
+
assert.contains(e.message, 'must be initialized with secretKey');
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should throw an error if secretKey is undefined', () => {
|
|
381
|
+
try {
|
|
382
|
+
new Auth(undefined);
|
|
383
|
+
assert.fail('Should have thrown an error');
|
|
384
|
+
} catch (e) {
|
|
385
|
+
assert.contains(e.message, 'must be initialized with secretKey');
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should create an instance when secretKey is provided', () => {
|
|
390
|
+
const auth = new Auth('test-secret-key');
|
|
391
|
+
assert.strictEqual(auth.secretKey, 'test-secret-key');
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe('generateToken', () => {
|
|
396
|
+
it('should generate a token without requiring secretKey parameter', () => {
|
|
397
|
+
const auth = new Auth('test-secret-key');
|
|
398
|
+
const params = { userId: 'test-user', role: 'admin' };
|
|
399
|
+
const token = auth.generateToken(params);
|
|
400
|
+
assert.strictEqual(typeof token, 'string');
|
|
401
|
+
assert.strictEqual(token.length > 0, true);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should generate different tokens for different params', () => {
|
|
405
|
+
const auth = new Auth('test-secret-key');
|
|
406
|
+
const token1 = auth.generateToken({ userId: 'user1' });
|
|
407
|
+
const token2 = auth.generateToken({ userId: 'user2' });
|
|
408
|
+
assert.notStrictEqual(token1, token2);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('verifyToken', () => {
|
|
413
|
+
it('should verify a token without requiring secretKey parameter', () => {
|
|
414
|
+
const auth = new Auth('test-secret-key');
|
|
415
|
+
const params = { userId: 'test-user', role: 'admin' };
|
|
416
|
+
const token = auth.generateToken(params);
|
|
417
|
+
const decoded = auth.verifyToken(token);
|
|
418
|
+
assert.strictEqual(decoded.userId, 'test-user');
|
|
419
|
+
assert.strictEqual(decoded.role, 'admin');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should throw an error when verifying a token with wrong secretKey', () => {
|
|
423
|
+
const auth1 = new Auth('secret-key-1');
|
|
424
|
+
const auth2 = new Auth('secret-key-2');
|
|
425
|
+
const token = auth1.generateToken({ userId: 'test-user' });
|
|
426
|
+
try {
|
|
427
|
+
auth2.verifyToken(token);
|
|
428
|
+
assert.fail('Should have thrown an error');
|
|
429
|
+
} catch (e) {
|
|
430
|
+
assert.contains(e.message, 'invalid signature');
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should throw an error when verifying an invalid token', () => {
|
|
435
|
+
const auth = new Auth('test-secret-key');
|
|
436
|
+
try {
|
|
437
|
+
auth.verifyToken('invalid-token-string');
|
|
438
|
+
assert.fail('Should have thrown an error');
|
|
439
|
+
} catch (e) {
|
|
440
|
+
assert.strictEqual(typeof e.message, 'string');
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe('createCustomValidator', () => {
|
|
446
|
+
it('should throw an error if validator is not a function', () => {
|
|
447
|
+
const auth = new Auth('test-secret-key');
|
|
448
|
+
try {
|
|
449
|
+
auth.createCustomValidator('not-a-function');
|
|
450
|
+
assert.fail('Should have thrown an error');
|
|
451
|
+
} catch (e) {
|
|
452
|
+
assert.contains(e.message, 'Custom validator must be a function');
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should return the validator function if valid', () => {
|
|
457
|
+
const auth = new Auth('test-secret-key');
|
|
458
|
+
const validatorFn = () => true;
|
|
459
|
+
const result = auth.createCustomValidator(validatorFn);
|
|
460
|
+
assert.strictEqual(result, validatorFn);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe('Server Auth integration', () => {
|
|
466
|
+
it('should create auth instance when secretKey is provided', () => {
|
|
467
|
+
const server = new Server({
|
|
468
|
+
name: 'test-server',
|
|
469
|
+
routeDir: './test-routes',
|
|
470
|
+
port: 4000,
|
|
471
|
+
env: 'test',
|
|
472
|
+
secretKey: 'test-secret-key',
|
|
473
|
+
services: {}
|
|
474
|
+
});
|
|
475
|
+
assert.strictEqual(server.auth !== null, true);
|
|
476
|
+
assert.strictEqual(server.auth.secretKey, 'test-secret-key');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should not create auth instance when secretKey is not provided', () => {
|
|
480
|
+
const server = new Server({
|
|
481
|
+
name: 'test-server',
|
|
482
|
+
routeDir: './test-routes',
|
|
483
|
+
port: 4000,
|
|
484
|
+
env: 'test',
|
|
485
|
+
services: {}
|
|
486
|
+
});
|
|
487
|
+
assert.strictEqual(server.auth, null);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('should not create auth instance when secretKey is null', () => {
|
|
491
|
+
const server = new Server({
|
|
492
|
+
name: 'test-server',
|
|
493
|
+
routeDir: './test-routes',
|
|
494
|
+
port: 4000,
|
|
495
|
+
env: 'test',
|
|
496
|
+
secretKey: null,
|
|
497
|
+
services: {}
|
|
498
|
+
});
|
|
499
|
+
assert.strictEqual(server.auth, null);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
describe('rawBody functionality', () => {
|
|
504
|
+
it('should set rawBody as Buffer when route.rawBody is true', async () => {
|
|
505
|
+
const Api = new Server({
|
|
506
|
+
name: 'test-Server',
|
|
507
|
+
routeDir: './test-routes',
|
|
508
|
+
port: 4000,
|
|
509
|
+
env: 'test',
|
|
510
|
+
services: {}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
const testBody = Buffer.from(JSON.stringify({ test: 'data' }));
|
|
514
|
+
const req = {
|
|
515
|
+
body: testBody,
|
|
516
|
+
query: {},
|
|
517
|
+
params: {},
|
|
518
|
+
files: {},
|
|
519
|
+
headers: {},
|
|
520
|
+
get: (header) => {
|
|
521
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
522
|
+
if (header === 'host') return 'localhost:4000';
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const res = {
|
|
527
|
+
status: (code) => {
|
|
528
|
+
res.statusCode = code;
|
|
529
|
+
return res;
|
|
530
|
+
},
|
|
531
|
+
send: (data) => {
|
|
532
|
+
res.sentData = data;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const route = {
|
|
537
|
+
name: 'test-raw-body',
|
|
538
|
+
method: 'post',
|
|
539
|
+
endpoint: '/test',
|
|
540
|
+
rawBody: true,
|
|
541
|
+
run: (services, inputs, next) => {
|
|
542
|
+
assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
|
|
543
|
+
assert.deepStrictEqual(inputs.rawBody, testBody);
|
|
544
|
+
next(200, { success: true });
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
await Api.preprocessor(route, req, res);
|
|
549
|
+
assert.strictEqual(res.statusCode, 200);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('should not set rawBody when route.rawBody is false', async () => {
|
|
553
|
+
const Api = new Server({
|
|
554
|
+
name: 'test-Server',
|
|
555
|
+
routeDir: './test-routes',
|
|
556
|
+
port: 4000,
|
|
557
|
+
env: 'test',
|
|
558
|
+
services: {}
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const testBody = { test: 'data' };
|
|
562
|
+
const req = {
|
|
563
|
+
body: testBody,
|
|
564
|
+
query: {},
|
|
565
|
+
params: {},
|
|
566
|
+
files: {},
|
|
567
|
+
headers: {},
|
|
568
|
+
get: (header) => {
|
|
569
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
570
|
+
if (header === 'host') return 'localhost:4000';
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
const res = {
|
|
575
|
+
status: (code) => {
|
|
576
|
+
res.statusCode = code;
|
|
577
|
+
return res;
|
|
578
|
+
},
|
|
579
|
+
send: (data) => {
|
|
580
|
+
res.sentData = data;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const route = {
|
|
585
|
+
name: 'test-no-raw-body',
|
|
586
|
+
method: 'post',
|
|
587
|
+
endpoint: '/test',
|
|
588
|
+
rawBody: false,
|
|
589
|
+
run: (services, inputs, next) => {
|
|
590
|
+
assert.strictEqual(inputs.rawBody, undefined);
|
|
591
|
+
next(200, { success: true });
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
await Api.preprocessor(route, req, res);
|
|
596
|
+
assert.strictEqual(res.statusCode, 200);
|
|
597
|
+
});
|
|
598
|
+
|
|
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 () => {
|
|
759
|
+
const Api = new Server({
|
|
760
|
+
name: 'test-Server',
|
|
761
|
+
routeDir: './test-routes',
|
|
762
|
+
port: 4000,
|
|
763
|
+
env: 'test',
|
|
764
|
+
services: {}
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
const testBody = Buffer.from(JSON.stringify({ test: 'data' }));
|
|
768
|
+
const req = {
|
|
769
|
+
body: testBody,
|
|
770
|
+
query: { param1: 'value1' },
|
|
771
|
+
params: { id: '123' },
|
|
772
|
+
files: {},
|
|
773
|
+
headers: {},
|
|
774
|
+
get: (header) => {
|
|
775
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
776
|
+
if (header === 'host') return 'localhost:4000';
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
const res = {
|
|
781
|
+
status: (code) => {
|
|
782
|
+
res.statusCode = code;
|
|
783
|
+
return res;
|
|
784
|
+
},
|
|
785
|
+
send: (data) => {
|
|
786
|
+
res.sentData = data;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const route = {
|
|
791
|
+
name: 'test-raw-body-with-inputs',
|
|
792
|
+
method: 'post',
|
|
793
|
+
endpoint: '/test/:id',
|
|
794
|
+
rawBody: true,
|
|
795
|
+
inputs: {
|
|
796
|
+
param1: {
|
|
797
|
+
required: true
|
|
798
|
+
},
|
|
799
|
+
id: {
|
|
800
|
+
required: true
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
run: (services, inputs, next) => {
|
|
804
|
+
assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
|
|
805
|
+
assert.deepStrictEqual(inputs.rawBody, testBody);
|
|
806
|
+
assert.strictEqual(inputs.param1, 'value1');
|
|
807
|
+
assert.strictEqual(inputs.id, '123');
|
|
808
|
+
next(200, { success: true });
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
await Api.preprocessor(route, req, res);
|
|
813
|
+
assert.strictEqual(res.statusCode, 200);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('should handle rawBody without inputs', async () => {
|
|
817
|
+
const Api = new Server({
|
|
818
|
+
name: 'test-Server',
|
|
819
|
+
routeDir: './test-routes',
|
|
820
|
+
port: 4000,
|
|
821
|
+
env: 'test',
|
|
822
|
+
services: {}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const testBody = Buffer.from('raw string data');
|
|
826
|
+
const req = {
|
|
827
|
+
body: testBody,
|
|
828
|
+
query: {},
|
|
829
|
+
params: {},
|
|
830
|
+
files: {},
|
|
831
|
+
headers: {},
|
|
832
|
+
get: (header) => {
|
|
833
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
834
|
+
if (header === 'host') return 'localhost:4000';
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
const res = {
|
|
839
|
+
status: (code) => {
|
|
840
|
+
res.statusCode = code;
|
|
841
|
+
return res;
|
|
842
|
+
},
|
|
843
|
+
send: (data) => {
|
|
844
|
+
res.sentData = data;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
const route = {
|
|
849
|
+
name: 'test-raw-body-only',
|
|
850
|
+
method: 'post',
|
|
851
|
+
endpoint: '/test',
|
|
852
|
+
rawBody: true,
|
|
853
|
+
run: (services, inputs, next) => {
|
|
854
|
+
assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
|
|
855
|
+
assert.deepStrictEqual(inputs.rawBody, testBody);
|
|
856
|
+
next(200, { success: true });
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
await Api.preprocessor(route, req, res);
|
|
861
|
+
assert.strictEqual(res.statusCode, 200);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
it('should not include Buffer body in inputs processing when rawBody is true', async () => {
|
|
865
|
+
const Api = new Server({
|
|
866
|
+
name: 'test-Server',
|
|
867
|
+
routeDir: './test-routes',
|
|
868
|
+
port: 4000,
|
|
869
|
+
env: 'test',
|
|
870
|
+
services: {}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
const testBody = Buffer.from(JSON.stringify({ test: 'data', shouldNotBeParsed: true }));
|
|
874
|
+
const req = {
|
|
875
|
+
body: testBody,
|
|
876
|
+
query: { param1: 'value1' },
|
|
877
|
+
params: { id: '123' },
|
|
878
|
+
files: {},
|
|
879
|
+
headers: {},
|
|
880
|
+
get: (header) => {
|
|
881
|
+
if (header === 'origin') return 'http://localhost:4000';
|
|
882
|
+
if (header === 'host') return 'localhost:4000';
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
const res = {
|
|
887
|
+
status: (code) => {
|
|
888
|
+
res.statusCode = code;
|
|
889
|
+
return res;
|
|
890
|
+
},
|
|
891
|
+
send: (data) => {
|
|
892
|
+
res.sentData = data;
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const route = {
|
|
897
|
+
name: 'test-raw-body-excluded-from-inputs',
|
|
898
|
+
method: 'post',
|
|
899
|
+
endpoint: '/test/:id',
|
|
900
|
+
rawBody: true,
|
|
901
|
+
inputs: {
|
|
902
|
+
param1: {
|
|
903
|
+
required: true
|
|
904
|
+
},
|
|
905
|
+
id: {
|
|
906
|
+
required: true
|
|
907
|
+
},
|
|
908
|
+
// This should NOT be found in inputs even though it's in the Buffer body
|
|
909
|
+
test: {
|
|
910
|
+
required: false
|
|
911
|
+
},
|
|
912
|
+
shouldNotBeParsed: {
|
|
913
|
+
required: false
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
run: (services, inputs, next) => {
|
|
917
|
+
// Verify rawBody is still a Buffer
|
|
918
|
+
assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
|
|
919
|
+
assert.deepStrictEqual(inputs.rawBody, testBody);
|
|
920
|
+
|
|
921
|
+
// Verify inputs from query/params work
|
|
922
|
+
assert.strictEqual(inputs.param1, 'value1');
|
|
923
|
+
assert.strictEqual(inputs.id, '123');
|
|
924
|
+
|
|
925
|
+
// Verify that parsed body fields are NOT in inputs (because Buffer was excluded)
|
|
926
|
+
// buildParameters sets missing optional params to empty string, not undefined
|
|
927
|
+
assert.strictEqual(inputs.test, "");
|
|
928
|
+
assert.strictEqual(inputs.shouldNotBeParsed, "");
|
|
929
|
+
|
|
930
|
+
next(200, { success: true });
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
await Api.preprocessor(route, req, res);
|
|
935
|
+
assert.strictEqual(res.statusCode, 200);
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
});
|