diff --git a/src/buildServer.ts b/src/buildServer.ts index 8fc6a727..21dbd43b 100644 --- a/src/buildServer.ts +++ b/src/buildServer.ts @@ -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' }); diff --git a/src/jobs/index.ts b/src/jobs/index.ts index c2324cfa..80ddc026 100644 --- a/src/jobs/index.ts +++ b/src/jobs/index.ts @@ -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 { server.post('/stale-triage-notifier', (request, reply) => handleJobRoute(notifyProductOwnersForUntriagedIssues, request, reply) ); diff --git a/src/webhooks/bootstrap-dev-env/bootstrap-dev-env.ts b/src/webhooks/bootstrap-dev-env/bootstrap-dev-env.ts index e18cb5fc..140475d3 100644 --- a/src/webhooks/bootstrap-dev-env/bootstrap-dev-env.ts +++ b/src/webhooks/bootstrap-dev-env/bootstrap-dev-env.ts @@ -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 { const { body: payload } = request; const now = new Date(); @@ -20,5 +21,7 @@ export async function bootstrapWebhook( }, }); - return {}; + reply.code(200).send('OK'); + + return; } diff --git a/src/webhooks/gocd/gocd.ts b/src/webhooks/gocd/gocd.ts index 11c9b2b6..1e2eafef 100644 --- a/src/webhooks/gocd/gocd.ts +++ b/src/webhooks/gocd/gocd.ts @@ -9,7 +9,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur export async function gocdWebhook( request: FastifyRequest<{ Body: GoCDResponse }>, reply: FastifyReply -) { +): Promise { try { // If the webhook secret is not defined, throw an error if (GOCD_WEBHOOK_SECRET === undefined) { diff --git a/src/webhooks/index.test.ts b/src/webhooks/index.test.ts new file mode 100644 index 00000000..983188da --- /dev/null +++ b/src/webhooks/index.test.ts @@ -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(); + }); +}); diff --git a/src/webhooks/index.ts b/src/webhooks/index.ts index 594b91e9..24cf8a9c 100644 --- a/src/webhooks/index.ts +++ b/src/webhooks/index.ts @@ -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 { 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 { server.post('/metrics/bootstrap-dev-env/webhook', (request, reply) => handleRoute(bootstrapWebhook, request, reply) ); diff --git a/src/webhooks/kafka-control-plane/kafka-control-plane.ts b/src/webhooks/kafka-control-plane/kafka-control-plane.ts index beb6dea0..88b2b9f2 100644 --- a/src/webhooks/kafka-control-plane/kafka-control-plane.ts +++ b/src/webhooks/kafka-control-plane/kafka-control-plane.ts @@ -15,7 +15,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur export async function kafkactlWebhook( request: FastifyRequest<{ Body: KafkaControlPlaneResponse }>, reply: FastifyReply -) { +): Promise { try { if (KAFKA_CONTROL_PLANE_WEBHOOK_SECRET === undefined) { throw new TypeError('KAFKA_CONTROL_PLANE_WEBHOOK_SECRET must be set'); @@ -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; } } diff --git a/src/webhooks/sentry-options/sentry-options.ts b/src/webhooks/sentry-options/sentry-options.ts index 2897d50d..cff6286b 100644 --- a/src/webhooks/sentry-options/sentry-options.ts +++ b/src/webhooks/sentry-options/sentry-options.ts @@ -18,7 +18,7 @@ import { extractAndVerifySignature } from '@/utils/auth/extractAndVerifySignatur export async function sentryOptionsWebhook( request: FastifyRequest<{ Body: SentryOptionsResponse }>, reply: FastifyReply -) { +): Promise { try { // If the webhook secret is not defined, throw an error if (SENTRY_OPTIONS_WEBHOOK_SECRET === undefined) { diff --git a/src/webhooks/webpack/webpack.ts b/src/webhooks/webpack/webpack.ts index 3520d0cb..9bec7eb2 100644 --- a/src/webhooks/webpack/webpack.ts +++ b/src/webhooks/webpack/webpack.ts @@ -9,14 +9,14 @@ export async function webpackWebhook( Body: { pull_request_number: number } & Record; }>, reply: FastifyReply -) { +): Promise { if (!verifyWebhook(request)) { reply.code(400); - return {}; + return; } insertAssetSize(request.body); reply.code(204); - return {}; + return; }