// @vitest-environment node import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@hivegrid/foundation", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, notify: vi.fn().mockResolvedValue(undefined) }; }); vi.mock("~/services/search/searchIndexProjection.server", () => ({ publishSearchProjectionDelete: vi.fn().mockResolvedValue(undefined), publishTaskSearchUpsert: vi.fn().mockResolvedValue(undefined), })); vi.mock("~/utils/db/.server/tenantRls.server", () => ({ withTenantRLS: (_tenantId: string, fn: (tx: unknown) => unknown) => fn(globalThis.__taskNotificationTestTx), })); vi.mock("~/utils/db.server", () => ({ db: {} })); import { notify } from "@hivegrid/foundation"; import { createTask } from "./taskService.server"; declare global { // eslint-disable-next-line no-var var __taskNotificationTestTx: Record; } function createTx() { return { tenantUser: { findFirst: vi.fn().mockResolvedValue({ id: "member_1" }) }, task: { create: vi.fn().mockImplementation(({ data }: { data: Record }) => Promise.resolve({ id: "task_1", ...data }) ), }, }; } const ctx = { userId: "user_actor", permissions: ["tasks.write"], groupIds: [] } as never; describe("task assignment notification", () => { beforeEach(() => { vi.mocked(notify).mockClear(); globalThis.__taskNotificationTestTx = createTx(); }); it("notifies a foreign assignee with an idempotent source key and task deeplink", async () => { await createTask({ tenantId: "tenant_1", tenantSlug: "acme", ctx, title: "Angebot nachfassen", assignedToUserId: "user_assignee", }); expect(notify).toHaveBeenCalledTimes(1); expect(notify).toHaveBeenCalledWith( expect.objectContaining({ tenantId: "tenant_1", userId: "user_assignee", channel: "IN_APP", category: "tasks", messageKey: "tasks.notification.assigned", sourceKey: "task-assigned:task_1:user_assignee", payloadMetadata: expect.objectContaining({ deeplink: "/acme/tasks/task_1" }), }), expect.anything() ); }); it("stays silent on self-assignment and on group-only assignment", async () => { await createTask({ tenantId: "tenant_1", tenantSlug: "acme", ctx, title: "Selbst", assignedToUserId: "user_actor" }); expect(notify).not.toHaveBeenCalled(); }); it("never fails the task write when the notification throws", async () => { vi.mocked(notify).mockRejectedValueOnce(new Error("notification backend down")); const task = await createTask({ tenantId: "tenant_1", tenantSlug: "acme", ctx, title: "Robust", assignedToUserId: "user_assignee", }); expect(task.id).toBe("task_1"); }); });