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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255import crypto from "node:crypto";
import { and, db, eq, or, sql } from "@openstatus/db";import { auditLog, incident, incidentEvent, selectWorkspaceSchema, type Workspace, workspace as workspaceTable,} from "@openstatus/db/src/schema";import { declareIncident } from "@openstatus/services/incident";import { beforeEach, describe, expect, test } from "@openstatus/test-utils";import { Hono } from "hono";
import { slackTestState } from "@/libs/test/doubles/slack-test-state";import { TEST_SIGNING_SECRET as SIGNING_SECRET, withSlackConfig,} from "@/libs/test/slack-config";
import { handleSlackCommand } from "./commands";import type { SlackEnv } from "./config";import { verifySlackSignature } from "./verify";
function createTestApp() { const app = withSlackConfig(new Hono<SlackEnv>()); app.post("/slack/commands", verifySlackSignature, handleSlackCommand); return app;}
function post( app: ReturnType<typeof createTestApp>, text: string, user: string, channelId = "C1", extra: Record<string, string> = {},) { const body = new URLSearchParams({ text, team_id: "T_KNOWN", user_id: user, channel_id: channelId, ...extra, }).toString(); const timestamp = Math.floor(Date.now() / 1000); const sig = crypto .createHmac("sha256", SIGNING_SECRET) .update(`v0:${timestamp}:${body}`) .digest("hex"); return app.request("/slack/commands", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "x-slack-request-timestamp": String(timestamp), "x-slack-signature": `v0=${sig}`, }, body, });}
describe("handleSlackCommand (members only)", () => { const app = createTestApp();
beforeEach(() => { slackTestState.calls = []; slackTestState.resolveWorkspace = (teamId: string) => teamId === "T_KNOWN" ? Promise.resolve({ workspace: { id: 1, limits: { "slack-agent": true } }, botToken: "xoxb-test", botUserId: "UBOT", }) : Promise.resolve(null); });
test("`incident declare` without a title opens the form", async () => { slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: { email: "ping@openstatus.dev" } }, }); const res = await post(app, "incident declare", "U_OWNER", "C_HERE", { trigger_id: "trig-cmd", }); expect(res.status).toBe(200); const open = slackTestState.calls.find((c) => c.method === "views.open"); expect(open?.args.trigger_id).toBe("trig-cmd"); const view = open?.args.view as Record<string, unknown>; expect(view.callback_id).toBe("declare_incident"); expect(view.private_metadata).toBe(JSON.stringify({ channelId: "C_HERE" })); });
test("`incident declare` in an unconnected workspace says so", async () => { const res = await post(app, "incident declare", "U_OWNER", "C_HERE", { trigger_id: "trig-cmd", team_id: "T_UNKNOWN", }); const json = (await res.json()) as { text: string }; expect(json.text).toContain("isn't connected"); expect(slackTestState.calls.some((c) => c.method === "views.open")).toBe( false, ); });
test("help needs no link", async () => { const res = await post(app, "help", `U_${crypto.randomUUID()}`); const json = (await res.json()) as { text: string }; expect(json.text).toContain("/openstatus subscribe"); });
test("no argument shows help", async () => { const res = await post(app, "", `U_${crypto.randomUUID()}`); const json = (await res.json()) as { text: string }; expect(json.text).toContain("/openstatus subscribe"); });
test("an unlinked user gets the link card instead of running the command", async () => { slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: {} } }); const res = await post(app, "subscriptions", `U_${crypto.randomUUID()}`); const json = (await res.json()) as { text: string; blocks?: { type: string }[]; }; expect(json.text).toContain("Link your openstatus account"); expect(json.blocks?.some((b) => b.type === "actions")).toBe(true); });
test("a linked member runs the command", async () => { slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: { email: "ping@openstatus.dev" } }, }); const res = await post(app, "subscriptions", `U_${crypto.randomUUID()}`); const json = (await res.json()) as { text: string; blocks?: unknown }; // The `subscriptions` reply itself, not the link card or the error text. expect(json.text).toContain("subscribed to"); expect(json.blocks).toBeUndefined(); });});
describe("/openstatus incident", () => { const app = createTestApp(); const redisStore = (globalThis as Record<string, unknown>) .__testRedisStore as Map<string, string>; let workspace: Workspace;
beforeEach(async () => { slackTestState.calls = []; slackTestState.usersInfoImpl = () => Promise.resolve({ ok: true, user: { profile: { email: "ping@openstatus.dev" } }, }); workspace = selectWorkspaceSchema.parse( await db.query.workspace.findFirst({ where: eq(workspaceTable.id, 1) }), ); slackTestState.resolveWorkspace = (teamId: string) => teamId === "T_KNOWN" ? Promise.resolve({ workspace: { ...workspace, limits: { ...workspace.limits, "slack-agent": true }, }, botToken: "xoxb-test", botUserId: "UBOT", }) : Promise.resolve(null); });
test("declare posts an approval card instead of declaring", async () => { redisStore.clear(); const res = await post( app, "incident declare Checkout down --sev critical", `U_${crypto.randomUUID()}`, ); const json = (await res.json()) as { text: string }; expect(json.text).toContain("approval card"); const card = slackTestState.calls.find( (c) => c.method === "update" && Array.isArray(c.args.blocks), ); expect(card).toBeDefined(); const pending = [...redisStore.entries()].find(([key]) => key.startsWith("slack:action:"), ); expect(pending?.[1]).toContain("declare_incident"); expect(pending?.[1]).toContain("critical"); });
test("note in a bound channel lands on the timeline", async () => { const channelId = `C_INC_${crypto.randomUUID()}`; const created = await declareIncident({ ctx: { workspace, actor: { type: "system", job: "test" } }, input: { title: "Bound", severity: "minor" }, }); await db .update(incident) .set({ slackTeamId: "T_KNOWN", slackChannelId: channelId }) .where(eq(incident.id, created.id)); try { const res = await post( app, "incident note rolled back the deploy", `U_${crypto.randomUUID()}`, channelId, ); const json = (await res.json()) as { text: string }; expect(json.text).toContain("Added to the timeline"); const events = await db .select() .from(incidentEvent) .where(eq(incidentEvent.incidentId, created.id)) .all(); expect(events.some((e) => e.message === "rolled back the deploy")).toBe( true, ); } finally { await db .delete(auditLog) .where( and( eq(auditLog.workspaceId, workspace.id), or( and( eq(auditLog.entityType, "incident"), eq(auditLog.entityId, String(created.id)), ), and( eq(auditLog.action, "incident_event.create"), sql`json_extract(${auditLog.metadata}, '$.incidentId') = ${created.id}`, ), ), ), ); await db .delete(incidentEvent) .where(eq(incidentEvent.incidentId, created.id)); await db.delete(incident).where(eq(incident.id, created.id)); } });
test("note outside an incident channel explains where to run it", async () => { const res = await post( app, "incident note hello", `U_${crypto.randomUUID()}`, ); const json = (await res.json()) as { text: string }; expect(json.text).toContain("incident's channel"); });});