From 25ce562d17992a027780efcde3a8d3e1ad52c062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Tue, 31 Mar 2026 23:56:35 +0200 Subject: [PATCH] test(api): cover assistant notification inbox tools --- ...nt-tools-notification-inbox-errors.test.ts | 69 +++++++++++++++ ...tant-tools-notification-inbox-read.test.ts | 84 +++++++++++++++++++ ...sistant-tools-notification-test-helpers.ts | 34 ++++++++ 3 files changed, 187 insertions(+) create mode 100644 packages/api/src/__tests__/assistant-tools-notification-inbox-errors.test.ts create mode 100644 packages/api/src/__tests__/assistant-tools-notification-inbox-read.test.ts create mode 100644 packages/api/src/__tests__/assistant-tools-notification-test-helpers.ts diff --git a/packages/api/src/__tests__/assistant-tools-notification-inbox-errors.test.ts b/packages/api/src/__tests__/assistant-tools-notification-inbox-errors.test.ts new file mode 100644 index 0000000..edf0e20 --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-notification-inbox-errors.test.ts @@ -0,0 +1,69 @@ +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 notification inbox error tools", () => { + it("returns a stable assistant error when marking a missing notification as read", async () => { + const update = vi.fn().mockRejectedValue({ + code: "P2025", + message: "No record was found for an update.", + }); + const ctx = createToolContext( + { + user: { + findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), + }, + notification: { + update, + }, + }, + SystemRole.CONTROLLER, + ); + + const result = await executeTool( + "mark_notification_read", + JSON.stringify({ notificationId: "notif_missing" }), + ctx, + ); + + expect(update).toHaveBeenCalledWith({ + where: { id: "notif_missing", userId: "user_1" }, + data: expect.objectContaining({ + readAt: expect.any(Date), + }), + }); + expect(JSON.parse(result.content)).toEqual({ + error: "Notification not found with the given criteria.", + }); + }); + + it("returns a stable assistant error when deleting a missing notification", async () => { + const ctx = createToolContext(withUserLookup({ + notification: { + findFirst: vi.fn().mockResolvedValue(null), + }, + }), SystemRole.ADMIN); + + const result = await executeTool( + "delete_notification", + JSON.stringify({ id: "notification_missing" }), + ctx, + ); + + expect(JSON.parse(result.content)).toEqual({ + error: "Notification not found with the given criteria.", + }); + }); +}); diff --git a/packages/api/src/__tests__/assistant-tools-notification-inbox-read.test.ts b/packages/api/src/__tests__/assistant-tools-notification-inbox-read.test.ts new file mode 100644 index 0000000..9b1f070 --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-notification-inbox-read.test.ts @@ -0,0 +1,84 @@ +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 notification inbox read tools", () => { + it("returns the unread notification count for the current user", async () => { + const db = withUserLookup({ + notification: { + count: vi.fn().mockResolvedValue(5), + }, + }); + const ctx = createToolContext(db, SystemRole.USER); + + const result = await executeTool( + "get_unread_notification_count", + JSON.stringify({}), + ctx, + ); + + expect(JSON.parse(result.content)).toEqual({ count: 5 }); + expect(db.notification.count).toHaveBeenCalledWith( + expect.objectContaining({ + where: { userId: "user_1", readAt: null }, + }), + ); + }); + + it("lists notifications for the current session user through the real notification router path", async () => { + const db = { + user: { + findUnique: vi.fn().mockResolvedValue({ id: "user_1" }), + }, + notification: { + findMany: vi.fn().mockResolvedValue([ + { + id: "note_1", + title: "Check staffing", + body: "Review the new staffing suggestion", + readAt: null, + createdAt: new Date("2026-03-29T08:00:00.000Z"), + }, + ]), + }, + }; + const ctx = createToolContext(db, SystemRole.CONTROLLER); + + const result = await executeTool( + "list_notifications", + JSON.stringify({ unreadOnly: true, limit: 5 }), + ctx, + ); + + expect(db.user.findUnique).toHaveBeenCalledWith({ + where: { email: "user@example.com" }, + select: { id: true }, + }); + expect(db.notification.findMany).toHaveBeenCalledWith({ + where: { + userId: "user_1", + readAt: null, + }, + orderBy: { createdAt: "desc" }, + take: 5, + }); + expect(JSON.parse(result.content)).toEqual([ + expect.objectContaining({ + id: "note_1", + title: "Check staffing", + }), + ]); + }); +}); diff --git a/packages/api/src/__tests__/assistant-tools-notification-test-helpers.ts b/packages/api/src/__tests__/assistant-tools-notification-test-helpers.ts new file mode 100644 index 0000000..c289ec9 --- /dev/null +++ b/packages/api/src/__tests__/assistant-tools-notification-test-helpers.ts @@ -0,0 +1,34 @@ +import { vi } from "vitest"; +import { SystemRole } from "@capakraken/shared"; +import type { ToolContext } from "../router/assistant-tools.js"; + +export function createToolContext( + db: Record, + userRole: SystemRole = SystemRole.USER, +): ToolContext { + return { + db: db as ToolContext["db"], + userId: "user_1", + userRole, + permissions: new Set(), + session: { + user: { email: "user@example.com", name: "Assistant User", image: null }, + expires: "2026-03-29T00:00:00.000Z", + }, + dbUser: { + id: "user_1", + systemRole: userRole, + permissionOverrides: null, + }, + roleDefaults: null, + }; +} + +export function withUserLookup(db: Record, userId = "user_1") { + return { + user: { + findUnique: vi.fn().mockResolvedValue({ id: userId }), + }, + ...db, + }; +}