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.
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485import { eq } from "@openstatus/db";import { monitor, notification, page, pageComponent, selectWorkspaceSchema, statusReport, workspace,} from "@openstatus/db/src/schema";import { getLimits } from "@openstatus/db/src/schema/plan/utils";import { expect } from "@std/expect";import { beforeAll, describe, test } from "@std/testing/bdd";
import { createWorkspaceFixture, expectAuditRow, makeApiKeyCtx, makeSystemCtx, makeUserCtx, readAuditLog, withTestTransaction,} from "../../../test/helpers";import type { DrizzleTx, ServiceContext } from "../../context";import { ForbiddenError } from "../../errors";import { getWorkspace, getWorkspaceByStripeId, getWorkspaceUsage, listWorkspaces, updateWorkspaceName, updateWorkspacePlan,} from "../index.ts";
let teamCtx: ServiceContext;let teamUserId: number;
beforeAll(async () => { const team = await createWorkspaceFixture("team"); teamUserId = team.userId; teamCtx = makeUserCtx(team.workspace, { userId: teamUserId });});
describe("getWorkspace", () => { test("returns the caller's workspace", async () => { await withTestTransaction(async (tx) => { const result = await getWorkspace({ ctx: { ...teamCtx, db: tx } }); expect(result.id).toBe(teamCtx.workspace.id); expect(typeof result.limits).toBe("object"); }); });});
describe("getWorkspaceUsage", () => { test("returns a zero-or-positive count for every key", async () => { await withTestTransaction(async (tx) => { const result = await getWorkspaceUsage({ ctx: { ...teamCtx, db: tx }, });
// Iterate the usage object *before* any `toMatchObject` call โ // the `toMatchObject` implementation mutates the received // object in place, replacing number fields with the // `expect.any(Number)` asymmetric-matcher stub on the expected // side. Subsequent reads of `result.<key>` then return the // matcher object (typeof "object"), not the original count. Do // the value-shape + non-negative check first. for (const key of [ "monitors", "notifications", "pages", "pageComponents", "statusReports", "checks", ] as const) { const value = result[key]; expect(typeof value).toBe("number"); if (typeof value === "number") { expect(value).toBeGreaterThanOrEqual(0); } } expect(result.checks).toBe(0); }); });
test("counts workspace-scoped status reports", async () => { await withTestTransaction(async (tx) => { await tx.insert(statusReport).values({ workspaceId: teamCtx.workspace.id, status: "investigating", title: "svc-ws-test-status-report", }); const result = await getWorkspaceUsage({ ctx: { ...teamCtx, db: tx }, }); expect(result.statusReports).toBeGreaterThanOrEqual(1); }); });
// The usage block is six independent counts rather than a relational read; // each one must move by exactly the number of rows inserted. test("each count moves by exactly the rows added", async () => { await withTestTransaction(async (tx) => { const ctx = { ...teamCtx, db: tx }; const workspaceId = teamCtx.workspace.id; const before = await getWorkspaceUsage({ ctx });
await tx.insert(monitor).values([ { workspaceId, url: "https://a.example.dev", name: "svc-ws-usage-1" }, { workspaceId, url: "https://b.example.dev", name: "svc-ws-usage-2" }, ]); await tx.insert(notification).values({ workspaceId, name: "svc-ws-usage-notif", provider: "email", data: JSON.stringify({ email: "usage@openstatus.dev" }), }); const [pageRow] = await tx .insert(page) .values({ workspaceId, title: "svc-ws-usage-page", description: "", slug: `svc-ws-usage-${workspaceId}`, customDomain: "", }) .returning(); if (!pageRow) throw new Error("page insert returned no row"); await tx.insert(pageComponent).values([ { workspaceId, pageId: pageRow.id, type: "static", name: "svc-ws-usage-c1", order: 0, }, { workspaceId, pageId: pageRow.id, type: "static", name: "svc-ws-usage-c2", order: 1, }, ]); await tx.insert(statusReport).values({ workspaceId, status: "investigating", title: "svc-ws-usage-report", });
const after = await getWorkspaceUsage({ ctx }); expect(after.monitors).toBe(before.monitors + 2); expect(after.notifications).toBe(before.notifications + 1); expect(after.pages).toBe(before.pages + 1); expect(after.pageComponents).toBe(before.pageComponents + 2); expect(after.statusReports).toBe(before.statusReports + 1); expect(after.checks).toBe(0); }); });
test("excludes soft-deleted monitors from the monitor count", async () => { await withTestTransaction(async (tx) => { const ctx = { ...teamCtx, db: tx }; const workspaceId = teamCtx.workspace.id; const before = (await getWorkspaceUsage({ ctx })).monitors;
await tx.insert(monitor).values({ workspaceId, url: "https://deleted.example.dev", name: "svc-ws-usage-deleted", deletedAt: new Date(), });
const after = (await getWorkspaceUsage({ ctx })).monitors; expect(after).toBe(before); }); });
test("does not count another workspace's rows", async () => { await withTestTransaction(async (tx) => { const ctx = { ...teamCtx, db: tx }; const before = await getWorkspaceUsage({ ctx });
const [foreign] = await tx .insert(workspace) .values({ slug: `svc-ws-usage-foreign-${teamCtx.workspace.id}`, name: "Foreign", stripeId: `svc-ws-usage-stripe-${teamCtx.workspace.id}`, plan: "team", }) .returning(); if (!foreign) throw new Error("workspace insert returned no row"); await tx.insert(monitor).values({ workspaceId: foreign.id, url: "https://foreign.example.dev", name: "svc-ws-usage-foreign-monitor", }); await tx.insert(statusReport).values({ workspaceId: foreign.id, status: "investigating", title: "svc-ws-usage-foreign-report", });
const after = await getWorkspaceUsage({ ctx }); expect(after.monitors).toBe(before.monitors); expect(after.statusReports).toBe(before.statusReports); }); });});
describe("listWorkspaces", () => { test("returns every workspace a user belongs to", async () => { await withTestTransaction(async (tx) => { const rows = await listWorkspaces({ ctx: { ...teamCtx, db: tx }, input: { userId: teamUserId }, }); const ids = rows.map((r) => r.id); expect(ids).toContain(teamCtx.workspace.id); }); });
test("returns an empty list for a user with no memberships", async () => { await withTestTransaction(async (tx) => { const rows = await listWorkspaces({ ctx: { ...teamCtx, db: tx }, input: { userId: 999_999 }, }); expect(rows).toEqual([]); }); });});
describe("updateWorkspaceName", () => { test("rejects read-only actor", async () => { await withTestTransaction(async (tx) => { const readOnlyCtx = { ...makeApiKeyCtx(teamCtx.workspace, { keyId: "k-read", userId: 1, scopes: ["read"], }), db: tx, }; await expect( updateWorkspaceName({ ctx: readOnlyCtx, input: { name: "blocked" }, }), ).rejects.toBeInstanceOf(ForbiddenError); }); });
test("renames the workspace and emits an audit row", async () => { await withTestTransaction(async (tx) => { const ctx = { ...teamCtx, db: tx }; const nextName = `svc-ws-test-${Date.now()}`; await updateWorkspaceName({ ctx, input: { name: nextName }, });
const row = await tx .select() .from(workspace) .where(eq(workspace.id, teamCtx.workspace.id)) .get(); expect(row?.name).toBe(nextName);
await expectAuditRow({ workspaceId: teamCtx.workspace.id, action: "workspace.update", entityType: "workspace", entityId: teamCtx.workspace.id, actorType: "user", db: tx, }); }); });});
describe("getWorkspaceByStripeId", () => { test("resolves the workspace for a known stripe customer id", async () => { await withTestTransaction(async (tx) => { const inserted = await tx .insert(workspace) .values({ slug: "svc-ws-by-stripe", name: "Stripe Lookup", plan: "team", stripeId: "cus_svc_ws_by_stripe", limits: JSON.stringify(getLimits("team")), }) .returning() .get();
const result = await getWorkspaceByStripeId({ input: { stripeId: "cus_svc_ws_by_stripe" }, db: tx, }); expect(result?.id).toBe(inserted.id); expect(typeof result?.limits).toBe("object"); }); });
test("returns null when no workspace maps to the customer", async () => { await withTestTransaction(async (tx) => { const result = await getWorkspaceByStripeId({ input: { stripeId: "cus_does_not_exist" }, db: tx, }); expect(result).toBeNull(); }); });});
async function insertPlanWorkspace( tx: DrizzleTx, opts: { plan: "free" | "starter" | "team" | "scale"; slug: string },) { const row = await tx .insert(workspace) .values({ slug: opts.slug, name: opts.slug, plan: opts.plan, stripeId: `cus_${opts.slug}`, subscriptionId: `sub_${opts.slug}`, limits: JSON.stringify(getLimits(opts.plan)), }) .returning() .get(); return selectWorkspaceSchema.parse(row);}
describe("updateWorkspacePlan", () => { test("writes trialEndsAt only when given", async () => { await withTestTransaction(async (tx) => { const ws = await insertPlanWorkspace(tx, { plan: "free", slug: "svc-plan-trial", }); const ctx = { ...makeSystemCtx(ws, { job: "signup-trial" }), db: tx }; const trialEndsAt = new Date("2027-01-15T00:00:00Z"); const base = { plan: "starter" as const, subscriptionId: "sub_trial", paidUntil: trialEndsAt, endsAt: trialEndsAt, limits: getLimits("starter"), }; const read = () => tx.select().from(workspace).where(eq(workspace.id, ws.id)).get();
await updateWorkspacePlan({ ctx, input: { ...base, trialEndsAt } }); expect((await read())?.trialEndsAt).toEqual(trialEndsAt);
await updateWorkspacePlan({ ctx, input: base }); expect((await read())?.trialEndsAt).toEqual(trialEndsAt);
await updateWorkspacePlan({ ctx, input: { ...base, trialEndsAt: null } }); expect((await read())?.trialEndsAt).toBeNull(); }); });
test("writes the new plan + limits and audits the change", async () => { await withTestTransaction(async (tx) => { const ws = await insertPlanWorkspace(tx, { plan: "team", slug: "svc-plan-downgrade", }); const ctx = { ...makeSystemCtx(ws, { job: "stripe-subscription-updated" }), db: tx, };
await updateWorkspacePlan({ ctx, input: { plan: "starter", subscriptionId: "sub_new", paidUntil: new Date("2027-01-01T00:00:00Z"), endsAt: new Date("2027-01-01T00:00:00Z"), limits: getLimits("starter"), }, });
const after = await tx .select() .from(workspace) .where(eq(workspace.id, ws.id)) .get(); expect(after?.plan).toBe("starter"); expect(after?.subscriptionId).toBe("sub_new"); // Compare parsed content, not the raw string โ the verb persists // `limitsSchema`-canonicalised JSON (key order differs from the // config object returned by `getLimits`). expect(JSON.parse(after?.limits ?? "{}")).toEqual(getLimits("starter"));
await expectAuditRow({ workspaceId: ws.id, action: "workspace.update", entityType: "workspace", entityId: ws.id, actorType: "system", db: tx, });
const [audit] = await readAuditLog({ workspaceId: ws.id, entityType: "workspace", entityId: ws.id, db: tx, }); expect(audit?.changedFields).toContain("plan"); // No `reason` passed โ no metadata stamped. expect(audit?.metadata).toBeNull(); }); });
test("stamps reason / from / to metadata when a reason is given", async () => { await withTestTransaction(async (tx) => { const ws = await insertPlanWorkspace(tx, { plan: "free", slug: "svc-plan-checkout", }); const ctx = { ...makeSystemCtx(ws, { job: "stripe-session-completed" }), db: tx, };
await updateWorkspacePlan({ ctx, input: { plan: "team", subscriptionId: "sub_checkout", paidUntil: new Date("2027-01-01T00:00:00Z"), endsAt: new Date("2027-01-01T00:00:00Z"), limits: getLimits("team"), reason: "checkout_session_completed", }, });
const [audit] = await readAuditLog({ workspaceId: ws.id, entityType: "workspace", entityId: ws.id, db: tx, }); expect(audit?.metadata).toMatchObject({ reason: "checkout_session_completed", from: "free", to: "team", }); }); });
test("rejects a read-only api key actor", async () => { await withTestTransaction(async (tx) => { const ws = await insertPlanWorkspace(tx, { plan: "team", slug: "svc-plan-readonly", }); const ctx = { ...makeApiKeyCtx(ws, { keyId: "k-read", userId: 1, scopes: ["read"] }), db: tx, };
await expect( updateWorkspacePlan({ ctx, input: { plan: "starter", subscriptionId: null, paidUntil: null, endsAt: null, limits: getLimits("starter"), }, }), ).rejects.toBeInstanceOf(ForbiddenError); }); });});