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
243
tests/admin-api.test.ts
Normal file
243
tests/admin-api.test.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
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"]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue