import { SystemRole } from "@capakraken/shared"; import { describe, expect, it, vi } from "vitest"; import { notificationRouter } from "../router/notification.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(notificationRouter); function createContext( db: Record, 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.MANAGER ? "user_mgr" : "user_1", systemRole: role, permissionOverrides: null, } : null, }; } describe("notification router authorization", () => { it("requires authentication for self-service notification lists", async () => { const findMany = vi.fn(); const caller = createCaller(createContext({ notification: { findMany, }, user: { findUnique: vi.fn(), }, }, { session: false })); await expect(caller.list({ limit: 20 })).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Authentication required", }); expect(findMany).not.toHaveBeenCalled(); }); it("requires authentication for unread counts, reminders, and self-service deletes", async () => { const count = vi.fn(); const findFirst = vi.fn(); const deleteFn = vi.fn(); const caller = createCaller(createContext({ notification: { count, findMany: vi.fn(), findFirst, delete: deleteFn, }, user: { findUnique: vi.fn(), }, }, { session: false })); await expect(caller.unreadCount()).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Authentication required", }); await expect(caller.listReminders({ limit: 10 })).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Authentication required", }); await expect(caller.delete({ id: "notif_1" })).rejects.toMatchObject({ code: "UNAUTHORIZED", message: "Authentication required", }); expect(count).not.toHaveBeenCalled(); expect(findFirst).not.toHaveBeenCalled(); expect(deleteFn).not.toHaveBeenCalled(); }); it("forbids regular users from creating broadcasts", async () => { const create = vi.fn(); const caller = createCaller(createContext({ notificationBroadcast: { create, }, })); await expect(caller.createBroadcast({ title: "Ops update", targetType: "all", })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Manager or Admin role required", }); expect(create).not.toHaveBeenCalled(); }); it("forbids regular users from reading broadcasts or creating tasks", async () => { const findUnique = vi.fn(); const create = vi.fn(); const caller = createCaller(createContext({ notificationBroadcast: { findUnique, }, notification: { create, }, })); await expect(caller.getBroadcastById({ id: "broadcast_1" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Manager or Admin role required", }); await expect(caller.createTask({ userId: "user_2", title: "Review proposal", })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Manager or Admin role required", }); expect(findUnique).not.toHaveBeenCalled(); expect(create).not.toHaveBeenCalled(); }); it("forbids regular users from reassigning tasks", async () => { const findUnique = vi.fn(); const caller = createCaller(createContext({ notification: { findUnique, }, })); await expect(caller.assignTask({ id: "task_1", assigneeId: "user_2" })).rejects.toMatchObject({ code: "FORBIDDEN", message: "Manager or Admin role required", }); expect(findUnique).not.toHaveBeenCalled(); }); it("allows managers to list broadcasts", async () => { const findMany = vi.fn().mockResolvedValue([ { id: "broadcast_1", title: "Ops update", createdAt: new Date("2026-03-30T10:00:00Z"), sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" }, }, ]); const caller = createCaller(createContext({ notificationBroadcast: { findMany, }, }, { role: SystemRole.MANAGER })); const result = await caller.listBroadcasts({ limit: 10 }); expect(result).toHaveLength(1); expect(findMany).toHaveBeenCalledWith({ orderBy: { createdAt: "desc" }, take: 10, include: { sender: { select: { id: true, name: true, email: true } }, }, }); }); it("allows managers to read individual broadcasts", async () => { const findUnique = vi.fn().mockResolvedValue({ id: "broadcast_1", title: "Ops update", sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" }, }); const caller = createCaller(createContext({ notificationBroadcast: { findUnique, }, }, { role: SystemRole.MANAGER })); const result = await caller.getBroadcastById({ id: "broadcast_1" }); expect(result).toEqual({ id: "broadcast_1", title: "Ops update", sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" }, }); expect(findUnique).toHaveBeenCalledWith({ where: { id: "broadcast_1" }, include: { sender: { select: { id: true, name: true, email: true } }, }, }); }); });