Skip to content

Commit

Permalink
Add test coverage for loading webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
brian-lou committed Oct 15, 2024
1 parent 5604fc4 commit c72e96a
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/buildServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export async function buildServer(
await loadBrain();

// Other webhooks operate as regular Fastify handlers
routeHandlers(server);
server.register(routeHandlers);

// Endpoints for Cloud Scheduler webhooks (Cron Jobs)
server.register(routeJobs, { prefix: '/jobs' });
Expand Down
2 changes: 1 addition & 1 deletion src/jobs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export const opts = {
};

// Function that creates a sub fastify server for job webhooks
export async function routeJobs(server: Fastify, _options) {
export async function routeJobs(server: Fastify, _options): Promise<void> {
server.post('/stale-triage-notifier', (request, reply) =>
handleJobRoute(notifyProductOwnersForUntriagedIssues, request, reply)
);
Expand Down
11 changes: 7 additions & 4 deletions src/webhooks/bootstrap-dev-env/bootstrap-dev-env.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { FastifyRequest } from 'fastify';
import { FastifyReply, FastifyRequest } from 'fastify';

import { insert } from '@/utils/db/metrics';

export async function bootstrapWebhook(
request: FastifyRequest<{ Body: { event: string; name: string } }>
) {
request: FastifyRequest<{ Body: { event: string; name: string } }>,
reply: FastifyReply
): Promise<void> {
const { body: payload } = request;

const now = new Date();
Expand All @@ -20,5 +21,7 @@ export async function bootstrapWebhook(
},
});

return {};
reply.code(200).send('OK');

return;
}
2 changes: 1 addition & 1 deletion src/webhooks/gocd/gocd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur
export async function gocdWebhook(
request: FastifyRequest<{ Body: GoCDResponse }>,
reply: FastifyReply
) {
): Promise<void> {
try {
// If the webhook secret is not defined, throw an error
if (GOCD_WEBHOOK_SECRET === undefined) {
Expand Down
138 changes: 138 additions & 0 deletions src/webhooks/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import fastify, {
FastifyInstance,
FastifyReply,
FastifyRequest,
} from 'fastify';

import * as bootstrapDevEnv from './bootstrap-dev-env/bootstrap-dev-env';
import * as gocdWebhooks from './gocd/gocd';
import * as kafkaWebhooks from './kafka-control-plane/kafka-control-plane';
import * as sentryOptionsWebhooks from './sentry-options/sentry-options';
import * as webpackWebhooks from './webpack/webpack';
import { routeHandlers } from '.';

const mockBootstrapWebhook = jest.fn(
async (_request: FastifyRequest, response: FastifyReply) => {
response.code(200).send('OK');
}
);
const mockGocd = jest.fn(
async (_request: FastifyRequest, response: FastifyReply) => {
response.code(200).send('OK');
}
);
const mockKafkaCtlPlane = jest.fn(
async (_request: FastifyRequest, response: FastifyReply) => {
response.code(200).send('OK');
}
);
const mockSentryOptions = jest.fn(
async (_request: FastifyRequest, response: FastifyReply) => {
response.code(200).send('OK');
}
);
const mockWebpack = jest.fn(
async (_request: FastifyRequest, response: FastifyReply) => {
response.code(204).send();
}
);

jest
.spyOn(bootstrapDevEnv, 'bootstrapWebhook')
.mockImplementation(mockBootstrapWebhook);

jest.spyOn(gocdWebhooks, 'gocdWebhook').mockImplementation(mockGocd);

jest
.spyOn(kafkaWebhooks, 'kafkactlWebhook')
.mockImplementation(mockKafkaCtlPlane);

jest
.spyOn(sentryOptionsWebhooks, 'sentryOptionsWebhook')
.mockImplementation(mockSentryOptions);

jest.spyOn(webpackWebhooks, 'webpackWebhook').mockImplementation(mockWebpack);

describe('cron jobs testing', function () {
let server: FastifyInstance;

beforeEach(async function () {
server = fastify();
server.register(routeHandlers);
mockBootstrapWebhook.mockClear();
mockGocd.mockClear();
mockKafkaCtlPlane.mockClear();
mockSentryOptions.mockClear();
mockWebpack.mockClear();
});

afterEach(() => {
server.close();
jest.clearAllMocks();
});

it('POST /metrics/bootstrap-dev-env/webhook should call bootstrapWebhook', async () => {
const reply = await server.inject({
method: 'POST',
url: '/metrics/bootstrap-dev-env/webhook',
});
expect(reply.statusCode).toBe(200);
expect(mockBootstrapWebhook).toHaveBeenCalled();
expect(mockGocd).not.toHaveBeenCalled();
expect(mockKafkaCtlPlane).not.toHaveBeenCalled();
expect(mockSentryOptions).not.toHaveBeenCalled();
expect(mockWebpack).not.toHaveBeenCalled();
});

it('POST /metrics/gocd/webhook should call gocdWebhook', async () => {
const reply = await server.inject({
method: 'POST',
url: '/metrics/gocd/webhook',
});
expect(reply.statusCode).toBe(200);
expect(mockBootstrapWebhook).not.toHaveBeenCalled();
expect(mockGocd).toHaveBeenCalled();
expect(mockKafkaCtlPlane).not.toHaveBeenCalled();
expect(mockSentryOptions).not.toHaveBeenCalled();
expect(mockWebpack).not.toHaveBeenCalled();
});

it('POST /metrics/kafka-control-plane/webhook should call kafkactlWebhook', async () => {
const reply = await server.inject({
method: 'POST',
url: '/metrics/kafka-control-plane/webhook',
});
expect(reply.statusCode).toBe(200);
expect(mockBootstrapWebhook).not.toHaveBeenCalled();
expect(mockGocd).not.toHaveBeenCalled();
expect(mockKafkaCtlPlane).toHaveBeenCalled();
expect(mockSentryOptions).not.toHaveBeenCalled();
expect(mockWebpack).not.toHaveBeenCalled();
});

it('POST /metrics/sentry-options/webhook should call sentryOptionsWebhook', async () => {
const reply = await server.inject({
method: 'POST',
url: '/metrics/sentry-options/webhook',
});
expect(reply.statusCode).toBe(200);
expect(mockBootstrapWebhook).not.toHaveBeenCalled();
expect(mockGocd).not.toHaveBeenCalled();
expect(mockKafkaCtlPlane).not.toHaveBeenCalled();
expect(mockSentryOptions).toHaveBeenCalled();
expect(mockWebpack).not.toHaveBeenCalled();
});

it('POST /metrics/webpack/webhook should call webpackWebhook', async () => {
const reply = await server.inject({
method: 'POST',
url: '/metrics/webpack/webhook',
});
expect(reply.statusCode).toBe(204);
expect(mockBootstrapWebhook).not.toHaveBeenCalled();
expect(mockGocd).not.toHaveBeenCalled();
expect(mockKafkaCtlPlane).not.toHaveBeenCalled();
expect(mockSentryOptions).not.toHaveBeenCalled();
expect(mockWebpack).toHaveBeenCalled();
});
});
24 changes: 15 additions & 9 deletions src/webhooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import * as Sentry from '@sentry/node';
import { FastifyReply, FastifyRequest } from 'fastify';

import { Fastify } from '@/types';

import { bootstrapWebhook } from './bootstrap-dev-env';
import { gocdWebhook } from './gocd';
import { kafkactlWebhook } from './kafka-control-plane';
import { sentryOptionsWebhook } from './sentry-options';
import { webpackWebhook } from './webpack';
import { bootstrapWebhook } from './bootstrap-dev-env/bootstrap-dev-env';
import { gocdWebhook } from './gocd/gocd';
import { kafkactlWebhook } from './kafka-control-plane/kafka-control-plane';
import { sentryOptionsWebhook } from './sentry-options/sentry-options';
import { webpackWebhook } from './webpack/webpack';

// Error handling wrapper function
async function handleRoute(handler, request, reply) {
async function handleRoute(
handler,
request: FastifyRequest,
reply: FastifyReply
): Promise<void> {
try {
return await handler(request, reply);
await handler(request, reply);
} catch (err) {
console.error(err);
Sentry.captureException(err);
return reply.code(400).send('Bad Request');
reply.code(400).send('Bad Request');
return;
}
}

// Function that maps routes to their respective handlers
export function routeHandlers(server: Fastify) {
export async function routeHandlers(server: Fastify, _options): Promise<void> {
server.post('/metrics/bootstrap-dev-env/webhook', (request, reply) =>
handleRoute(bootstrapWebhook, request, reply)
);
Expand Down
8 changes: 5 additions & 3 deletions src/webhooks/kafka-control-plane/kafka-control-plane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur
export async function kafkactlWebhook(
request: FastifyRequest<{ Body: KafkaControlPlaneResponse }>,
reply: FastifyReply
) {
): Promise<void> {
try {
if (KAFKA_CONTROL_PLANE_WEBHOOK_SECRET === undefined) {
throw new TypeError('KAFKA_CONTROL_PLANE_WEBHOOK_SECRET must be set');
Expand All @@ -34,10 +34,12 @@ export async function kafkactlWebhook(

const { body }: { body: KafkaControlPlaneResponse } = request;
await messageSlack(body);
return reply.code(200).send('OK');
reply.code(200).send('OK');
return;
} catch (err) {
Sentry.captureException(err);
return reply.code(500).send();
reply.code(500).send();
return;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/webhooks/sentry-options/sentry-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur
export async function sentryOptionsWebhook(
request: FastifyRequest<{ Body: SentryOptionsResponse }>,
reply: FastifyReply
) {
): Promise<void> {
try {
// If the webhook secret is not defined, throw an error
if (SENTRY_OPTIONS_WEBHOOK_SECRET === undefined) {
Expand Down
6 changes: 3 additions & 3 deletions src/webhooks/webpack/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export async function webpackWebhook(
Body: { pull_request_number: number } & Record<string, any>;
}>,
reply: FastifyReply
) {
): Promise<void> {
if (!verifyWebhook(request)) {
reply.code(400);
return {};
return;
}

insertAssetSize(request.body);

reply.code(204);
return {};
return;
}

0 comments on commit c72e96a

Please sign in to comment.