mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
feat: migrate to Nuxt 4 (Nitro) and Tailwind CSS v3
This commit is contained in:
parent
f4959eebf8
commit
b139712a91
166 changed files with 19790 additions and 5539 deletions
226
tests/providers.test.ts
Normal file
226
tests/providers.test.ts
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
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<string, unknown> = {}): 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();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue