first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import { createHmac, timingSafeEqual } from 'crypto';
|
||||
import { verifySignature } from '../GithubTriggerHelpers';
|
||||
|
||||
jest.mock('crypto', () => ({
|
||||
...jest.requireActual('crypto'),
|
||||
createHmac: jest.fn().mockReturnValue({
|
||||
update: jest.fn().mockReturnThis(),
|
||||
digest: jest
|
||||
.fn()
|
||||
.mockReturnValue('757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17'),
|
||||
}),
|
||||
timingSafeEqual: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('GithubTriggerHelpers', () => {
|
||||
let mockWebhookFunctions: {
|
||||
getWorkflowStaticData: jest.Mock;
|
||||
getRequestObject: jest.Mock;
|
||||
};
|
||||
const testWebhookSecret = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2';
|
||||
const testBody =
|
||||
'{"action":"opened","pull_request":{"id":123},"repository":{"full_name":"owner/repo"}}';
|
||||
const testSignature = 'sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockWebhookFunctions = {
|
||||
getWorkflowStaticData: jest.fn(),
|
||||
getRequestObject: jest.fn(),
|
||||
};
|
||||
|
||||
// Default mock return values
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({
|
||||
webhookSecret: testWebhookSecret,
|
||||
});
|
||||
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-hub-signature-256') return testSignature;
|
||||
return null;
|
||||
}),
|
||||
rawBody: testBody,
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifySignature', () => {
|
||||
it('should return true when no webhook secret is stored (backwards compatibility)', () => {
|
||||
mockWebhookFunctions.getWorkflowStaticData.mockReturnValue({});
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockWebhookFunctions.getWorkflowStaticData).toHaveBeenCalledWith('node');
|
||||
});
|
||||
|
||||
it('should return false when signature header is missing', () => {
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockReturnValue(null),
|
||||
rawBody: testBody,
|
||||
});
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when signature does not start with sha256=', () => {
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-hub-signature-256') return 'invalid-format-signature';
|
||||
return null;
|
||||
}),
|
||||
rawBody: testBody,
|
||||
});
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when rawBody is missing', () => {
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-hub-signature-256') return testSignature;
|
||||
return null;
|
||||
}),
|
||||
rawBody: undefined,
|
||||
});
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when signature is valid', () => {
|
||||
(timingSafeEqual as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(createHmac).toHaveBeenCalledWith('sha256', testWebhookSecret);
|
||||
expect(timingSafeEqual).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false when signature is invalid', () => {
|
||||
(timingSafeEqual as jest.Mock).mockReturnValue(false);
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(createHmac).toHaveBeenCalledWith('sha256', testWebhookSecret);
|
||||
expect(timingSafeEqual).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle Buffer rawBody correctly', () => {
|
||||
const bufferBody = Buffer.from(testBody);
|
||||
mockWebhookFunctions.getRequestObject.mockReturnValue({
|
||||
header: jest.fn().mockImplementation((header) => {
|
||||
if (header === 'x-hub-signature-256') return testSignature;
|
||||
return null;
|
||||
}),
|
||||
rawBody: bufferBody,
|
||||
});
|
||||
(timingSafeEqual as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(true);
|
||||
const mockHmac = createHmac('sha256', testWebhookSecret);
|
||||
expect(mockHmac.update).toHaveBeenCalledWith(bufferBody);
|
||||
});
|
||||
|
||||
it('should return false when computed and provided signatures have different lengths', () => {
|
||||
// Mock a different length signature
|
||||
const mockHmacInstance = {
|
||||
update: jest.fn().mockReturnThis(),
|
||||
digest: jest.fn().mockReturnValue('short'),
|
||||
};
|
||||
(createHmac as jest.Mock).mockReturnValue(mockHmacInstance);
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
// timingSafeEqual should not be called if lengths don't match
|
||||
expect(timingSafeEqual).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return false when an error occurs during verification', () => {
|
||||
(createHmac as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('Crypto error');
|
||||
});
|
||||
|
||||
const result = verifySignature.call(mockWebhookFunctions as never);
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user