Skip to content

Commit b8d77a0

Browse files
Matt Van Hornclaude
authored andcommitted
test(HTTPReceiver): add tests for invalidRequestSignatureHandler
Cover three scenarios: custom handler called with correct args on signature failure, default noop handler doesn't throw, and missing headers pass undefined for signature/ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7220fe2 commit b8d77a0

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

test/unit/receivers/HTTPReceiver.spec.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,125 @@ describe('HTTPReceiver', () => {
571571
});
572572
});
573573

574+
describe('invalidRequestSignatureHandler', () => {
575+
it('should call the custom handler when signature verification fails', async () => {
576+
const spy = sinon.spy();
577+
const fakeParseAndVerify = sinon.fake.rejects(new Error('Signature mismatch'));
578+
const fakeBuildNoBodyResponse = sinon.fake();
579+
580+
const overridesWithFakeVerify = mergeOverrides(overrides, {
581+
'./HTTPModuleFunctions': {
582+
parseAndVerifyHTTPRequest: fakeParseAndVerify,
583+
parseHTTPRequestBody: sinon.fake(),
584+
buildNoBodyResponse: fakeBuildNoBodyResponse,
585+
'@noCallThru': true,
586+
},
587+
});
588+
589+
const HTTPReceiver = importHTTPReceiver(overridesWithFakeVerify);
590+
const receiver = new HTTPReceiver({
591+
signingSecret: 'secret',
592+
logger: noopLogger,
593+
invalidRequestSignatureHandler: spy,
594+
});
595+
assert.isNotNull(receiver);
596+
597+
const fakeReq = sinon.createStubInstance(IncomingMessage) as unknown as IncomingMessage;
598+
fakeReq.url = '/slack/events';
599+
fakeReq.method = 'POST';
600+
fakeReq.headers = {
601+
'x-slack-signature': 'v0=bad',
602+
'x-slack-request-timestamp': '1234567890',
603+
};
604+
(fakeReq as IncomingMessage & { rawBody?: string }).rawBody = '{"token":"test"}';
605+
606+
const fakeRes = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse;
607+
608+
receiver.requestListener(fakeReq, fakeRes);
609+
610+
// Wait for the async closure inside handleIncomingEvent to settle
611+
await new Promise((resolve) => setTimeout(resolve, 50));
612+
613+
assert(spy.calledOnce, 'invalidRequestSignatureHandler should be called once');
614+
const args = spy.firstCall.args[0];
615+
assert.equal(args.rawBody, '{"token":"test"}');
616+
assert.equal(args.signature, 'v0=bad');
617+
assert.equal(args.ts, 1234567890);
618+
});
619+
620+
it('should use the default noop handler when no custom handler is provided', async () => {
621+
const fakeParseAndVerify = sinon.fake.rejects(new Error('Signature mismatch'));
622+
const fakeBuildNoBodyResponse = sinon.fake();
623+
624+
const overridesWithFakeVerify = mergeOverrides(overrides, {
625+
'./HTTPModuleFunctions': {
626+
parseAndVerifyHTTPRequest: fakeParseAndVerify,
627+
parseHTTPRequestBody: sinon.fake(),
628+
buildNoBodyResponse: fakeBuildNoBodyResponse,
629+
'@noCallThru': true,
630+
},
631+
});
632+
633+
const HTTPReceiver = importHTTPReceiver(overridesWithFakeVerify);
634+
const receiver = new HTTPReceiver({
635+
signingSecret: 'secret',
636+
logger: noopLogger,
637+
});
638+
639+
const fakeReq = sinon.createStubInstance(IncomingMessage) as unknown as IncomingMessage;
640+
fakeReq.url = '/slack/events';
641+
fakeReq.method = 'POST';
642+
fakeReq.headers = {};
643+
644+
const fakeRes = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse;
645+
646+
// Should not throw even without a custom handler
647+
receiver.requestListener(fakeReq, fakeRes);
648+
await new Promise((resolve) => setTimeout(resolve, 50));
649+
650+
sinon.assert.calledOnce(fakeBuildNoBodyResponse);
651+
sinon.assert.calledWith(fakeBuildNoBodyResponse, fakeRes, 401);
652+
});
653+
654+
it('should pass undefined for signature and ts when headers are missing', async () => {
655+
const spy = sinon.spy();
656+
const fakeParseAndVerify = sinon.fake.rejects(new Error('Signature mismatch'));
657+
const fakeBuildNoBodyResponse = sinon.fake();
658+
659+
const overridesWithFakeVerify = mergeOverrides(overrides, {
660+
'./HTTPModuleFunctions': {
661+
parseAndVerifyHTTPRequest: fakeParseAndVerify,
662+
parseHTTPRequestBody: sinon.fake(),
663+
buildNoBodyResponse: fakeBuildNoBodyResponse,
664+
'@noCallThru': true,
665+
},
666+
});
667+
668+
const HTTPReceiver = importHTTPReceiver(overridesWithFakeVerify);
669+
const receiver = new HTTPReceiver({
670+
signingSecret: 'secret',
671+
logger: noopLogger,
672+
invalidRequestSignatureHandler: spy,
673+
});
674+
675+
const fakeReq = sinon.createStubInstance(IncomingMessage) as unknown as IncomingMessage;
676+
fakeReq.url = '/slack/events';
677+
fakeReq.method = 'POST';
678+
fakeReq.headers = {};
679+
680+
const fakeRes = sinon.createStubInstance(ServerResponse) as unknown as ServerResponse;
681+
682+
receiver.requestListener(fakeReq, fakeRes);
683+
await new Promise((resolve) => setTimeout(resolve, 50));
684+
685+
assert(spy.calledOnce);
686+
const args = spy.firstCall.args[0];
687+
assert.equal(args.rawBody, '');
688+
assert.isUndefined(args.signature);
689+
assert.isUndefined(args.ts);
690+
});
691+
});
692+
574693
it("should throw if request doesn't match install path, redirect URI path, or custom routes", async () => {
575694
const installProviderStub = sinon.createStubInstance(InstallProvider);
576695
const HTTPReceiver = importHTTPReceiver(overrides);

0 commit comments

Comments
 (0)