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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611import crypto from "node:crypto";
import { beforeEach, describe, expect, test } from "@openstatus/test-utils";import { Hono } from "hono";
// workspace-resolver / @slack/web-api / agent 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 { handleSlackEvent, looksLikeUncardedDraft } from "./handler";import { verifySlackSignature } from "./verify";
function createTestApp() { const app = withSlackConfig(new Hono<SlackEnv>()); app.post("/slack/events", verifySlackSignature, handleSlackEvent); return app;}
function signAndPost( app: ReturnType<typeof createTestApp>, body: Record<string, unknown>,) { const rawBody = JSON.stringify(body); const timestamp = Math.floor(Date.now() / 1000); const basestring = `v0:${timestamp}:${rawBody}`; const sig = crypto .createHmac("sha256", SIGNING_SECRET) .update(basestring) .digest("hex");
return app.request("/slack/events", { method: "POST", headers: { "Content-Type": "application/json", "x-slack-request-timestamp": String(timestamp), "x-slack-signature": `v0=${sig}`, }, body: rawBody, });}
describe("handleSlackEvent", () => { const app = createTestApp();
beforeEach(() => { slackTestState.calls = []; slackTestState.postMessageOverride = null; slackTestState.updateOverride = null; slackTestState.runAgentOverride = null; slackTestState.resolveWorkspace = (teamId: string) => { if (teamId === "T_KNOWN") { return Promise.resolve({ workspace: { id: 1, name: "Test Workspace", slug: "test", plan: "free", limits: {}, }, botToken: "xoxb-test", botUserId: "UBOT", }); } return Promise.resolve(null); }; });
test("responds to url_verification challenge", async () => { const res = await signAndPost(app, { type: "url_verification", challenge: "test-challenge-123", });
expect(res.status).toBe(200); const json = (await res.json()) as { challenge: string }; expect(json.challenge).toBe("test-challenge-123"); });
test("returns ok for non-event_callback types", async () => { const res = await signAndPost(app, { type: "app_rate_limited", });
expect(res.status).toBe(200); const json = (await res.json()) as { ok: boolean }; expect(json.ok).toBe(true); });
test("returns ok for event_callback", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_${Date.now()}_1`, event: { type: "app_mention", text: "<@UBOT> create an incident", user: "U1", channel: "C1", ts: "100.1", }, });
expect(res.status).toBe(200); const json = (await res.json()) as { ok: boolean }; expect(json.ok).toBe(true); });
test("publishes the home view on app_home_opened", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_home_${Date.now()}`, event: { type: "app_home_opened", tab: "home", user: "U1", }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50));
const publish = slackTestState.calls.find( (m) => m.method === "views.publish", ); expect(publish).toBeDefined(); expect((publish?.args.view as { type: string }).type).toBe("home"); expect(publish?.args.user_id).toBe("U1"); });
test("ignores app_home_opened for the messages tab", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_home_msgs_${Date.now()}`, event: { type: "app_home_opened", tab: "messages", user: "U1", }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("responds once when a mention arrives as app_mention and message", async () => { const ts = `${Date.now()}.55`; const base = { type: "event_callback", team_id: "T_KNOWN", event: { text: "<@UBOT> create an incident", user: "U1", channel: "C1", channel_type: "channel", ts, }, };
// Same underlying message, delivered as two distinct events. await signAndPost(app, { ...base, event_id: `evt_mention_${ts}`, event: { ...base.event, type: "app_mention" }, }); await signAndPost(app, { ...base, event_id: `evt_message_${ts}`, event: { ...base.event, type: "message" }, }); await new Promise((r) => setTimeout(r, 100));
const thinking = slackTestState.calls.filter( (m) => m.method === "postMessage", ); expect(thinking.length).toBe(1); });
test("handles app_uninstalled event", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_uninstall_${Date.now()}`, event: { type: "app_uninstalled", }, });
expect(res.status).toBe(200); });
test("handles tokens_revoked event", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_revoked_${Date.now()}`, event: { type: "tokens_revoked", }, });
expect(res.status).toBe(200); });
test("deduplicates events with same event_id", async () => { const eventId = `evt_dedup_${Date.now()}`; const body = { type: "event_callback", team_id: "T_KNOWN", event_id: eventId, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.1`, }, };
await signAndPost(app, body); await new Promise((r) => setTimeout(r, 50));
slackTestState.calls = []; await signAndPost(app, body); await new Promise((r) => setTimeout(r, 50));
expect(slackTestState.calls.length).toBe(0); });
test("ignores events from unknown teams", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_UNKNOWN", event_id: `evt_unknown_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.2`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores message events from bots", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_bot_${Date.now()}`, event: { type: "message", text: "bot message", bot_id: "B123", channel: "C1", ts: `${Date.now()}.3`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores channel message without bot mention", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_nomention_${Date.now()}`, event: { type: "message", text: "just a regular message", user: "U1", channel: "C1", channel_type: "channel", ts: `${Date.now()}.4`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores DM messages", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_dm_${Date.now()}`, event: { type: "message", text: "hello in DM", user: "U1", channel: "D1", channel_type: "im", ts: `${Date.now()}.5`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores events without channel", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_nochan_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", ts: `${Date.now()}.6`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores events without timestamp", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_nots_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores events without team_id", async () => { const res = await signAndPost(app, { type: "event_callback", event_id: `evt_noteam_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.7`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores unsupported event types", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_unsupported_${Date.now()}`, event: { type: "channel_created", }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores channel_join system messages", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_join_${Date.now()}`, event: { type: "message", subtype: "channel_join", text: "<@U1> has joined the channel", user: "U1", channel: "C1", ts: `${Date.now()}.10`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores channel_leave system messages", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_leave_${Date.now()}`, event: { type: "message", subtype: "channel_leave", text: "<@U1> has left the channel", user: "U1", channel: "C1", ts: `${Date.now()}.11`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("ignores events with no event payload", async () => { const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_noevent_${Date.now()}`, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 50)); expect(slackTestState.calls.length).toBe(0); });
test("falls back to top-level message on cannot_reply_to_message", async () => { let callCount = 0; slackTestState.postMessageOverride = (args: Record<string, unknown>) => { callCount++; if (callCount === 1) { const err = new Error("An API error occurred: cannot_reply_to_message"); Object.assign(err, { code: "slack_webapi_platform_error", data: { ok: false, error: "cannot_reply_to_message" }, }); return Promise.reject(err); } slackTestState.calls.push({ method: "postMessage", args }); return Promise.resolve({ ts: "fallback.ts" }); };
const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_cantreply_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.20`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 100));
const fallbackPost = slackTestState.calls.find( (m) => m.method === "postMessage" && !m.args.thread_ts, ); expect(fallbackPost).toBeDefined(); });
test("returns early on non-recoverable postMessage error", async () => { slackTestState.postMessageOverride = () => { const err = new Error("An API error occurred: channel_not_found"); Object.assign(err, { code: "slack_webapi_platform_error", data: { ok: false, error: "channel_not_found" }, }); return Promise.reject(err); };
const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_channotfound_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.21`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 100));
const updateMessages = slackTestState.calls.filter( (m) => m.method === "update", ); expect(updateMessages.length).toBe(0); });
test("shows error message when runAgent throws", async () => { slackTestState.runAgentOverride = () => Promise.reject(new Error("agent exploded"));
const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_agenterr_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.30`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 100));
const errorUpdate = slackTestState.calls.find( (m) => m.method === "update" && typeof m.args.text === "string" && m.args.text.includes("Something went wrong"), ); expect(errorUpdate).toBeDefined(); });
test("does not throw when both runAgent and error update fail", async () => { slackTestState.runAgentOverride = () => Promise.reject(new Error("agent exploded")); slackTestState.updateOverride = () => { const err = new Error("An API error occurred: channel_not_found"); Object.assign(err, { code: "slack_webapi_platform_error", data: { ok: false, error: "channel_not_found" }, }); return Promise.reject(err); };
const res = await signAndPost(app, { type: "event_callback", team_id: "T_KNOWN", event_id: `evt_doublefail_${Date.now()}`, event: { type: "app_mention", text: "<@UBOT> hello", user: "U1", channel: "C1", ts: `${Date.now()}.31`, }, });
expect(res.status).toBe(200); await new Promise((r) => setTimeout(r, 100)); // No unhandled rejection โ the .catch() in the error handler swallows it });});
describe("looksLikeUncardedDraft", () => { test("flags a prose draft that ends by asking permission", () => { expect( looksLikeUncardedDraft( `There's one status page ("Inter.link"). Here's the draft incident report I'll create:
**Title:** Traffic Forwarding Issue โ POP FRA1-DE**Status:** Investigating**Message:** We are currently investigating a traffic forwarding issue.
Shall I go ahead and publish this, or would you like to adjust anything?`, ), ).toBe(true); });
test("flags the other permission phrasings", () => { const draft = "*Title:* API outage\n*Message:* We're on it.\n"; expect(looksLikeUncardedDraft(`${draft}Want me to create this?`)).toBe( true, ); expect( looksLikeUncardedDraft(`${draft}Would you like me to post it now?`), ).toBe(true); });
test("ignores a plain answer that happens to end in a question", () => { expect( looksLikeUncardedDraft( "You have no active incidents. Want me to create one?", ), ).toBe(false); });
test("ignores a listing with field labels but no permission question", () => { expect( looksLikeUncardedDraft( "*Title:* API outage\n*Status:* investigating\n*Message:* We're on it.", ), ).toBe(false); });
test("ignores empty text", () => { expect(looksLikeUncardedDraft("")).toBe(false); });});