// @vitest-environment node import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("../../db.server", () => ({ db: { group: { create: vi.fn(), delete: vi.fn(), deleteMany: vi.fn(), findFirst: vi.fn(), findMany: vi.fn(), update: vi.fn(), updateMany: vi.fn(), }, groupUser: { createMany: vi.fn(), deleteMany: vi.fn(), }, }, })); import { db } from "../../db.server"; import { addUsersToGroup, createGroup, deleteGroup, getAllGroups, getGroup, getGroups, getMyGroups, removeUsersFromGroup, updateGroup } from "./groups.db.server"; const dbMock = vi.mocked(db); describe("group persistence", () => { beforeEach(() => { vi.clearAllMocks(); }); it("lists tenant groups and selected groups with users included", async () => { vi.mocked(dbMock.group.findMany).mockResolvedValue([] as never); await getAllGroups("tenant_1"); await getGroups("tenant_1", ["group_1", "group_2"]); expect(dbMock.group.findMany).toHaveBeenNthCalledWith( 1, expect.objectContaining({ where: { tenantId: "tenant_1" }, include: expect.objectContaining({ users: expect.any(Object) }), orderBy: [{ name: "asc" }], }) ); expect(dbMock.group.findMany).toHaveBeenNthCalledWith( 2, expect.objectContaining({ where: { tenantId: "tenant_1", id: { in: ["group_1", "group_2"] } }, include: expect.objectContaining({ users: expect.any(Object) }), orderBy: [{ name: "asc" }], }) ); }); it("limits my-groups lookup to created or joined groups in the tenant", async () => { vi.mocked(dbMock.group.findMany).mockResolvedValueOnce([] as never); await getMyGroups("user_1", "tenant_1"); expect(dbMock.group.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { tenantId: "tenant_1", OR: [{ createdByUserId: "user_1" }, { users: { some: { userId: "user_1" } } }], }, include: expect.objectContaining({ users: expect.any(Object) }), orderBy: [{ name: "asc" }], }) ); }); it("gets groups with optional tenant scope", async () => { vi.mocked(dbMock.group.findFirst).mockResolvedValue({ id: "group_1" } as never); await getGroup("group_1"); await getGroup("group_1", "tenant_1"); expect(dbMock.group.findFirst).toHaveBeenNthCalledWith(1, expect.objectContaining({ where: { id: "group_1" } })); expect(dbMock.group.findFirst).toHaveBeenNthCalledWith(2, expect.objectContaining({ where: { id: "group_1", tenantId: "tenant_1" } })); }); it("creates and mutates groups with tenant-scoped bulk operations when a tenant is provided", async () => { vi.mocked(dbMock.group.create).mockResolvedValueOnce({ id: "group_1" } as never); vi.mocked(dbMock.group.updateMany).mockResolvedValueOnce({ count: 1 } as never); vi.mocked(dbMock.group.update).mockResolvedValueOnce({ id: "group_1" } as never); vi.mocked(dbMock.group.deleteMany).mockResolvedValueOnce({ count: 1 } as never); vi.mocked(dbMock.group.delete).mockResolvedValueOnce({ id: "group_1" } as never); const data = { createdByUserId: "user_1", tenantId: "tenant_1", name: "Team", description: "Core team", color: 1 }; await createGroup(data); await updateGroup("group_1", { name: "Ops", description: "Ops team", color: 2 }, "tenant_1"); await updateGroup("group_1", { name: "Ops", description: "Ops team", color: 2 }); await deleteGroup("group_1", "tenant_1"); await deleteGroup("group_1"); expect(dbMock.group.create).toHaveBeenCalledWith({ data }); expect(dbMock.group.updateMany).toHaveBeenCalledWith({ where: { id: "group_1", tenantId: "tenant_1" }, data: { name: "Ops", description: "Ops team", color: 2 }, }); expect(dbMock.group.update).toHaveBeenCalledWith({ where: { id: "group_1" }, data: { name: "Ops", description: "Ops team", color: 2 }, }); expect(dbMock.group.deleteMany).toHaveBeenCalledWith({ where: { id: "group_1", tenantId: "tenant_1" } }); expect(dbMock.group.delete).toHaveBeenCalledWith({ where: { id: "group_1" } }); }); it("adds users to a group idempotently (skipDuplicates) and removes them in bulk", async () => { vi.mocked(dbMock.groupUser.createMany).mockResolvedValueOnce({ count: 2 } as never); vi.mocked(dbMock.groupUser.deleteMany).mockResolvedValueOnce({ count: 1 } as never); await addUsersToGroup("group_1", ["user_1", "user_2"]); await removeUsersFromGroup("group_1", ["user_1"]); expect(dbMock.groupUser.createMany).toHaveBeenCalledWith({ data: [ { groupId: "group_1", userId: "user_1" }, { groupId: "group_1", userId: "user_2" }, ], skipDuplicates: true, }); expect(dbMock.groupUser.deleteMany).toHaveBeenCalledWith({ where: { groupId: "group_1", userId: { in: ["user_1"] } } }); }); it("short-circuits bulk membership ops on an empty user list", async () => { expect(await addUsersToGroup("group_1", [])).toEqual({ count: 0 }); expect(await removeUsersFromGroup("group_1", [])).toEqual({ count: 0 }); expect(dbMock.groupUser.createMany).not.toHaveBeenCalled(); expect(dbMock.groupUser.deleteMany).not.toHaveBeenCalled(); }); });