// @vitest-environment node import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("../../db.server", () => ({ db: { groupUser: { create: vi.fn(), createMany: vi.fn(), deleteMany: vi.fn(), }, $transaction: vi.fn(), }, })); import { db } from "../../db.server"; import { createUserGroup, createUserGroups, deleteGroupUsers, deleteUserGroup, replaceGroupUsers } from "./userGroups.db.server"; const dbMock = vi.mocked(db); describe("user group persistence", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(dbMock.$transaction).mockImplementation((async (callback: (tx: typeof dbMock) => Promise) => callback(dbMock)) as never); }); it("creates and deletes single group memberships", async () => { vi.mocked(dbMock.groupUser.create).mockResolvedValueOnce({ id: "group_user_1" } as never); vi.mocked(dbMock.groupUser.deleteMany).mockResolvedValueOnce({ count: 1 } as never); vi.mocked(dbMock.groupUser.deleteMany).mockResolvedValueOnce({ count: 2 } as never); await expect(createUserGroup("user_1", "group_1")).resolves.toEqual({ id: "group_user_1" }); await expect(deleteUserGroup("user_1", "group_1")).resolves.toEqual({ count: 1 }); await expect(deleteGroupUsers("group_1", "tenant_1")).resolves.toEqual({ count: 2 }); expect(dbMock.groupUser.create).toHaveBeenCalledWith({ data: { userId: "user_1", groupId: "group_1" } }); expect(dbMock.groupUser.deleteMany).toHaveBeenNthCalledWith(1, { where: { userId: "user_1", groupId: "group_1" } }); expect(dbMock.groupUser.deleteMany).toHaveBeenNthCalledWith(2, { where: { groupId: "group_1", group: { tenantId: "tenant_1" } } }); }); it("bulk-creates deduplicated group memberships with database duplicate protection", async () => { vi.mocked(dbMock.groupUser.createMany).mockResolvedValueOnce({ count: 2 } as never); await expect(createUserGroups("group_1", ["user_1", "user_2", "user_1"])).resolves.toEqual({ count: 2 }); expect(dbMock.groupUser.createMany).toHaveBeenCalledWith({ data: [ { userId: "user_1", groupId: "group_1" }, { userId: "user_2", groupId: "group_1" }, ], skipDuplicates: true, }); }); it("skips bulk membership writes when no users are selected", async () => { await expect(createUserGroups("group_1", [])).resolves.toEqual({ count: 0 }); expect(dbMock.groupUser.createMany).not.toHaveBeenCalled(); }); it("replaces group memberships inside one transaction", async () => { vi.mocked(dbMock.groupUser.deleteMany).mockResolvedValueOnce({ count: 3 } as never); vi.mocked(dbMock.groupUser.createMany).mockResolvedValueOnce({ count: 2 } as never); await expect(replaceGroupUsers("group_1", "tenant_1", ["user_1", "user_2", "user_1"])).resolves.toEqual({ count: 2 }); expect(dbMock.$transaction).toHaveBeenCalledTimes(1); expect(dbMock.groupUser.deleteMany).toHaveBeenCalledWith({ where: { groupId: "group_1", group: { tenantId: "tenant_1" } } }); expect(dbMock.groupUser.createMany).toHaveBeenCalledWith({ data: [ { userId: "user_1", groupId: "group_1" }, { userId: "user_2", groupId: "group_1" }, ], skipDuplicates: true, }); }); it("replaces group memberships with an empty set without bulk insert", async () => { vi.mocked(dbMock.groupUser.deleteMany).mockResolvedValueOnce({ count: 3 } as never); await expect(replaceGroupUsers("group_1", "tenant_1", [])).resolves.toEqual({ count: 0 }); expect(dbMock.$transaction).toHaveBeenCalledTimes(1); expect(dbMock.groupUser.deleteMany).toHaveBeenCalledWith({ where: { groupId: "group_1", group: { tenantId: "tenant_1" } } }); expect(dbMock.groupUser.createMany).not.toHaveBeenCalled(); }); });