import { describe, it, expect } from "bun:test"; import { createHmac } from "crypto"; import { detectProvider } from "../server/lib/providers"; import { customProvider } from "../server/lib/providers/custom"; import { verifyGiteaSignature } from "../server/lib/providers/gitea/verify"; import { parseGiteaEvent } from "../server/lib/providers/gitea/parse"; import { parseEvent } from "../server/lib/providers/github/parse"; import { formatEvent } from "../server/lib/formatters"; import type { Env, Route } from "../server/lib/types"; function giteaSign(body: string, secret: string): string { return createHmac("sha256", secret).update(body).digest("hex"); } describe("provider detection", () => { it("detects github by X-GitHub-Event header", () => { const p = detectProvider({ "x-github-event": "push" }); expect(p?.id).toBe("github"); }); it("detects gitea by X-Gitea-Event header", () => { const p = detectProvider({ "x-gitea-event": "push" }); expect(p?.id).toBe("gitea"); }); it("prefers gitea when both gitea and github headers are present", () => { // Gitea webhooks also send GitHub-compatible headers (X-GitHub-Event, // X-Hub-Signature-256, X-Gogs-*), so detection must not misclassify them. const p = detectProvider({ "x-gitea-event": "push", "x-github-event": "push", "x-gitea-signature": "abc", "x-hub-signature-256": "sha256=abc", }); expect(p?.id).toBe("gitea"); }); it("returns null for unknown providers", () => { expect(detectProvider({})).toBeNull(); expect(detectProvider({ "x-gitlab-event": "Push Hook" })).toBeNull(); }); }); describe("gitea signature", () => { const secret = "gitea-secret"; it("accepts a valid hex HMAC-SHA256 signature (no sha256= prefix)", async () => { const body = '{"ref":"refs/heads/main"}'; const sig = giteaSign(body, secret); expect(await verifyGiteaSignature(body, sig, secret)).toBe(true); }); it("rejects an invalid signature", async () => { expect(await verifyGiteaSignature("body", "deadbeef", secret)).toBe(false); }); it("rejects when signature or secret is missing", async () => { expect(await verifyGiteaSignature("body", undefined, secret)).toBe(false); expect(await verifyGiteaSignature("body", "abc", undefined)).toBe(false); }); }); describe("gitea event parsing", () => { it("parses a push event and sets the provider", () => { const event = parseGiteaEvent( { "x-gitea-event": "push", "x-gitea-delivery": "d-1" }, JSON.stringify({ ref: "refs/heads/main", compare_url: "https://git.example.com/org/repo/compare/abc...def", pusher: { login: "octo", html_url: "https://git.example.com/octo" }, repository: { full_name: "org/repo", html_url: "https://git.example.com/org/repo" }, }), ); expect(event).not.toBeNull(); expect(event!.provider).toBe("gitea"); expect(event!.event).toBe("push"); expect(event!.deliveryId).toBe("d-1"); expect(event!.payload.compare).toBe("https://git.example.com/org/repo/compare/abc...def"); expect((event!.payload.sender as { login?: string }).login).toBe("octo"); }); it("maps pull_request_comment to pull_request_review_comment and pulls the PR", () => { const event = parseGiteaEvent( { "x-gitea-event": "pull_request_comment" }, JSON.stringify({ action: "created", issue: { number: 7, title: "Add feature", html_url: "https://git.example.com/org/repo/pulls/7", }, comment: { body: "looks good", line: 12, html_url: "https://git.example.com/org/repo/pulls/7#issuecomment-1", }, repository: { full_name: "org/repo" }, sender: { login: "octo" }, }), ); expect(event!.event).toBe("pull_request_review_comment"); const pr = event!.payload.pull_request as { number?: number; title?: string }; expect(pr.number).toBe(7); const comment = event!.payload.comment as { position?: number }; expect(comment.position).toBe(12); }); it("copies top-level commit_id onto the comment for commit_comment events", () => { const event = parseGiteaEvent( { "x-gitea-event": "commit_comment" }, JSON.stringify({ action: "created", commit_id: "abcd1234ef", comment: { body: "why?", html_url: "https://git.example.com/org/repo/commit/abcd1234ef#commitcomment-1", }, repository: { full_name: "org/repo" }, sender: { login: "octo" }, }), ); expect(event!.event).toBe("commit_comment"); expect((event!.payload.comment as { commit_id?: string }).commit_id).toBe("abcd1234ef"); }); it("returns null for missing event header or invalid JSON", () => { expect(parseGiteaEvent({}, "{}")).toBeNull(); expect(parseGiteaEvent({ "x-gitea-event": "push" }, "not json")).toBeNull(); }); it("formats a normalized gitea push with gitea commit links", () => { const event = parseGiteaEvent( { "x-gitea-event": "push" }, JSON.stringify({ ref: "refs/heads/main", compare_url: "https://git.example.com/org/repo/compare/abc...def", pusher: { login: "octo", html_url: "https://git.example.com/octo" }, commits: [{ id: "abcd1234ef", message: "fix stuff", added: [], removed: [], modified: [] }], repository: { full_name: "org/repo", html_url: "https://git.example.com/org/repo" }, }), ); const route: Route = { id: "test", name: "Test", enabled: true, filters: [], targets: [{ channelId: "111" }], }; const msg = formatEvent(route, event!); expect(msg.title).toContain("org/repo"); expect(msg.fields![0].value).toBe( "[`abcd123`](https://git.example.com/org/repo/commit/abcd1234ef) fix stuff", ); }); }); describe("custom provider", () => { const secret = "tenant-secret"; const body = JSON.stringify({ title: "Deploy failed", repo: "acme/widget", color: "red", description: "prod down", }); function env(overrides: Record = {}): Env { return { GITHUB_WEBHOOK_SECRET: secret, ...overrides } as Env; } it("matches only requests with X-WebHooker-Signature and no forge headers", () => { expect(customProvider.matches({ "x-webhooker-signature": "sha256=abc" })).toBe(true); expect(customProvider.matches({})).toBe(false); expect(customProvider.matches({ "x-github-event": "push" })).toBe(false); expect(customProvider.matches({ "x-gitea-event": "push" })).toBe(false); }); it("detectProvider finds the custom provider", () => { expect(detectProvider({ "x-webhooker-signature": "sha256=abc" })?.id).toBe("custom"); }); it("accepts a valid sha256 HMAC signature", async () => { const sig = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`; expect(await customProvider.verify(body, { "x-webhooker-signature": sig }, env())).toBe(true); }); it("rejects an invalid signature", async () => { expect( await customProvider.verify(body, { "x-webhooker-signature": "sha256=wrong" }, env()), ).toBe(false); }); it("rejects when the secret is missing", async () => { const sig = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`; expect( (await customProvider.verify(body, { "x-webhooker-signature": sig }, {})) as unknown as Env, ).toBe(false); }); it("parses the body into a custom event with optional deliveryId", () => { const event = customProvider.parse(body, {}); expect(event).not.toBeNull(); expect(event!.event).toBe("custom"); expect(event!.provider).toBe("custom"); expect((event!.payload as { title: string }).title).toBe("Deploy failed"); const withId = customProvider.parse(JSON.stringify({ title: "x", deliveryId: "alert-1" }), {}); expect(withId!.deliveryId).toBe("alert-1"); }); it("returns null for invalid JSON", () => { expect(customProvider.parse("not json", {})).toBeNull(); }); it("extracts the GitHub App installation id on github events", () => { const event = parseEvent( { "x-github-event": "push", "x-github-delivery": "d1" }, JSON.stringify({ installation: { id: 42 }, repository: { full_name: "a/b" } }), ); expect(event!.installationId).toBe(42); const noInstall = parseEvent( { "x-github-event": "push" }, JSON.stringify({ repository: { full_name: "a/b" } }), ); expect(noInstall!.installationId).toBeUndefined(); }); });