import { test } from "node:test";
import assert from "node:assert/strict";
import { createHmac } from "node:crypto";
import { handleSlackCommandRequest, type SlackWebhookEnv } from "../src/adapters/slack/webhook.ts";
import { testDb } from "./helpers.ts";
const SIGNING_SECRET = "test-signing-secret";
const BASE_ENV: SlackWebhookEnv = { SLACK_SIGNING_SECRET: SIGNING_SECRET };
function sign(body: string, timestamp: string, secret: string = SIGNING_SECRET): string {
const base = `v0:${timestamp}:${body}`;
return `v0=${createHmac("sha256", secret).update(base).digest("hex")}`;
}
function slashRequest(
form: Record<string, string>,
opts: { timestamp?: string; signature?: string; skipSignature?: boolean } = {}
): Request {
const body = new URLSearchParams(form).toString();
const timestamp = opts.timestamp ?? String(Math.floor(Date.now() / 1000));
const headers: Record<string, string> = { "Content-Type": "application/x-www-form-urlencoded" };
if (!opts.skipSignature) {
headers["X-Slack-Request-Timestamp"] = timestamp;
headers["X-Slack-Signature"] = opts.signature ?? sign(body, timestamp);
}
return new Request("https://example.com/slack/commands", { method: "POST", headers, body });
}
test("rejects a request with a bad signature", async () => {
const { db } = testDb();
const req = slashRequest({ command: "/cm-list", text: "", user_id: "U1" }, { signature: "v0=deadbeef" });
const res = await handleSlackCommandRequest(req, BASE_ENV, db);
assert.equal(res.status, 401);
});
test("rejects a request with no signature headers at all", async () => {
const { db } = testDb();
const req = slashRequest({ command: "/cm-list", text: "", user_id: "U1" }, { skipSignature: true });
const res = await handleSlackCommandRequest(req, BASE_ENV, db);
assert.equal(res.status, 401);