mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
243 lines
7.7 KiB
TypeScript
243 lines
7.7 KiB
TypeScript
import { describe, it, expect } from "bun:test";
|
|
import {
|
|
adminGroupRename,
|
|
adminGroupRoutesGet,
|
|
adminApiMe,
|
|
} from "../server/lib/web/admin";
|
|
import { createAdminSession, adminCookie } from "../server/lib/web/session";
|
|
import { loadGroups } from "../server/lib/web/groups";
|
|
import { loadRoutes } from "../server/lib/config";
|
|
import { createInvite, listInvites } from "../server/lib/web/invites";
|
|
import { getTenantSecret, setTenantSecret } from "../server/lib/web/tenants";
|
|
import { makeEvent, responseStatus } from "./helpers";
|
|
import type { Env, Route } from "../server/lib/types";
|
|
|
|
function createMockKV(): KVNamespace {
|
|
const store = new Map<string, string>();
|
|
return {
|
|
get: async (key: string, type?: string) => {
|
|
const v = store.get(key);
|
|
if (v == null) return null;
|
|
if (type === "json") return JSON.parse(v);
|
|
return v;
|
|
},
|
|
put: async (key: string, value: string) => {
|
|
store.set(key, value);
|
|
},
|
|
delete: async (key: string) => {
|
|
store.delete(key);
|
|
},
|
|
list: async () => ({
|
|
keys: [...store.keys()].map((k) => ({ name: k })),
|
|
list_complete: true,
|
|
cacheStatus: null,
|
|
}),
|
|
} as unknown as KVNamespace;
|
|
}
|
|
|
|
function createMockDB(): D1Database {
|
|
return {
|
|
prepare: () => ({
|
|
bind: () => ({
|
|
run: async () => ({ success: true }),
|
|
all: async () => ({ results: [] }),
|
|
}),
|
|
}),
|
|
} as unknown as D1Database;
|
|
}
|
|
|
|
function createEnv(overrides: Partial<Env> = {}): Env {
|
|
return {
|
|
GITHUB_WEBHOOK_SECRET: "secret",
|
|
KV: createMockKV(),
|
|
DB: createMockDB(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("admin handlers (h3)", () => {
|
|
it("renames an owned group and follows routes, secret and invites", async () => {
|
|
const kv = createMockKV();
|
|
await kv.put(
|
|
"config:groups",
|
|
JSON.stringify([
|
|
{
|
|
id: "old-team",
|
|
name: "Old Team",
|
|
adminIds: [],
|
|
members: [{ login: "alice", role: "owner" }],
|
|
},
|
|
]),
|
|
);
|
|
await kv.put(
|
|
"config:routes",
|
|
JSON.stringify([
|
|
{
|
|
id: "r1",
|
|
name: "R1",
|
|
enabled: true,
|
|
groupId: "old-team",
|
|
filters: [{ type: "event", match: "push" }],
|
|
targets: [{ channelId: "111" }],
|
|
},
|
|
] as Route[]),
|
|
);
|
|
await setTenantSecret(kv, "old-team");
|
|
const originalSecret = (await getTenantSecret(kv, "old-team"))!;
|
|
await createInvite(kv, {
|
|
groupId: "old-team",
|
|
role: "viewer",
|
|
expiresAt: Date.now() + 86400_000,
|
|
createdBy: "alice",
|
|
});
|
|
const env = createEnv({ KV: kv });
|
|
const sessionId = await createAdminSession(kv, "1001", "alice");
|
|
|
|
const event = makeEvent("/api/groups/old-team/rename", {
|
|
method: "PUT",
|
|
headers: { cookie: adminCookie(sessionId), "content-type": "application/json" },
|
|
body: JSON.stringify({ newId: "new-team" }),
|
|
env,
|
|
});
|
|
const result = (await adminGroupRename(event, "old-team")) as { ok?: boolean };
|
|
expect(responseStatus(event)).toBe(200);
|
|
expect(result.ok).toBe(true);
|
|
|
|
const groups = await loadGroups(kv);
|
|
expect(groups).toHaveLength(1);
|
|
expect(groups[0]!.id).toBe("new-team");
|
|
expect(groups[0]!.name).toBe("Old Team");
|
|
|
|
const routes = await loadRoutes(kv);
|
|
expect(routes[0]!.groupId).toBe("new-team");
|
|
|
|
expect(await getTenantSecret(kv, "new-team")).toBe(originalSecret);
|
|
expect(await getTenantSecret(kv, "old-team")).toBeNull();
|
|
|
|
const invites = await listInvites(kv, "new-team");
|
|
expect(invites).toHaveLength(1);
|
|
expect(invites[0]!.groupId).toBe("new-team");
|
|
expect(await listInvites(kv, "old-team")).toHaveLength(0);
|
|
});
|
|
|
|
it("forbids non-owner members from renaming", async () => {
|
|
const kv = createMockKV();
|
|
await kv.put(
|
|
"config:groups",
|
|
JSON.stringify([
|
|
{ id: "team", name: "Team", adminIds: [], members: [{ login: "bob", role: "admin" }] },
|
|
]),
|
|
);
|
|
const env = createEnv({ KV: kv });
|
|
const sessionId = await createAdminSession(kv, "2002", "bob");
|
|
|
|
const event = makeEvent("/api/groups/team/rename", {
|
|
method: "PUT",
|
|
headers: { cookie: adminCookie(sessionId), "content-type": "application/json" },
|
|
body: JSON.stringify({ newId: "new-team" }),
|
|
env,
|
|
});
|
|
await adminGroupRename(event, "team");
|
|
expect(responseStatus(event)).toBe(403);
|
|
});
|
|
|
|
it("rejects invalid, duplicate or unchanged ids", async () => {
|
|
const kv = createMockKV();
|
|
await kv.put(
|
|
"config:groups",
|
|
JSON.stringify([
|
|
{ id: "team", name: "Team", adminIds: [], members: [{ login: "alice", role: "owner" }] },
|
|
{ id: "other", name: "Other", adminIds: [] },
|
|
]),
|
|
);
|
|
const env = createEnv({ KV: kv });
|
|
const sessionId = await createAdminSession(kv, "1001", "alice");
|
|
|
|
const make = (newId: string) => {
|
|
const event = makeEvent("/api/groups/team/rename", {
|
|
method: "PUT",
|
|
headers: { cookie: adminCookie(sessionId), "content-type": "application/json" },
|
|
body: JSON.stringify({ newId }),
|
|
env,
|
|
});
|
|
return adminGroupRename(event, "team").then(() => responseStatus(event));
|
|
};
|
|
|
|
expect(await make("Bad ID!")).toBe(400);
|
|
expect(await make("team")).toBe(400);
|
|
expect(await make("other")).toBe(400);
|
|
});
|
|
|
|
it("returns 401 without a session", async () => {
|
|
const env = createEnv();
|
|
const event = makeEvent("/api/groups/team/rename", {
|
|
method: "PUT",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ newId: "new-team" }),
|
|
env,
|
|
});
|
|
await expect(adminGroupRename(event, "team")).rejects.toMatchObject({ statusCode: 401 });
|
|
});
|
|
|
|
it("returns 404 for an unknown group", async () => {
|
|
const kv = createMockKV();
|
|
await kv.put(
|
|
"config:groups",
|
|
JSON.stringify([
|
|
{ id: "mine", name: "Mine", adminIds: [], members: [{ login: "alice", role: "owner" }] },
|
|
]),
|
|
);
|
|
const env = createEnv({ KV: kv });
|
|
const sessionId = await createAdminSession(kv, "1001", "alice");
|
|
|
|
const event = makeEvent("/api/groups/nope/rename", {
|
|
method: "PUT",
|
|
headers: { cookie: adminCookie(sessionId), "content-type": "application/json" },
|
|
body: JSON.stringify({ newId: "new-team" }),
|
|
env,
|
|
});
|
|
const result = (await adminGroupRename(event, "nope")) as { error?: string };
|
|
expect(responseStatus(event)).toBe(404);
|
|
expect(result.error).toBe("Group not found");
|
|
});
|
|
|
|
it("me and group routes scoping work", async () => {
|
|
const kv = createMockKV();
|
|
await kv.put(
|
|
"config:groups",
|
|
JSON.stringify([
|
|
{ id: "mine", name: "Mine", adminIds: [], members: [{ login: "alice", role: "owner" }] },
|
|
{ id: "theirs", name: "Theirs", adminIds: [], members: [{ login: "bob", role: "owner" }] },
|
|
]),
|
|
);
|
|
await kv.put(
|
|
"config:routes",
|
|
JSON.stringify([
|
|
{
|
|
id: "mine-r",
|
|
name: "Mine R",
|
|
enabled: true,
|
|
groupId: "mine",
|
|
filters: [{ type: "event", match: "push" }],
|
|
targets: [{ channelId: "111" }],
|
|
},
|
|
] as Route[]),
|
|
);
|
|
const env = createEnv({ KV: kv });
|
|
const sessionId = await createAdminSession(kv, "1001", "alice");
|
|
|
|
const meEvent = makeEvent("/api/me", { headers: { cookie: adminCookie(sessionId) }, env });
|
|
const me = (await adminApiMe(meEvent)) as { groups?: Array<{ id: string }>; isSuper?: boolean };
|
|
expect(me.groups?.map((g) => g.id)).toEqual(["mine"]);
|
|
expect(me.isSuper).toBe(false);
|
|
|
|
const routesEvent = makeEvent("/api/groups/mine/routes", {
|
|
headers: { cookie: adminCookie(sessionId) },
|
|
env,
|
|
});
|
|
const groupRoutes = (await adminGroupRoutesGet(routesEvent, "mine")) as {
|
|
routes?: Array<{ id: string }>;
|
|
};
|
|
expect(groupRoutes.routes?.map((r) => r.id)).toEqual(["mine-r"]);
|
|
});
|
|
});
|