impulse-api 3.0.6 → 3.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/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "test": "nyc --reporter=lcov --reporter=text-summary mocha",
13
13
  "test-server": "node ./test/integration/test-server.js"
14
14
  },
15
- "version": "3.0.6",
15
+ "version": "3.0.8",
16
16
  "engines": {
17
17
  "node": ">=22"
18
18
  },
package/src/server.js CHANGED
@@ -266,16 +266,23 @@ class Server {
266
266
 
267
267
  if (route.inputs) {
268
268
  try {
269
- data = this.buildParameters(
270
- Object.assign(req.query || {}, req.body || {}, req.params || {}, req.files || {}),
271
- route.inputs
272
- );
269
+ // When rawBody is true, exclude req.body from inputs processing since it's a Buffer
270
+ const paramsForInputs = route.rawBody === true
271
+ ? Object.assign(req.query || {}, req.params || {}, req.files || {})
272
+ : Object.assign(req.query || {}, req.body || {}, req.params || {}, req.files || {});
273
+
274
+ data = this.buildParameters(paramsForInputs, route.inputs);
273
275
  } catch (error) {
274
276
  sendResponse(error);
275
277
  return;
276
278
  }
277
279
  }
278
280
 
281
+ // Handle rawBody - append the raw Buffer if rawBody flag is set
282
+ if (route.rawBody === true) {
283
+ data.rawBody = req.body; // This will be a Buffer, not a parsed object
284
+ }
285
+
279
286
  if (route.headers) {
280
287
  try {
281
288
  data.headers = this.buildParameters(req.headers, route.headers);
@@ -474,10 +481,15 @@ class Server {
474
481
  method = route.method.toLowerCase();
475
482
  verb = (verbMap[method]) ? verbMap[method] : method;
476
483
 
477
- // express-fileupload handles multipart/form-data (including files)
478
- // express.json() handles JSON bodies
479
- // multer().none() is not needed and conflicts with file uploads
480
- this.http[verb](route.endpoint, this.preprocessor.bind(this, route));
484
+ // For routes that need raw body (e.g., webhooks), use express.raw() instead of JSON
485
+ if (route.rawBody === true) {
486
+ this.http[verb](route.endpoint, express.raw({ type: 'application/json' }), this.preprocessor.bind(this, route));
487
+ } else {
488
+ // express-fileupload handles multipart/form-data (including files)
489
+ // express.json() handles JSON bodies
490
+ // multer().none() is not needed and conflicts with file uploads
491
+ this.http[verb](route.endpoint, this.preprocessor.bind(this, route));
492
+ }
481
493
 
482
494
  });
483
495
  });
@@ -253,12 +253,13 @@ exports.testRoute = {
253
253
  });
254
254
 
255
255
  it('should validate custom validator function type', () => {
256
+ const auth = new Auth('test-secret');
256
257
  assert.throws(() => {
257
- Auth.createCustomValidator('not-a-function');
258
+ auth.createCustomValidator('not-a-function');
258
259
  }, /Custom validator must be a function/);
259
260
 
260
261
  assert.doesNotThrow(() => {
261
- Auth.createCustomValidator(() => {});
262
+ auth.createCustomValidator(() => {});
262
263
  });
263
264
  });
264
265
  });
@@ -449,7 +449,7 @@ describe('server-test', () => {
449
449
  auth.createCustomValidator('not-a-function');
450
450
  assert.fail('Should have thrown an error');
451
451
  } catch (e) {
452
- assert.contains(e.message, 'must be a function');
452
+ assert.contains(e.message, 'Custom validator must be a function');
453
453
  }
454
454
  });
455
455
 
@@ -499,4 +499,281 @@ describe('server-test', () => {
499
499
  assert.strictEqual(server.auth, null);
500
500
  });
