85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
// @vitest-environment node
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
vi.mock("@hivegrid/foundation", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("@hivegrid/foundation")>();
|
|
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<string, unknown>;
|
|
}
|
|
|
|
function createTx() {
|
|
return {
|
|
tenantUser: { findFirst: vi.fn().mockResolvedValue({ id: "member_1" }) },
|
|
task: {
|
|
create: vi.fn().mockImplementation(({ data }: { data: Record<string, unknown> }) =>
|
|
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");
|
|
});
|
|
});
|