Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
9.5 kB ยท 294 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295import "./test-preload.ts";import { db, eq } from "@openstatus/db";import { pageSubscriber, pageSubscriberToPageComponent, statusReport, statusReportUpdate, statusReportUpdateToPageComponents, statusReportsToPageComponents,} from "@openstatus/db/src/schema";import { createPage, createPageComponent, createTestWorkspace,} from "@openstatus/db/src/test/factories";import { EmailClient } from "@openstatus/emails";import { expect } from "@std/expect";import { afterAll, afterEach, beforeAll, beforeEach, describe, test,} from "@std/testing/bdd";import { assertSpyCalls, type Stub, stub } from "@std/testing/mock";
import { dispatchPageUpdate, dispatchStatusReportUpdate } from "./dispatcher";import type { PageUpdate } from "./types";
// RESEND_API_KEY is set in test-preload.ts (see bunfig.toml) so @openstatus/emails// loads successfully and EmailClient prototype methods can be spied on.
let sendStatusReportUpdateMock: Stub<EmailClient>;let rejectNextSend: Error | null = null;
// Built in `beforeAll` โ a private page keeps the subscriber set this suite// dispatches to independent of anything else running against the database.let PAGE_ID: number;let COMPONENT_1: number;let COMPONENT_2: number;
const EMAILS = { entirePage: "dispatcher-page-test@example.com", component1: "dispatcher-comp1-test@example.com", component2: "dispatcher-comp2-test@example.com",};
// Captured IDs after insertlet _subEntirePageId: number;let subComponent1Id: number;let subComponent2Id: number;
function makePageUpdate(overrides: Partial<PageUpdate> = {}): PageUpdate { return { id: 1, pageId: PAGE_ID, title: "Test Incident", status: "investigating", message: "We are investigating.", pageComponentIds: [], pageComponents: [], date: new Date().toISOString(), ...overrides, };}
async function cleanAll() { for (const email of Object.values(EMAILS)) { await db.delete(pageSubscriber).where(eq(pageSubscriber.email, email)); }}
beforeAll(async () => { const { workspace } = await createTestWorkspace(); PAGE_ID = (await createPage(workspace.id)).id; COMPONENT_1 = (await createPageComponent(workspace.id, PAGE_ID)).id; COMPONENT_2 = (await createPageComponent(workspace.id, PAGE_ID, { order: 1 })) .id;
await cleanAll();
const insertAccepted = async (email: string) => { return db .insert(pageSubscriber) .values({ channelType: "email", email, pageId: PAGE_ID, token: crypto.randomUUID(), acceptedAt: new Date(), expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }) .returning() .get(); };
const subEntirePage = await insertAccepted(EMAILS.entirePage); const subComponent1 = await insertAccepted(EMAILS.component1); const subComponent2 = await insertAccepted(EMAILS.component2);
_subEntirePageId = subEntirePage.id; subComponent1Id = subComponent1.id; subComponent2Id = subComponent2.id;
// subEntirePage has no component associations โ entire-page scope // subComponent1 subscribes to component 1 only await db .insert(pageSubscriberToPageComponent) .values({ pageSubscriberId: subComponent1Id, pageComponentId: COMPONENT_1 }) .run();
// subComponent2 subscribes to component 2 only await db .insert(pageSubscriberToPageComponent) .values({ pageSubscriberId: subComponent2Id, pageComponentId: COMPONENT_2 }) .run();});
afterAll(cleanAll);
beforeEach(() => { rejectNextSend = null; sendStatusReportUpdateMock = stub( EmailClient.prototype, "sendStatusReportUpdate", () => { if (rejectNextSend) { const error = rejectNextSend; rejectNextSend = null; return Promise.reject(error); } return Promise.resolve(undefined); }, );});
afterEach(() => { sendStatusReportUpdateMock.restore();});
// โโโ dispatchPageUpdate - component filtering โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
describe("dispatchPageUpdate - component filtering", () => { test("notifies entire-page and component-1 subscriber when update affects component 1", async () => { await dispatchPageUpdate( makePageUpdate({ pageComponentIds: [COMPONENT_1] }), );
assertSpyCalls(sendStatusReportUpdateMock, 1); const { subscribers } = sendStatusReportUpdateMock.calls[0].args[0]; const emails = subscribers.map((s: { email: string }) => s.email);
expect(emails).toContain(EMAILS.entirePage); expect(emails).toContain(EMAILS.component1); expect(emails).not.toContain(EMAILS.component2); });
test("notifies entire-page and component-2 subscriber when update affects component 2", async () => { await dispatchPageUpdate( makePageUpdate({ pageComponentIds: [COMPONENT_2] }), );
const { subscribers } = sendStatusReportUpdateMock.calls[0].args[0]; const emails = subscribers.map((s: { email: string }) => s.email);
expect(emails).toContain(EMAILS.entirePage); expect(emails).toContain(EMAILS.component2); expect(emails).not.toContain(EMAILS.component1); });
test("notifies all subscribers when update affects both components", async () => { await dispatchPageUpdate( makePageUpdate({ pageComponentIds: [COMPONENT_1, COMPONENT_2] }), );
const { subscribers } = sendStatusReportUpdateMock.calls[0].args[0]; const emails = subscribers.map((s: { email: string }) => s.email);
expect(emails).toContain(EMAILS.entirePage); expect(emails).toContain(EMAILS.component1); expect(emails).toContain(EMAILS.component2); });
test("notifies only the entire-page subscriber when update has no affected components", async () => { await dispatchPageUpdate(makePageUpdate({ pageComponentIds: [] }));
assertSpyCalls(sendStatusReportUpdateMock, 1); const { subscribers } = sendStatusReportUpdateMock.calls[0].args[0]; const emails = subscribers.map((s: { email: string }) => s.email);
// Assert only against this suite's own subscribers โ package test suites // run in parallel against the same seeded page, so an exact count over // all recipients races with concurrently inserted subscribers. const ownEmails = emails.filter((e: string) => Object.values(EMAILS).includes(e), ); expect(ownEmails).toEqual([EMAILS.entirePage]); });
test("does not notify component subscribers when update affects a different component", async () => { // Only component 1 is affected โ component 2 subscriber should be skipped await dispatchPageUpdate( makePageUpdate({ pageComponentIds: [COMPONENT_1] }), );
const { subscribers } = sendStatusReportUpdateMock.calls[0].args[0]; const emails = subscribers.map((s: { email: string }) => s.email);
expect(emails).not.toContain(EMAILS.component2); });});
// โโโ dispatchPageUpdate - edge cases โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
describe("dispatchPageUpdate - edge cases", () => { test("does not call sendNotifications for a non-existent page", async () => { await dispatchPageUpdate( makePageUpdate({ pageId: 99999, pageComponentIds: [COMPONENT_1] }), );
assertSpyCalls(sendStatusReportUpdateMock, 0); });
test("does not propagate channel failure โ resolves even when sendStatusReportUpdate throws", async () => { rejectNextSend = new Error("SMTP failure");
await expect( dispatchPageUpdate(makePageUpdate({ pageComponentIds: [] })), ).resolves.toBeUndefined(); });});
// โโโ dispatchStatusReportUpdate - impacts โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
describe("dispatchStatusReportUpdate - impacts", () => { test("an earlier update is sent with the impacts as of that update", async () => { const report = await db .insert(statusReport) .values({ status: "investigating", title: "Late dispatch", pageId: PAGE_ID, }) .returning() .get(); await db .insert(statusReportsToPageComponents) .values({ statusReportId: report.id, pageComponentId: COMPONENT_1 }) .run();
const insertUpdate = async ( date: Date, impact: "major_outage" | "operational", ) => { const update = await db .insert(statusReportUpdate) .values({ status: "investigating", date, message: "msg", statusReportId: report.id, }) .returning() .get(); await db .insert(statusReportUpdateToPageComponents) .values({ statusReportUpdateId: update.id, pageComponentId: COMPONENT_1, impact, }) .run(); return update; };
const earlier = await insertUpdate( new Date("2026-01-01T10:00:00Z"), "major_outage", ); await insertUpdate(new Date("2026-01-01T11:00:00Z"), "operational");
try { await dispatchStatusReportUpdate(earlier.id); assertSpyCalls(sendStatusReportUpdateMock, 1); const args = sendStatusReportUpdateMock.calls[0].args[0]; expect( args.componentImpacts.map((c: { impact: string }) => c.impact), ).toEqual(["major_outage"]); } finally { await db.delete(statusReport).where(eq(statusReport.id, report.id)); } });});