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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681import { db, eq } from "@openstatus/db";import { auditLog, user } from "@openstatus/db/src/schema";import type { AuditEntry } from "@openstatus/db/src/schema";import { expect } from "@std/expect";import { afterAll, beforeAll, describe, test } from "@std/testing/bdd";
import { clearAuditLog, createWorkspaceFixture, makeApiKeyCtx, makeSlackCtx, makeSystemCtx, makeUserCtx, readAuditLog, withTestTransaction,} from "../../../test/helpers";import type { ServiceContext } from "../../context";import { NotFoundError } from "../../errors";import { diffTopLevel, emitAudit } from "../emit";import { getAuditLog } from "../get";
/** * Test-only escape hatch around `emitAudit`'s compile-time snapshot * contract (`.create` requires `after`, `.delete` requires `before`, * `.update` requires both). Several tests here intentionally pass * bare entries to exercise the runtime path โ column persistence, * actor derivation, ordering โ without caring about snapshot shape. * Real call sites must satisfy the full type. */async function emitLoose( tx: Parameters<typeof emitAudit>[0], ctx: ServiceContext, entry: unknown,) { return emitAudit(tx, ctx, entry as AuditEntry);}
let teamCtx: ServiceContext;let teamUserId: number;let otherCtx: ServiceContext;
beforeAll(async () => { const team = await createWorkspaceFixture("team"); teamUserId = team.userId; teamCtx = makeUserCtx(team.workspace, { userId: teamUserId }); // Built here rather than inside a test: `withTestTransaction` holds an open // transaction on the shared connection, and a committed write mid-stream // aborts it. const other = await createWorkspaceFixture("free"); otherCtx = makeUserCtx(other.workspace, { userId: other.userId }); // Wipe rows from prior aborted runs โ the fail-closed test below runs // outside `withTestTransaction` and could leak on a botched prior run. await clearAuditLog(teamCtx.workspace.id);});
afterAll(async () => { await clearAuditLog(teamCtx.workspace.id);});
describe("diffTopLevel", () => { test("empty when objects are equal", () => { const result = diffTopLevel( { name: "a", count: 1 }, { name: "a", count: 1 }, ); expect(result).toEqual([]); });
test("reports top-level keys that differ", () => { const result = diffTopLevel( { name: "a", count: 1 }, { name: "b", count: 1 }, ); expect(result).toEqual(["name"]); });
test("reports additions and removals as changed", () => { const added = diffTopLevel({ name: "a" }, { name: "a", extra: "x" }); expect(added).toEqual(["extra"]); const removed = diffTopLevel({ name: "a", extra: "x" }, { name: "a" }); expect(removed).toEqual(["extra"]); });
test("ignores updatedAt and createdAt", () => { const before = { name: "a", createdAt: new Date("2025-01-01"), updatedAt: new Date("2025-01-01"), }; const after = { name: "a", createdAt: new Date("2025-06-01"), updatedAt: new Date("2025-06-01"), }; expect(diffTopLevel(before, after)).toEqual([]); });
test("reports non-ignored changes alongside ignored ones", () => { const before = { name: "a", updatedAt: new Date("2025-01-01"), }; const after = { name: "b", updatedAt: new Date("2025-06-01"), }; expect(diffTopLevel(before, after)).toEqual(["name"]); });
test("treats null and undefined as absent โ both absent means unchanged", () => { expect(diffTopLevel({ a: null }, { a: undefined })).toEqual([]); expect(diffTopLevel({ a: undefined }, { a: null })).toEqual([]); expect(diffTopLevel({}, { a: null })).toEqual([]); expect(diffTopLevel({ a: null }, {})).toEqual([]); });
test("treats null โ value as changed", () => { expect(diffTopLevel({ a: null }, { a: "x" })).toEqual(["a"]); expect(diffTopLevel({ a: "x" }, { a: null })).toEqual(["a"]); });
test("top-level only โ nested object changes report the parent key", () => { const before = { headers: { Authorization: "old" }, name: "a" }; const after = { headers: { Authorization: "new" }, name: "a" }; expect(diffTopLevel(before, after)).toEqual(["headers"]); });
test("array equality is deep, not reference-based", () => { const before = { tags: [1, 2, 3] }; const after = { tags: [1, 2, 3] }; expect(diffTopLevel(before, after)).toEqual([]); const changed = diffTopLevel({ tags: [1, 2] }, { tags: [1, 2, 3] }); expect(changed).toEqual(["tags"]); });});
describe("emitAudit โ row persistence", () => { test("inserts a row with the expected columns", async () => { await withTestTransaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "monitor.create", entityType: "monitor", entityId: 9001, metadata: { jobType: "http", url: "https://example.com" }, });
const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9001, db: tx, }); expect(rows).toHaveLength(1); const row = rows[0]; expect(row).toBeDefined(); if (!row) throw new Error("unreachable"); expect(row.workspaceId).toBe(teamCtx.workspace.id); expect(row.action).toBe("monitor.create"); expect(row.entityType).toBe("monitor"); expect(row.entityId).toBe("9001"); expect(row.actorType).toBe("user"); expect(row.actorId).toBe(String(teamUserId)); expect(row.actorUserId).toBe(teamUserId); expect(row.metadata).toEqual({ jobType: "http", url: "https://example.com", }); expect(row.before).toBeNull(); expect(row.after).toBeNull(); expect(row.changedFields).toBeNull(); expect(row.createdAt).toBeInstanceOf(Date); }); });
test("stringifies numeric entityIds", async () => { await withTestTransaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "page.delete", entityType: "page", entityId: 42, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "page", entityId: 42, db: tx, }); expect(row?.entityId).toBe("42"); }); });
// This test intentionally runs OUTSIDE `withTestTransaction`. Its whole // point is to verify that a real outer transaction rolls back when an // emit-bearing block throws โ wrapping it in another tx would assert // against the wrapping tx instead of the committed db. test("fail-closed: insert error rolls the enclosing tx back", async () => { let threw = false; try { await db.transaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "monitor.create", entityType: "monitor", entityId: 9002, }); throw new Error("simulated downstream failure"); }); } catch { threw = true; } expect(threw).toBe(true); const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9002, }); expect(rows).toHaveLength(0); });});
describe("emitAudit โ changed_fields derivation", () => { test("null when only after is supplied (create-shape)", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.create", entityType: "monitor", entityId: 9100, after: { name: "fresh" }, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9100, db: tx, }); expect(row?.changedFields).toBeNull(); }); });
test("null when only before is supplied (delete-shape)", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.delete", entityType: "monitor", entityId: 9101, before: { name: "going" }, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9101, db: tx, }); expect(row?.changedFields).toBeNull(); }); });
test("no-op update (before === after) skips the insert entirely", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9102, before: { name: "same", url: "https://x" }, after: { name: "same", url: "https://x" }, }); const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9102, db: tx, }); expect(rows).toHaveLength(0); }); });
test("ignoring updatedAt/createdAt only โ still a no-op, still skipped", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9105, before: { name: "a", updatedAt: new Date("2025-01-01") }, after: { name: "a", updatedAt: new Date("2025-06-01") }, }); const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9105, db: tx, }); expect(rows).toHaveLength(0); }); });
test("metadata-only update with empty row diff still lands", async () => { // Off-row mutations (e.g. component-scope edits on `page_subscriber`) // change a join table, not the row itself โ `before === after` for // every column. The metadata is the signal, so the row must land. await withTestTransaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "page_subscriber.update", entityType: "page_subscriber", entityId: 9106, before: { name: "same" }, after: { name: "same" }, metadata: { componentIds: [1, 2] }, }); const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "page_subscriber", entityId: 9106, db: tx, }); expect(rows).toHaveLength(1); expect(rows[0]?.changedFields).toEqual([]); expect(rows[0]?.metadata).toEqual({ componentIds: [1, 2] }); }); });
test("populated with the changed top-level keys", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9103, before: { name: "old", url: "https://a", method: "GET" }, after: { name: "new", url: "https://b", method: "GET" }, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9103, db: tx, }); expect(row?.changedFields?.sort()).toEqual(["name", "url"]); }); });
test("ignores updatedAt when computing the diff โ a real change still records only the real key", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9104, before: { name: "old", updatedAt: new Date("2025-01-01") }, after: { name: "new", updatedAt: new Date("2025-06-01") }, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9104, db: tx, }); expect(row?.changedFields).toEqual(["name"]); }); });});
describe("emitAudit โ actor derivation", () => { test("user actor โ actorId and actorUserId are the same id", async () => { await withTestTransaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "workspace.update", entityType: "workspace", entityId: teamCtx.workspace.id, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "workspace", db: tx, }); expect(row?.actorType).toBe("user"); expect(row?.actorId).toBe(String(teamUserId)); expect(row?.actorUserId).toBe(teamUserId); }); });
test("apiKey actor without linked user โ actorUserId null", async () => { await withTestTransaction(async (tx) => { const ctx = makeApiKeyCtx(teamCtx.workspace, { keyId: "k_anon" }); await emitLoose(tx, ctx, { action: "api_key.delete", entityType: "api_key", entityId: 9200, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "api_key", entityId: 9200, db: tx, }); expect(row?.actorType).toBe("apiKey"); expect(row?.actorId).toBe("k_anon"); expect(row?.actorUserId).toBeNull(); }); });
test("apiKey actor with linked user โ actorUserId populated", async () => { await withTestTransaction(async (tx) => { const ctx = makeApiKeyCtx(teamCtx.workspace, { keyId: "k_linked", userId: 42, }); await emitLoose(tx, ctx, { action: "api_key.delete", entityType: "api_key", entityId: 9201, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "api_key", entityId: 9201, db: tx, }); expect(row?.actorType).toBe("apiKey"); expect(row?.actorId).toBe("k_linked"); expect(row?.actorUserId).toBe(42); }); });
test("slack actor โ actorId is slackUserId", async () => { await withTestTransaction(async (tx) => { const ctx = makeSlackCtx(teamCtx.workspace, { teamId: "T1", slackUserId: "U9", userId: 7, }); await emitLoose(tx, ctx, { action: "maintenance.update", entityType: "maintenance", entityId: 9300, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "maintenance", entityId: 9300, db: tx, }); expect(row?.actorType).toBe("slack"); expect(row?.actorId).toBe("U9"); expect(row?.actorUserId).toBe(7); }); });
test("system actor โ actorId is the job name, actorUserId null", async () => { await withTestTransaction(async (tx) => { const ctx = makeSystemCtx(teamCtx.workspace, { job: "cleanup-job" }); await emitLoose(tx, ctx, { action: "monitor.delete", entityType: "monitor", entityId: 9400, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9400, db: tx, }); expect(row?.actorType).toBe("system"); expect(row?.actorId).toBe("cleanup-job"); expect(row?.actorUserId).toBeNull(); }); });});
describe("emitAudit โ ordering", () => { test("autoincrement id provides a stable tiebreaker within the same ms", async () => { await withTestTransaction(async (tx) => { for (let i = 0; i < 5; i++) { await emitLoose(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9500 + i, }); } const rows = await tx .select() .from(auditLog) .where(eq(auditLog.workspaceId, teamCtx.workspace.id)) .orderBy(auditLog.id) .all(); const emitted = rows.filter( (r) => r.action === "monitor.update" && Number(r.entityId) >= 9500 && Number(r.entityId) < 9505, ); expect(emitted).toHaveLength(5); const ids = emitted.map((r) => Number(r.entityId)); expect(ids).toEqual([9500, 9501, 9502, 9503, 9504]); // Ids are monotonically increasing โ reads ordered by (createdAt DESC, id DESC) // get a deterministic order even when createdAt collides. for (let i = 1; i < emitted.length; i++) { const prev = emitted[i - 1]; const curr = emitted[i]; if (!prev || !curr) throw new Error("unreachable"); expect(curr.id).toBeGreaterThan(prev.id); } }); });});
describe("emitAudit โ entry-shape regressions", () => { test("entry without metadata serializes null to the metadata column", async () => { await withTestTransaction(async (tx) => { await emitLoose(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9600, }); const [row] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9600, db: tx, }); expect(row?.metadata).toBeNull(); }); });});
describe("emitAudit โ runtime Zod validation", () => { test("rejects an unknown action string", async () => { await withTestTransaction(async (tx) => { let threw = false; try { await emitAudit(tx, teamCtx, { // Cast through unknown โ the runtime check is exactly the // point being tested; at compile time this would be a TS error. action: "monitor.bogus", entityType: "monitor", entityId: 9700, } as unknown as AuditEntry); } catch { threw = true; } expect(threw).toBe(true); const rows = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9700, db: tx, }); expect(rows).toHaveLength(0); }); });
test("rejects a mismatched entityType for a known action", async () => { await withTestTransaction(async (tx) => { let threw = false; try { await emitAudit(tx, teamCtx, { action: "monitor.create", // `monitor.create` is bound to entityType: "monitor"; this should // fail the discriminator. entityType: "page", entityId: 9701, } as unknown as AuditEntry); } catch { threw = true; } expect(threw).toBe(true); }); });
test("rejects a non-integer entityId", async () => { await withTestTransaction(async (tx) => { let threw = false; try { await emitAudit(tx, teamCtx, { action: "monitor.delete", entityType: "monitor", // The schema requires `intId`; floats should fail. entityId: 1.5, } as unknown as AuditEntry); } catch { threw = true; } expect(threw).toBe(true); }); });});
describe("getAuditLog", () => { test("returns the row when scoped to the same workspace", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.update", entityType: "monitor", entityId: 9800, before: { name: "old" }, after: { name: "new" }, }); const [inserted] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9800, db: tx, }); if (!inserted) throw new Error("unreachable");
const row = await getAuditLog({ ctx: { ...teamCtx, db: tx }, input: { id: inserted.id }, }); expect(row.id).toBe(inserted.id); expect(row.action).toBe("monitor.update"); expect(row.entityType).toBe("monitor"); expect(row.entityId).toBe("9800"); expect(row.changedFields).toEqual(["name"]); expect(row.user?.id).toBe(teamUserId); }); });
test("throws NotFoundError when the id belongs to another workspace", async () => { await withTestTransaction(async (tx) => { await emitAudit(tx, teamCtx, { action: "monitor.create", entityType: "monitor", entityId: 9801, after: { name: "in-team" }, }); const [inserted] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9801, db: tx, }); if (!inserted) throw new Error("unreachable");
let caught: unknown; try { await getAuditLog({ ctx: { ...otherCtx, db: tx }, input: { id: inserted.id }, }); } catch (e) { caught = e; } expect(caught).toBeInstanceOf(NotFoundError); }); });
test("soft-deleted actor resolves to user: null", async () => { await withTestTransaction(async (tx) => { const ghostUserId = 9_999_001; await tx .insert(user) .values({ id: ghostUserId, tenantId: `ghost-${ghostUserId}`, email: `ghost-${ghostUserId}@example.test`, deletedAt: new Date("2025-01-01"), }) .run();
const ghostCtx = makeUserCtx(teamCtx.workspace, { userId: ghostUserId }); await emitAudit(tx, ghostCtx, { action: "monitor.create", entityType: "monitor", entityId: 9802, after: { name: "by-ghost" }, }); const [inserted] = await readAuditLog({ workspaceId: teamCtx.workspace.id, entityType: "monitor", entityId: 9802, db: tx, }); if (!inserted) throw new Error("unreachable"); expect(inserted.actorUserId).toBe(ghostUserId);
const row = await getAuditLog({ ctx: { ...teamCtx, db: tx }, input: { id: inserted.id }, }); expect(row.user).toBeNull(); }); });});