Files
outline/server/models/WebhookSubscription.test.ts
T
Tom Moor 0c0facc2a1 perf: Avoid empty webhook processor work via cached subscription lookup (#12593)
* Avoid empty webhook processor work via cached subscription lookup

WebhookProcessor ran for every event but most teams have no matching
webhook subscription, costing an empty processor job and a database query
per event.

Cache a team's enabled subscriptions ({ id, events }) in Redis, invalidated
by model lifecycle hooks, and add an optional BaseProcessor.shouldQueue hook
consulted by the global event queue so the webhook processor only enqueues a
job when a matching subscription exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feedback

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:53:40 -04:00

95 lines
2.7 KiB
TypeScript

import { buildTeam, buildWebhookSubscription } from "@server/test/factories";
import WebhookSubscription from "./WebhookSubscription";
describe("WebhookSubscription", () => {
describe("matchEvent", () => {
it("matches everything for a wildcard subscription", () => {
expect(WebhookSubscription.matchEvent(["*"], "users.signin")).toBe(true);
});
it("matches an exact event name", () => {
expect(
WebhookSubscription.matchEvent(["users.signin"], "users.signin")
).toBe(true);
});
it("matches a namespace prefix", () => {
expect(WebhookSubscription.matchEvent(["users"], "users.signin")).toBe(
true
);
});
it("does not match unrelated events", () => {
expect(
WebhookSubscription.matchEvent(["documents"], "users.signin")
).toBe(false);
});
});
describe("findEnabledByTeamId", () => {
it("returns only enabled subscriptions for the team", async () => {
const subscription = await buildWebhookSubscription({
events: ["users"],
});
const disabled = await buildWebhookSubscription({
teamId: subscription.teamId,
events: ["documents"],
});
await disabled.disable();
const result = await WebhookSubscription.findEnabledByTeamId(
subscription.teamId
);
expect(result).toHaveLength(1);
expect(result[0].id).toEqual(subscription.id);
expect(result[0].events).toEqual(["users"]);
});
it("returns an empty array when the team has no subscriptions", async () => {
const team = await buildTeam();
const result = await WebhookSubscription.findEnabledByTeamId(team.id);
expect(result).toEqual([]);
});
it("reflects changes after a subscription is disabled", async () => {
const subscription = await buildWebhookSubscription({
events: ["users"],
});
// prime the cache
const before = await WebhookSubscription.findEnabledByTeamId(
subscription.teamId
);
expect(before).toHaveLength(1);
await subscription.disable();
const after = await WebhookSubscription.findEnabledByTeamId(
subscription.teamId
);
expect(after).toHaveLength(0);
});
it("reflects changes after a subscription is destroyed", async () => {
const subscription = await buildWebhookSubscription({
events: ["users"],
});
const before = await WebhookSubscription.findEnabledByTeamId(
subscription.teamId
);
expect(before).toHaveLength(1);
await subscription.destroy();
const after = await WebhookSubscription.findEnabledByTeamId(
subscription.teamId
);
expect(after).toHaveLength(0);
});
});
});