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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784import crypto from "node:crypto";
import { and, db, eq } from "@openstatus/db";import { auditLog, incident, incidentEvent, selectWorkspaceSchema, workspace as workspaceTable,} from "@openstatus/db/src/schema";import { addUserToWorkspace, createPage, createTestWorkspace, createUser,} from "@openstatus/db/src/test/factories";import { declareIncident } from "@openstatus/services/incident";import { beforeEach, describe, expect, test } from "@openstatus/test-utils";import { Hono } from "hono";
import { settleBackgroundTasks } from "@/libs/background";// workspace-resolver / @slack/web-api are swapped for doubles via the test// import map; behavior is driven through this shared mutable state.import { slackTestState } from "@/libs/test/doubles/slack-test-state";import { TEST_SIGNING_SECRET as SIGNING_SECRET, withSlackConfig,} from "@/libs/test/slack-config";
import type { SlackEnv } from "./config";import { handleSlackInteraction } from "./interactions";import { verifySlackSignature } from "./verify";
const redisStore = (globalThis as Record<string, unknown>) .__testRedisStore as Map<string, string>;
const basePending = { id: "pending-123", workspaceId: 1, teamId: "T_KNOWN", channelId: "C1", threadTs: "1.1", messageTs: "1.2", userId: "U_OWNER", createdAt: Date.now(),};
function configureSlackDoubles() { slackTestState.calls = []; // The seeded member of workspace 1, so the clicking user is linked. slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: { email: "ping@openstatus.dev" } }, }); slackTestState.resolveWorkspace = (teamId: string) => teamId === "T_KNOWN" ? Promise.resolve({ botToken: "xoxb-fallback", workspace: { id: 1, limits: { "slack-agent": true } }, }) : Promise.resolve(null);}
function createTestApp() { const app = withSlackConfig(new Hono<SlackEnv>()); app.post("/slack/interactions", verifySlackSignature, handleSlackInteraction); return app;}
function seedCreateStatusReport(id = "pending-123") { const data = { ...basePending, id, payload: { toolName: "create_status_report", input: { title: "Test Incident", status: "investigating", message: "Investigating the issue", pageId: 1, pageComponentIds: [], }, }, }; redisStore.set(`slack:action:${id}`, JSON.stringify(data)); redisStore.set(`slack:thread:${data.threadTs}`, id); return data;}
function seedCreateMaintenance( id = "maint-001", overrides: Record<string, unknown> = {},) { const now = Date.now(); const data = { ...basePending, id, threadTs: "2.1", messageTs: "2.2", payload: { toolName: "create_maintenance", input: { title: "DB Maintenance", message: "Scheduled database upgrade.", from: new Date(now + 86400000).toISOString(), to: new Date(now + 86400000 + 3600000).toISOString(), pageId: 1, pageComponentIds: [], ...overrides, }, }, }; redisStore.set(`slack:action:${id}`, JSON.stringify(data)); redisStore.set(`slack:thread:${data.threadTs}`, id); return data;}
// The route acks Slack immediately and finishes the work in the background,// so every test waits for that work before asserting on its side effects.async function signAndPost( app: ReturnType<typeof createTestApp>, payload: Record<string, unknown>,) { const payloadStr = JSON.stringify(payload); const body = `payload=${encodeURIComponent(payloadStr)}`; const timestamp = Math.floor(Date.now() / 1000); const basestring = `v0:${timestamp}:${body}`; const sig = crypto .createHmac("sha256", SIGNING_SECRET) .update(basestring) .digest("hex");
const res = await app.request("/slack/interactions", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "x-slack-request-timestamp": String(timestamp), "x-slack-signature": `v0=${sig}`, }, body, }); await settleBackgroundTasks(); return res;}
describe("handleSlackInteraction (dispatch)", () => { const app = createTestApp();
beforeEach(() => { configureSlackDoubles(); redisStore.clear(); });
test("returns ok for non-block_actions", async () => { const res = await signAndPost(app, { type: "message_action", actions: [] }); expect(res.status).toBe(200); expect(slackTestState.calls).toHaveLength(0); });
test("returns ok for unknown action_id prefix", async () => { const res = await signAndPost(app, { type: "block_actions", user: { id: "U1" }, channel: { id: "C1" }, message: { ts: "1.1" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "unknown_action" }], }); expect(res.status).toBe(200); expect(slackTestState.calls).toHaveLength(0); });
test("cancel updates message to cancelled", async () => { seedCreateStatusReport(); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "cancel_pending-123" }], }); expect(res.status).toBe(200); const cancelCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).includes("Cancelled"), ); expect(cancelCall).toBeDefined(); });
test("rejects action from wrong user", async () => { seedCreateStatusReport(); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OTHER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_pending-123" }], }); expect(res.status).toBe(200); const ephemeral = slackTestState.calls.find( (c) => c.method === "postEphemeral", ); expect(ephemeral).toBeDefined(); expect(ephemeral?.args.text as string).toContain("Only the person"); expect(redisStore.has("slack:action:pending-123")).toBe(true); });
test("shows expired message when pending not found", async () => { const res = await signAndPost(app, { type: "block_actions", user: { id: "U1" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_unknown-id" }], }); expect(res.status).toBe(200); const expiredCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).includes("expired"), ); expect(expiredCall).toBeDefined(); });
test("returns ok when no bot token available", async () => { const res = await signAndPost(app, { type: "block_actions", user: { id: "U1" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_UNKNOWN" }, actions: [{ action_id: "approve_some-id" }], }); expect(res.status).toBe(200); expect(slackTestState.calls).toHaveLength(0); });
test("approve_flag parses flag=true; approve parses flag=false", async () => { seedCreateStatusReport(); // Both prefixes should be accepted; we just verify the dispatcher // reaches consume() and isn't tripped by the prefix parser. const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_flag_pending-123" }], }); expect(res.status).toBe(200); });
test("resolves the bot token on click, not from the stored action", async () => { seedCreateStatusReport(); let resolveCalls = 0; slackTestState.resolveWorkspace = (teamId: string) => { resolveCalls++; return teamId === "T_KNOWN" ? Promise.resolve({ botToken: "xoxb-fresh", workspace: { id: 1, limits: { "slack-agent": true } }, }) : Promise.resolve(null); };
await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "cancel_pending-123" }], });
// A card outlives the turn that made it, so the token that made it may // have been revoked by now โ it is never persisted, only resolved here. expect(resolveCalls).toBe(1); expect(slackTestState.calls.some((c) => c.method === "update")).toBe(true); });
test("refuses an action drafted against another workspace", async () => { seedCreateStatusReport(); // A reinstall can point the team at a different workspace than the one the // card was drafted for; executing it there would hit the wrong status page. slackTestState.resolveWorkspace = () => Promise.resolve({ botToken: "xoxb-other", workspace: { id: 2, limits: { "slack-agent": true } }, });
await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_pending-123" }], });
const update = slackTestState.calls.find((c) => c.method === "update"); expect(update?.args.text).toContain("different workspace"); // Not consumed: the action is still there for the right workspace. expect(redisStore.has("slack:action:pending-123")).toBe(true); });
test("does nothing when the workspace no longer resolves", async () => { seedCreateStatusReport(); slackTestState.resolveWorkspace = () => Promise.resolve(null);
await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_pending-123" }], });
expect(slackTestState.calls).toHaveLength(0); // The action survives an uninstall rather than being silently burned. expect(redisStore.has("slack:action:pending-123")).toBe(true); });
test("cancel consumes pending from redis", async () => { seedCreateStatusReport(); await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "1.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "cancel_pending-123" }], }); expect(redisStore.has("slack:action:pending-123")).toBe(false); });});
describe("handleSlackInteraction (members only)", () => { const app = createTestApp();
beforeEach(() => { configureSlackDoubles(); redisStore.clear(); slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: {} } }); });
function seedUnlinked(id: string, slackUserId: string) { const data = { ...seedCreateMaintenance(id), userId: slackUserId }; redisStore.set(`slack:action:${id}`, JSON.stringify(data)); }
test("an unlinked approver gets the link card and the draft stays live", async () => { const slackUserId = `U_UNLINKED_${crypto.randomUUID()}`; seedUnlinked("maint-unlinked", slackUserId); const res = await signAndPost(app, { type: "block_actions", user: { id: slackUserId }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_maint-unlinked" }], }); expect(res.status).toBe(200); expect(redisStore.has("slack:action:maint-unlinked")).toBe(true); const ephemeral = slackTestState.calls.find( (c) => c.method === "postEphemeral", ); expect(ephemeral?.args.text).toContain("Link your openstatus account"); expect(slackTestState.calls.some((c) => c.method === "update")).toBe(false); });
test("an unlinked initiator can still cancel their own draft", async () => { const slackUserId = `U_UNLINKED_${crypto.randomUUID()}`; seedUnlinked("maint-unlinked-cancel", slackUserId); const res = await signAndPost(app, { type: "block_actions", user: { id: slackUserId }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "cancel_maint-unlinked-cancel" }], }); expect(res.status).toBe(200); expect(redisStore.has("slack:action:maint-unlinked-cancel")).toBe(false); const cancelled = slackTestState.calls.find( (c) => c.method === "update" && c.args.text === ":no_entry_sign: Cancelled.", ); expect(cancelled).toBeDefined(); });});
describe("registry-runner execution paths", () => { const app = createTestApp();
beforeEach(() => { configureSlackDoubles(); redisStore.clear(); });
test("approve create_maintenance shows scheduled success", async () => { seedCreateMaintenance(); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_maint-001" }], }); expect(res.status).toBe(200); const successCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).includes( "Maintenance *DB Maintenance* scheduled", ), ); expect(successCall).toBeDefined(); expect(successCall?.args.text as string).not.toContain( "subscribers notified", ); });
test("approve_flag create_maintenance notifies", async () => { seedCreateMaintenance(); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_flag_maint-001" }], }); expect(res.status).toBe(200); const successCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).includes("subscribers notified"), ); expect(successCall).toBeDefined(); });
test("cancel does not execute the tool", async () => { seedCreateMaintenance(); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "cancel_maint-001" }], }); expect(res.status).toBe(200); const cancelCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).includes("Cancelled"), ); expect(cancelCall).toBeDefined(); });
test("ServiceError (stale page id) surfaces typed message", async () => { seedCreateMaintenance("maint-bad", { pageId: 99999 }); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_maint-bad" }], }); expect(res.status).toBe(200); const errCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).startsWith(":x:") && (c.args.text as string).toLowerCase().includes("not found"), ); expect(errCall).toBeDefined(); });
test("a confirmed action is attributed to the member with the Slack email", async () => { const { workspace } = await createTestWorkspace(); const page = await createPage(workspace.id); const member = await createUser(); await addUserToWorkspace(member.id, workspace.id, "member"); slackTestState.resolveWorkspace = () => Promise.resolve({ botToken: "xoxb-fallback", workspace: { id: workspace.id, limits: { "slack-agent": true } }, }); slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: { email: (member.email as string).toUpperCase() } }, });
const now = Date.now(); const data = { ...basePending, id: "maint-attr", workspaceId: workspace.id, threadTs: "3.1", messageTs: "3.2", payload: { toolName: "create_maintenance", input: { title: "DB Maintenance", message: "Scheduled database upgrade.", from: new Date(now + 86400000).toISOString(), to: new Date(now + 86400000 + 3600000).toISOString(), pageId: page.id, pageComponentIds: [], }, }, }; redisStore.set("slack:action:maint-attr", JSON.stringify(data)); redisStore.set("slack:thread:3.1", "maint-attr");
try { const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "3.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_maint-attr" }], }); expect(res.status).toBe(200);
// The verb ran with the matched member, so its audit row carries it. const created = await db .select() .from(auditLog) .where( and( eq(auditLog.workspaceId, workspace.id), eq(auditLog.action, "maintenance.create"), ), ) .get(); expect(created?.actorType).toBe("slack"); expect(created?.actorUserId).toBe(member.id); } finally { await db.delete(auditLog).where(eq(auditLog.workspaceId, workspace.id)); } });
test("from after to surfaces typed validation error", async () => { const now = Date.now(); seedCreateMaintenance("maint-bad-time", { from: new Date(now + 7200000).toISOString(), to: new Date(now + 3600000).toISOString(), }); const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: "C1" }, message: { ts: "2.2" }, team: { id: "T_KNOWN" }, actions: [{ action_id: "approve_maint-bad-time" }], }); expect(res.status).toBe(200); const errCall = slackTestState.calls.find( (c) => c.method === "update" && (c.args.text as string).startsWith(":x:"), ); expect(errCall).toBeDefined(); });});
describe("link this channel button", () => { const app = createTestApp();
beforeEach(() => { configureSlackDoubles(); redisStore.clear(); });
test("binds the channel to the incident as the clicking member", async () => { const workspace = selectWorkspaceSchema.parse( await db.query.workspace.findFirst({ where: eq(workspaceTable.id, 1) }), ); const created = await declareIncident({ ctx: { workspace, actor: { type: "system", job: "test" } }, input: { title: "Bind me", severity: "minor" }, }); const channelId = `C_BIND_${crypto.randomUUID()}`; try { const res = await signAndPost(app, { type: "block_actions", user: { id: "U_OWNER" }, channel: { id: channelId }, message: { ts: "9.9" }, team: { id: "T_KNOWN" }, actions: [ { action_id: `incident_bind_${created.id}`, value: channelId }, ], }); expect(res.status).toBe(200); const row = await db .select() .from(incident) .where(eq(incident.id, created.id)) .get(); expect(row?.slackChannelId).toBe(channelId); expect( slackTestState.calls.some( (c) => c.method === "update" && String(c.args.text).includes("linked"), ), ).toBe(true); } finally { await db .delete(auditLog) .where( and( eq(auditLog.entityType, "incident"), eq(auditLog.entityId, String(created.id)), ), ); await db .delete(incidentEvent) .where(eq(incidentEvent.incidentId, created.id)); await db.delete(incident).where(eq(incident.id, created.id)); } });});
describe("declare incident modal", () => { const app = createTestApp();
beforeEach(() => { configureSlackDoubles(); redisStore.clear(); });
function submission(values: Record<string, unknown>, metadata = "{}") { return { type: "view_submission", team: { id: "T_KNOWN" }, user: { id: "U_OWNER" }, view: { callback_id: "declare_incident", private_metadata: metadata, state: { values }, }, }; }
test("the global shortcut opens the form", async () => { const res = await signAndPost(app, { type: "shortcut", callback_id: "declare_incident", trigger_id: "trig-1", team: { id: "T_KNOWN" }, user: { id: "U_OWNER" }, }); expect(res.status).toBe(200); const open = slackTestState.calls.find((c) => c.method === "views.open"); expect(open).toBeDefined(); expect(open?.args.trigger_id).toBe("trig-1"); const view = open?.args.view as Record<string, unknown>; expect(view.callback_id).toBe("declare_incident"); expect(JSON.stringify(view.blocks)).toContain('"initial_user":"U_OWNER"'); });
test("the message shortcut prefills the summary", async () => { await signAndPost(app, { type: "message_action", callback_id: "declare_incident_from_message", trigger_id: "trig-2", team: { id: "T_KNOWN" }, user: { id: "U_OWNER" }, channel: { id: "C_ORIGIN" }, message: { text: "checkout is throwing 500s" }, }); const open = slackTestState.calls.find((c) => c.method === "views.open"); expect(open).toBeDefined(); const view = open?.args.view as Record<string, unknown>; expect(view.private_metadata).toBe( JSON.stringify({ channelId: "C_ORIGIN" }), ); expect(JSON.stringify(view.blocks)).toContain("checkout is throwing 500s"); });
test("an unlinked user gets the link card instead of the form", async () => { slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: {} } }); await signAndPost(app, { type: "shortcut", callback_id: "declare_incident", trigger_id: "trig-3", team: { id: "T_KNOWN" }, user: { id: "U_STRANGER" }, }); const open = slackTestState.calls.find((c) => c.method === "views.open"); expect(open).toBeDefined(); const view = open?.args.view as Record<string, unknown>; expect(view.callback_id).toBeUndefined(); expect(JSON.stringify(view.blocks)).toContain("Link account"); });
test("the home tab button opens the form", async () => { await signAndPost(app, { type: "block_actions", trigger_id: "trig-home", team: { id: "T_KNOWN" }, user: { id: "U_OWNER" }, actions: [{ action_id: "open_declare_incident" }], }); const open = slackTestState.calls.find((c) => c.method === "views.open"); expect(open).toBeDefined(); expect(open?.args.trigger_id).toBe("trig-home"); const view = open?.args.view as { callback_id?: string }; expect(view.callback_id).toBe("declare_incident"); });
test("a blank title is rejected on the form", async () => { const res = await signAndPost( app, submission({ title: { value: { value: " " } }, severity: { value: { selected_option: { value: "major" } } }, }), ); expect(await res.json()).toEqual({ response_action: "errors", errors: { title: "Give the incident a title." }, }); });
test("submitting declares the incident and closes the form", async () => { const title = `Modal ${crypto.randomUUID()}`; const res = await signAndPost( app, submission( { title: { value: { value: title } }, severity: { value: { selected_option: { value: "critical" } } }, summary: { value: { value: "Checkout is down" } }, commander: { value: { selected_user: "U_OWNER" } }, }, JSON.stringify({ channelId: "C_ORIGIN" }), ), ); expect(res.status).toBe(200); expect(await res.text()).toBe(""); const row = await db .select() .from(incident) .where(eq(incident.title, title)) .get(); try { expect(row?.severity).toBe("critical"); expect(row?.summary).toBe("Checkout is down"); expect(row?.workspaceId).toBe(1); const ephemeral = slackTestState.calls.find( (c) => c.method === "postEphemeral", ); expect(ephemeral?.args.channel).toBe("C_ORIGIN"); expect(String(ephemeral?.args.text)).toContain(title); } finally { if (row) { await db .delete(auditLog) .where( and( eq(auditLog.entityType, "incident"), eq(auditLog.entityId, String(row.id)), ), ); await db .delete(incidentEvent) .where(eq(incidentEvent.incidentId, row.id)); await db.delete(incident).where(eq(incident.id, row.id)); } } });});