ed4d4e4640
Four new test files — 27 tests total: - role-router-auth.test.ts (8): UNAUTHORIZED/FORBIDDEN on all mutations for unauthenticated/USER callers; MANAGER and ADMIN happy paths - webhook-router-auth.test.ts (6): adminProcedure guard verified for all six webhook procedures across USER/MANAGER/ADMIN roles - comment-sanitization-router.test.ts (4): proves stripHtml runs before db.comment.create — script tags stripped, plain text and @mentions preserved - auth-anomaly-check/route.test.ts (+5 unit tests): detectAuthAnomalies() unit coverage — empty window, global threshold, per-entity threshold, null entityId, and both anomaly types firing simultaneously Co-Authored-By: claude-flow <ruv@ruv.net>
164 lines
4.6 KiB
TypeScript
164 lines
4.6 KiB
TypeScript
import { SystemRole } from "@capakraken/shared";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { webhookRouter } from "../router/webhook.js";
|
|
import { createCallerFactory } from "../trpc.js";
|
|
|
|
const createCaller = createCallerFactory(webhookRouter);
|
|
|
|
function createContext(
|
|
db: Record<string, unknown>,
|
|
options: {
|
|
role?: SystemRole;
|
|
session?: boolean;
|
|
} = {},
|
|
) {
|
|
const { role = SystemRole.USER, session = true } = options;
|
|
|
|
return {
|
|
session: session
|
|
? {
|
|
user: { email: "user@example.com", name: "User", image: null },
|
|
expires: "2099-01-01T00:00:00.000Z",
|
|
}
|
|
: null,
|
|
db: db as never,
|
|
dbUser: session
|
|
? {
|
|
id: role === SystemRole.ADMIN ? "user_admin" : "user_1",
|
|
systemRole: role,
|
|
permissionOverrides: null,
|
|
}
|
|
: null,
|
|
};
|
|
}
|
|
|
|
describe("webhook router authorization", () => {
|
|
describe("unauthenticated access", () => {
|
|
it("rejects unauthenticated list call with UNAUTHORIZED", async () => {
|
|
const webhookFindMany = vi.fn();
|
|
const caller = createCaller(
|
|
createContext(
|
|
{ webhook: { findMany: webhookFindMany } },
|
|
{ session: false },
|
|
),
|
|
);
|
|
|
|
await expect(caller.list()).rejects.toMatchObject({
|
|
code: "UNAUTHORIZED",
|
|
message: "Authentication required",
|
|
});
|
|
|
|
expect(webhookFindMany).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("USER role — insufficient for admin-only procedures", () => {
|
|
it("forbids USER from calling list", async () => {
|
|
const webhookFindMany = vi.fn();
|
|
const caller = createCaller(
|
|
createContext({ webhook: { findMany: webhookFindMany } }),
|
|
);
|
|
|
|
await expect(caller.list()).rejects.toMatchObject({
|
|
code: "FORBIDDEN",
|
|
message: "Admin role required",
|
|
});
|
|
|
|
expect(webhookFindMany).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("MANAGER role — insufficient for admin-only procedures", () => {
|
|
it("forbids MANAGER from calling list", async () => {
|
|
const webhookFindMany = vi.fn();
|
|
const caller = createCaller(
|
|
createContext(
|
|
{ webhook: { findMany: webhookFindMany } },
|
|
{ role: SystemRole.MANAGER },
|
|
),
|
|
);
|
|
|
|
await expect(caller.list()).rejects.toMatchObject({
|
|
code: "FORBIDDEN",
|
|
message: "Admin role required",
|
|
});
|
|
|
|
expect(webhookFindMany).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("forbids MANAGER from calling create", async () => {
|
|
const webhookCreate = vi.fn();
|
|
const caller = createCaller(
|
|
createContext(
|
|
{ webhook: { create: webhookCreate } },
|
|
{ role: SystemRole.MANAGER },
|
|
),
|
|
);
|
|
|
|
await expect(
|
|
caller.create({
|
|
name: "My Webhook",
|
|
url: "https://example.com/hook",
|
|
events: ["allocation.created"],
|
|
}),
|
|
).rejects.toMatchObject({
|
|
code: "FORBIDDEN",
|
|
message: "Admin role required",
|
|
});
|
|
|
|
expect(webhookCreate).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("ADMIN role — full access to all procedures", () => {
|
|
it("allows ADMIN to call list without auth error", async () => {
|
|
const webhookFindMany = vi.fn().mockResolvedValue([]);
|
|
const caller = createCaller(
|
|
createContext(
|
|
{ webhook: { findMany: webhookFindMany } },
|
|
{ role: SystemRole.ADMIN },
|
|
),
|
|
);
|
|
|
|
const result = await caller.list();
|
|
|
|
expect(result).toEqual([]);
|
|
expect(webhookFindMany).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("allows ADMIN to call create without auth error", async () => {
|
|
const createdWebhook = {
|
|
id: "webhook_1",
|
|
name: "Slack Notifications",
|
|
url: "https://hooks.slack.com/services/test",
|
|
secret: null,
|
|
events: ["allocation.created"],
|
|
isActive: true,
|
|
createdAt: new Date("2024-01-01"),
|
|
updatedAt: new Date("2024-01-01"),
|
|
};
|
|
const webhookCreate = vi.fn().mockResolvedValue(createdWebhook);
|
|
const auditLogCreate = vi.fn().mockResolvedValue({});
|
|
|
|
const caller = createCaller(
|
|
createContext(
|
|
{
|
|
webhook: { create: webhookCreate },
|
|
auditLog: { create: auditLogCreate },
|
|
},
|
|
{ role: SystemRole.ADMIN },
|
|
),
|
|
);
|
|
|
|
const result = await caller.create({
|
|
name: "Slack Notifications",
|
|
url: "https://hooks.slack.com/services/test",
|
|
events: ["allocation.created"],
|
|
});
|
|
|
|
expect(result).toMatchObject({ id: "webhook_1", name: "Slack Notifications" });
|
|
expect(webhookCreate).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|