Skip to content

Commit

Permalink
NONE: Update our webhook route to send a status code back immediately (
Browse files Browse the repository at this point in the history
…#207)

* NONE: Update our webhook route to send a status code back immediately

* update integration tests

* lol finish writing comments about whats going on w jest

* address comments
  • Loading branch information
Roystbeef authored Jan 30, 2024
1 parent 246679e commit 76f141b
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 67 deletions.
3 changes: 3 additions & 0 deletions src/infrastructure/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { EventEmitter } from 'events';

export const eventBus = new EventEmitter();
1 change: 1 addition & 0 deletions src/infrastructure/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './logger';
export * from './event-bus';
7 changes: 7 additions & 0 deletions src/infrastructure/testing/event-bus-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { eventBus } from '../event-bus';

export function waitForEvent(eventName: string): Promise<void> {
return new Promise((resolve) => {
eventBus.once(eventName, resolve);
});
}
1 change: 1 addition & 0 deletions src/infrastructure/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './event-bus-utils';
22 changes: 22 additions & 0 deletions src/jobs/handle-figma-file-update-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { FigmaTeam } from '../domain/entities';
import { eventBus, getLogger } from '../infrastructure';
import { handleFigmaFileUpdateEventUseCase } from '../usecases';
import type { FigmaFileUpdateWebhookEventRequestBody } from '../web/routes/figma';

export const handleFigmaFileUpdateEvent = async (
requestBody: FigmaFileUpdateWebhookEventRequestBody,
figmaTeam: FigmaTeam,
): Promise<void> => {
const { file_key: fileKey, webhook_id: webhookId } = requestBody;
try {
await handleFigmaFileUpdateEventUseCase.execute(figmaTeam, fileKey);

getLogger().info({ webhookId }, 'Figma webhook callback succeeded.');
eventBus.emit('figma.webhook.succeeded', { webhookId });
} catch (e) {
getLogger().error(e, 'Figma webhook callback failed.', {
webhookId,
});
eventBus.emit('figma.webhook.failed', { webhookId });
}
};
1 change: 1 addition & 0 deletions src/jobs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './handle-figma-file-update-event';
23 changes: 12 additions & 11 deletions src/web/routes/figma/figma-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import type {
} from './types';

import { getLogger } from '../../../infrastructure';
import {
handleFigmaAuthorizationResponseUseCase,
handleFigmaFileUpdateEventUseCase,
} from '../../../usecases';
import { handleFigmaFileUpdateEvent } from '../../../jobs';
import { handleFigmaAuthorizationResponseUseCase } from '../../../usecases';
import { requestSchemaValidationMiddleware } from '../../middleware';
import { figmaWebhookAuthMiddleware } from '../../middleware/figma/figma-webhook-auth-middleware';

Expand All @@ -33,18 +31,21 @@ figmaRouter.post(
'/webhook',
requestSchemaValidationMiddleware(FIGMA_WEBHOOK_EVENT_REQUEST_SCHEMA),
figmaWebhookAuthMiddleware,
(req: FigmaWebhookEventRequest, res: FigmaWebhookEventResponse, next) => {
(req: FigmaWebhookEventRequest, res: FigmaWebhookEventResponse) => {
const { figmaTeam } = res.locals;

switch (req.body.event_type) {
case 'FILE_UPDATE': {
const { file_key } = req.body;
// Making body its own variable so typescript is happy with the refinement
// we did on the discriminated union
const body = req.body;
void setImmediate(
() => void handleFigmaFileUpdateEvent(body, figmaTeam),
);

handleFigmaFileUpdateEventUseCase
.execute(figmaTeam, file_key)
.then(() => res.sendStatus(HttpStatusCode.Ok))
.catch(next);
return;
// Immediately send a 200 back to figma, before doing any of our own
// async processing
return res.sendStatus(HttpStatusCode.Ok);
}
default:
return res.sendStatus(HttpStatusCode.Ok);
Expand Down
94 changes: 38 additions & 56 deletions src/web/routes/figma/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
figmaOAuth2UserCredentialsRepository,
figmaTeamRepository,
} from '../../../infrastructure/repositories';
import { waitForEvent } from '../../../infrastructure/testing';
import {
mockFigmaGetFileEndpoint,
mockFigmaGetTeamProjectsEndpoint,
Expand Down Expand Up @@ -200,6 +201,8 @@ describe('/figma', () => {
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');
});

it('should ignore if no associated designs are found for the file key', async () => {
Expand All @@ -224,6 +227,8 @@ describe('/figma', () => {
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(otherFilewebhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');
});

it('should ignore if Figma file is not found', async () => {
Expand Down Expand Up @@ -259,6 +264,8 @@ describe('/figma', () => {
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');
});

it('should ingest designs for available Figma nodes and ignore deleted nodes', async () => {
Expand Down Expand Up @@ -317,6 +324,8 @@ describe('/figma', () => {
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');
});

it('should return a 200 if fetching Figma team name fails with non-auth error', async () => {
Expand Down Expand Up @@ -364,33 +373,15 @@ describe('/figma', () => {
),
});

await request(app)
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);
});

it("should set the FigmaTeam status to 'ERROR' and return a 200 if fetching Figma team name fails with auth error", async () => {
mockFigmaGetTeamProjectsEndpoint({
baseUrl: getConfig().figma.apiBaseUrl,
teamId: figmaTeam.teamId,
status: HttpStatusCode.Forbidden,
});

await request(app)
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

const updatedFigmaTeam = await figmaTeamRepository.findByWebhookId(
figmaTeam.webhookId,
);
expect(updatedFigmaTeam?.authStatus).toStrictEqual(
FigmaTeamAuthStatus.ERROR,
);
await waitForEvent('figma.webhook.succeeded');
});

it('should return error if fetching Figma designs fails with unexpected error', async () => {
it('should send an error event if fetching Figma designs fails with unexpected error', async () => {
const associatedFigmaDesigns =
await associatedFigmaDesignRepository.findManyByFileKeyAndConnectInstallationId(
fileKey,
Expand Down Expand Up @@ -422,7 +413,31 @@ describe('/figma', () => {
await request(app)
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.InternalServerError);
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.failed');
});

it("should set the FigmaTeam status to 'ERROR' and return a 200 if fetching Figma team name fails with auth error", async () => {
mockFigmaGetTeamProjectsEndpoint({
baseUrl: getConfig().figma.apiBaseUrl,
teamId: figmaTeam.teamId,
status: HttpStatusCode.Forbidden,
});

await request(app)
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');

const updatedFigmaTeam = await figmaTeamRepository.findByWebhookId(
figmaTeam.webhookId,
);
expect(updatedFigmaTeam?.authStatus).toStrictEqual(
FigmaTeamAuthStatus.ERROR,
);
});

it("should set the FigmaTeam status to 'ERROR' and return a 200 if fetching Figma designs fails with auth error", async () => {
Expand Down Expand Up @@ -459,6 +474,8 @@ describe('/figma', () => {
.send(webhookEventRequestBody)
.expect(HttpStatusCode.Ok);

await waitForEvent('figma.webhook.succeeded');

const updatedFigmaTeam = await figmaTeamRepository.findByWebhookId(
figmaTeam.webhookId,
);
Expand All @@ -481,41 +498,6 @@ describe('/figma', () => {
.send(webhookEventRequestBody)
.expect(HttpStatusCode.BadRequest);
});

it('should return a 500 if fetching designs from Figma fails', async () => {
const associatedFigmaDesigns =
await associatedFigmaDesignRepository.findManyByFileKeyAndConnectInstallationId(
fileKey,
connectInstallation.id,
);
const nodeIds = associatedFigmaDesigns
.map(({ designId }) => designId.nodeId!)
.filter(isString);

mockFigmaGetTeamProjectsEndpoint({
baseUrl: getConfig().figma.apiBaseUrl,
teamId: figmaTeam.teamId,
response: generateGetTeamProjectsResponse({
name: figmaTeam.teamName,
}),
});
mockFigmaGetFileEndpoint({
baseUrl: getConfig().figma.apiBaseUrl,
accessToken: adminFigmaOAuth2UserCredentials.accessToken,
fileKey: fileKey,
query: {
ids: nodeIds.join(','),
depth: '0',
node_last_modified: 'true',
},
status: HttpStatusCode.InternalServerError,
});

await request(app)
.post(FIGMA_WEBHOOK_EVENT_ENDPOINT)
.send(webhookEventRequestBody)
.expect(HttpStatusCode.InternalServerError);
});
});

describe('PING event', () => {
Expand Down

0 comments on commit 76f141b

Please sign in to comment.