From 8bac169a5e58cdf9921e2a1e2ad9a77d1ed1b6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Wed, 1 Apr 2026 00:22:14 +0200 Subject: [PATCH] test(api): cover assistant broadcast reads --- .../assistant-tools-broadcast-detail.test.ts | 91 +++++++++++++++ .../assistant-tools-broadcast-list.test.ts | 105 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 packages/api/src/__tests__/assistant-tools-broadcast-detail.test.ts create mode 100644 packages/api/src/__tests__/assistant-tools-broadcast-list.test.ts diff --git a/packages/api/src/__tests__/assistant-tools-broadcast-detail.test.ts b/packages/api/src/__tests__/assistant-tools-broadcast-detail.test.ts new file mode 100644 index 0000000..bb9ffa5 --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-broadcast-detail.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it, vi } from "vitest"; +import { SystemRole } from "@capakraken/shared"; + +vi.mock("@capakraken/application", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), + getDashboardPeakTimes: vi.fn().mockResolvedValue([]), + listAssignmentBookings: vi.fn().mockResolvedValue([]), + }; +}); + +import { executeTool } from "../router/assistant-tools.js"; +import { createToolContext, withUserLookup } from "./assistant-tools-notification-test-helpers.js"; + +describe("assistant broadcast detail tools", () => { + it("reads broadcast details for managers and keeps the manager-only permission boundary", async () => { + const db = withUserLookup( + { + notificationBroadcast: { + findUnique: vi.fn().mockResolvedValue({ + id: "broadcast_7", + title: "Org update", + sender: { id: "user_mgr", name: "Manager", email: "mgr@example.com" }, + }), + }, + }, + "user_mgr", + ); + const managerCtx = createToolContext(db, SystemRole.MANAGER); + const userCtx = createToolContext( + withUserLookup({ + notificationBroadcast: { + findUnique: vi.fn(), + }, + }), + SystemRole.USER, + ); + + const allowed = await executeTool( + "get_broadcast_detail", + JSON.stringify({ id: "broadcast_7" }), + managerCtx, + ); + const denied = await executeTool( + "get_broadcast_detail", + JSON.stringify({ id: "broadcast_7" }), + userCtx, + ); + + expect(db.notificationBroadcast.findUnique).toHaveBeenCalledWith({ + where: { id: "broadcast_7" }, + include: { + sender: { select: { id: true, name: true, email: true } }, + }, + }); + expect(JSON.parse(allowed.content)).toEqual( + expect.objectContaining({ + id: "broadcast_7", + title: "Org update", + }), + ); + expect(JSON.parse(denied.content)).toEqual( + expect.objectContaining({ + error: "You do not have permission to perform this action.", + }), + ); + }); + + it("returns a stable assistant error for a missing broadcast", async () => { + const ctx = createToolContext( + withUserLookup({ + notificationBroadcast: { + findUnique: vi.fn().mockResolvedValue(null), + }, + }), + SystemRole.MANAGER, + ); + + const result = await executeTool( + "get_broadcast_detail", + JSON.stringify({ id: "broadcast_missing" }), + ctx, + ); + + expect(JSON.parse(result.content)).toEqual({ + error: "Broadcast not found with the given criteria.", + }); + }); +}); diff --git a/packages/api/src/__tests__/assistant-tools-broadcast-list.test.ts b/packages/api/src/__tests__/assistant-tools-broadcast-list.test.ts new file mode 100644 index 0000000..cf8438b --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-broadcast-list.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it, vi } from "vitest"; +import { SystemRole } from "@capakraken/shared"; + +vi.mock("@capakraken/application", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getDashboardBudgetForecast: vi.fn().mockResolvedValue([]), + getDashboardPeakTimes: vi.fn().mockResolvedValue([]), + listAssignmentBookings: vi.fn().mockResolvedValue([]), + }; +}); + +import { executeTool } from "../router/assistant-tools.js"; +import { createToolContext, withUserLookup } from "./assistant-tools-notification-test-helpers.js"; + +describe("assistant broadcast list tools", () => { + it("lists broadcasts through the real notification router path for manager users", async () => { + const db = withUserLookup( + { + notificationBroadcast: { + findMany: vi.fn().mockResolvedValue([ + { + id: "broadcast_1", + title: "Holiday planning reminder", + body: "Please verify local calendars.", + targetType: "role", + targetValue: "MANAGER", + category: "NOTIFICATION", + priority: "HIGH", + channel: "in_app", + recipientCount: 7, + createdAt: new Date("2026-03-30T10:00:00.000Z"), + scheduledAt: null, + sender: { + id: "user_mgr", + name: "Manager", + email: "mgr@example.com", + }, + }, + ]), + }, + }, + "user_mgr", + ); + const ctx = createToolContext(db, SystemRole.MANAGER); + + const result = await executeTool( + "list_broadcasts", + JSON.stringify({ limit: 10 }), + ctx, + ); + + expect(JSON.parse(result.content)).toEqual([ + { + id: "broadcast_1", + title: "Holiday planning reminder", + body: "Please verify local calendars.", + targetType: "role", + targetValue: "MANAGER", + category: "NOTIFICATION", + priority: "HIGH", + channel: "in_app", + recipientCount: 7, + createdAt: "2026-03-30T10:00:00.000Z", + scheduledAt: null, + sender: { + id: "user_mgr", + name: "Manager", + email: "mgr@example.com", + }, + }, + ]); + expect(db.notificationBroadcast.findMany).toHaveBeenCalledWith({ + orderBy: { createdAt: "desc" }, + take: 10, + include: { + sender: { select: { id: true, name: true, email: true } }, + }, + }); + }); + + it("rejects broadcast listing for regular users through the backing router", async () => { + const ctx = createToolContext( + withUserLookup({ + notificationBroadcast: { + findMany: vi.fn(), + }, + }), + SystemRole.USER, + ); + + const result = await executeTool( + "list_broadcasts", + JSON.stringify({ limit: 5 }), + ctx, + ); + + expect(JSON.parse(result.content)).toEqual( + expect.objectContaining({ + error: "You do not have permission to perform this action.", + }), + ); + }); +});