501
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 process inputs alongside rawBody when both are present', 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 = Buffer.from(JSON.stringify({ test: 'data' }));
609
+ const req = {
610
+ body: testBody,
611
+ query: { param1: 'value1' },
612
+ params: { id: '123' },
613
+ files: {},
614
+ headers: {},
615
+ get: (header) => {
616
+ if (header === 'origin') return 'http://localhost:4000';
617
+ if (header === 'host') return 'localhost:4000';
618
+ return null;
619
+ }
620
+ };
621
+ const res = {
622
+ status: (code) => {
623
+ res.statusCode = code;
624
+ return res;
625
+ },
626
+ send: (data) => {
627
+ res.sentData = data;
628
+ }
629
+ };
630
+
631
+ const route = {
632
+ name: 'test-raw-body-with-inputs',
633
+ method: 'post',
634
+ endpoint: '/test/:id',
635
+ rawBody: true,
636
+ inputs: {
637
+ param1: {
638
+ required: true
639
+ },
640
+ id: {
641
+ required: true
642
+ }
643
+ },
644
+ run: (services, inputs, next) => {
645
+ assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
646
+ assert.deepStrictEqual(inputs.rawBody, testBody);
647
+ assert.strictEqual(inputs.param1, 'value1');
648
+ assert.strictEqual(inputs.id, '123');
649
+ next(200, { success: true });
650
+ }
651
+ };
652
+
653
+ await Api.preprocessor(route, req, res);
654
+ assert.strictEqual(res.statusCode, 200);
655
+ });
656
+
657
+ it('should handle rawBody without inputs', async () => {
658
+ const Api = new Server({
659
+ name: 'test-Server',
660
+ routeDir: './test-routes',
661
+ port: 4000,
662
+ env: 'test',
663
+ services: {}
664
+ });
665
+
666
+ const testBody = Buffer.from('raw string data');
667
+ const req = {
668
+ body: testBody,
669
+ query: {},
670
+ params: {},
671
+ files: {},
672
+ headers: {},
673
+ get: (header) => {
674
+ if (header === 'origin') return 'http://localhost:4000';
675
+ if (header === 'host') return 'localhost:4000';
676
+ return null;
677
+ }
678
+ };
679
+ const res = {
680
+ status: (code) => {
681
+ res.statusCode = code;
682
+ return res;
683
+ },
684
+ send: (data) => {
685
+ res.sentData = data;
686
+ }
687
+ };
688
+
689
+ const route = {
690
+ name: 'test-raw-body-only',
691
+ method: 'post',
692
+ endpoint: '/test',
693
+ rawBody: true,
694
+ run: (services, inputs, next) => {
695
+ assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
696
+ assert.deepStrictEqual(inputs.rawBody, testBody);
697
+ next(200, { success: true });
698
+ }
699
+ };
700
+
701
+ await Api.preprocessor(route, req, res);
702
+ assert.strictEqual(res.statusCode, 200);
703
+ });
704
+
705
+ it('should not include Buffer body in inputs processing when rawBody is true', 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 = Buffer.from(JSON.stringify({ test: 'data', shouldNotBeParsed: true }));
715
+ const req = {
716
+ body: testBody,
717
+ query: { param1: 'value1' },
718
+ params: { id: '123' },
719
+ files: {},
720
+ headers: {},
721
+ get: (header) => {
722
+ if (header === 'origin') return 'http://localhost:4000';
723
+ if (header === 'host') return 'localhost:4000';
724
+ return null;
725
+ }
726
+ };
727
+ const res = {
728
+ status: (code) => {
729
+ res.statusCode = code;
730
+ return res;
731
+ },
732
+ send: (data) => {
733
+ res.sentData = data;
734
+ }
735
+ };
736
+
737
+ const route = {
738
+ name: 'test-raw-body-excluded-from-inputs',
739
+ method: 'post',
740
+ endpoint: '/test/:id',
741
+ rawBody: true,
742
+ inputs: {
743
+ param1: {
744
+ required: true
745
+ },
746
+ id: {
747
+ required: true
748
+ },
749
+ // This should NOT be found in inputs even though it's in the Buffer body
750
+ test: {
751
+ required: false
752
+ },
753
+ shouldNotBeParsed: {
754
+ required: false
755
+ }
756
+ },
757
+ run: (services, inputs, next) => {
758
+ // Verify rawBody is still a Buffer
759
+ assert.strictEqual(Buffer.isBuffer(inputs.rawBody), true);
760
+ assert.deepStrictEqual(inputs.rawBody, testBody);
761
+
762
+ // Verify inputs from query/params work
763
+ assert.strictEqual(inputs.param1, 'value1');
764
+ assert.strictEqual(inputs.id, '123');
765
+
766
+ // Verify that parsed body fields are NOT in inputs (because Buffer was excluded)
767
+ // buildParameters sets missing optional params to empty string, not undefined
768
+ assert.strictEqual(inputs.test, "");
769
+ assert.strictEqual(inputs.shouldNotBeParsed, "");
770
+
771
+ next(200, { success: true });
772
+ }
773
+ };
774
+
775
+ await Api.preprocessor(route, req, res);
776
+ assert.strictEqual(res.statusCode, 200);
777
+ });
778
+ });
502
779
  });