Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/db/permissions/rolePermissions.db.server.test.ts
T

158 lines
6.8 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../db.server", () => ({
db: {
role: {
findUnique: vi.fn(),
findMany: vi.fn(),
},
permission: {
findMany: vi.fn(),
},
rolePermission: {
create: vi.fn(),
createMany: vi.fn(),
delete: vi.fn(),
deleteMany: vi.fn(),
findFirst: vi.fn(),
findMany: vi.fn(),
},
$transaction: vi.fn(),
},
}));
vi.mock("~/utils/cache.server", () => ({ clearCacheKey: vi.fn() }));
vi.mock("../tenants.db.server", () => ({ adminGetAllTenants: vi.fn() }));
vi.mock("../users.db.server", () => ({ adminGetAllUsersNames: vi.fn() }));
import { clearCacheKey } from "~/utils/cache.server";
import { adminGetAllTenants } from "../tenants.db.server";
import { adminGetAllUsersNames } from "../users.db.server";
import { db } from "../../db.server";
import {
createRolePermission,
deleteRolePermission,
deleteRolePermissionById,
getAllRolePermissions,
setPermissionRoles,
setRolePermissions,
} from "./rolePermissions.db.server";
describe("role permission persistence", () => {
const dbMock = vi.mocked(db);
const findRoleByIdMock = vi.mocked(dbMock.role.findUnique);
const findPermissionsMock = vi.mocked(dbMock.permission.findMany);
const findRolesMock = vi.mocked(dbMock.role.findMany);
beforeEach(() => {
vi.clearAllMocks();
dbMock.$transaction.mockImplementation(async (callback: (tx: typeof dbMock) => Promise<unknown>) => callback(dbMock));
vi.mocked(adminGetAllTenants).mockResolvedValue([]);
vi.mocked(adminGetAllUsersNames).mockResolvedValue([]);
});
it("sets role permissions inside one awaited transaction before clearing caches", async () => {
findRoleByIdMock.mockResolvedValue({ id: "role_1", name: "Editor", type: "app" } as never);
findPermissionsMock.mockResolvedValue([{ id: "permission_a" }, { id: "permission_b" }] as never);
await setRolePermissions("role_1", ["settings.update", "settings.update", "members.read"]);
expect(dbMock.permission.findMany).toHaveBeenCalledWith({
where: { name: { in: ["settings.update", "members.read"] } },
select: { id: true },
});
expect(dbMock.$transaction).toHaveBeenCalledTimes(1);
expect(dbMock.rolePermission.deleteMany).toHaveBeenCalledWith({ where: { roleId: "role_1" } });
expect(dbMock.rolePermission.createMany).toHaveBeenCalledWith({
data: [
{ roleId: "role_1", permissionId: "permission_a" },
{ roleId: "role_1", permissionId: "permission_b" },
],
});
expect(clearCacheKey).toHaveBeenCalledWith("roles:app");
});
it("reads and creates role-permission links without duplicating existing links", async () => {
dbMock.rolePermission.findMany.mockResolvedValueOnce([{ id: "rp_1" }] as never);
dbMock.rolePermission.findFirst
.mockResolvedValueOnce({ id: "rp_existing" } as never)
.mockResolvedValueOnce(null);
dbMock.rolePermission.create.mockResolvedValueOnce({ id: "rp_created" } as never);
await expect(getAllRolePermissions()).resolves.toEqual([{ id: "rp_1" }]);
await expect(createRolePermission({ roleId: "role_1", permissionId: "permission_1" })).resolves.toEqual({ id: "rp_existing" });
await expect(createRolePermission({ roleId: "role_1", permissionId: "permission_2" })).resolves.toEqual({ id: "rp_created" });
expect(dbMock.rolePermission.create).toHaveBeenCalledTimes(1);
expect(dbMock.rolePermission.create).toHaveBeenCalledWith({ data: { roleId: "role_1", permissionId: "permission_2" } });
});
it("returns early when setting permissions for a missing role", async () => {
findRoleByIdMock.mockResolvedValueOnce(null);
await setRolePermissions("role_missing", ["settings.update"]);
expect(dbMock.permission.findMany).not.toHaveBeenCalled();
expect(dbMock.$transaction).not.toHaveBeenCalled();
});
it("clears app role user caches across tenants and user-role lookup caches", async () => {
findRoleByIdMock.mockResolvedValue({ id: "role_1", name: "Member", type: "app" } as never);
findPermissionsMock.mockResolvedValue([]);
vi.mocked(adminGetAllTenants).mockResolvedValue([
{ id: "tenant_1", users: [{ userId: "user_1" }, { userId: "user_2" }] },
{ id: "tenant_2", users: [{ userId: "user_3" }] },
] as never);
vi.mocked(adminGetAllUsersNames).mockResolvedValue([{ id: "user_1" }, { id: "user_4" }] as never);
await setRolePermissions("role_1", []);
expect(dbMock.rolePermission.createMany).not.toHaveBeenCalled();
expect(clearCacheKey).toHaveBeenCalledWith("roles:app");
expect(clearCacheKey).toHaveBeenCalledWith("userRoles:user_1:tenant_1");
expect(clearCacheKey).toHaveBeenCalledWith("userRoles:user_2:tenant_1");
expect(clearCacheKey).toHaveBeenCalledWith("userRoles:user_3:tenant_2");
expect(clearCacheKey).toHaveBeenCalledWith("userRole:user_1:Member");
expect(clearCacheKey).toHaveBeenCalledWith("userRole:user_4:Member");
});
it("sets permission roles inside one awaited transaction", async () => {
findRolesMock.mockResolvedValue([{ id: "role_a" }, { id: "role_b" }] as never);
await setPermissionRoles("permission_1", ["Admin", "Admin", "Member"]);
expect(dbMock.role.findMany).toHaveBeenCalledWith({
where: { name: { in: ["Admin", "Member"] } },
select: { id: true },
});
expect(dbMock.$transaction).toHaveBeenCalledTimes(1);
expect(dbMock.rolePermission.deleteMany).toHaveBeenCalledWith({ where: { permissionId: "permission_1" } });
expect(dbMock.rolePermission.createMany).toHaveBeenCalledWith({
data: [
{ roleId: "role_a", permissionId: "permission_1" },
{ roleId: "role_b", permissionId: "permission_1" },
],
});
});
it("clears permission role links without creating rows when role list is empty", async () => {
await setPermissionRoles("permission_1", []);
expect(dbMock.role.findMany).not.toHaveBeenCalled();
expect(dbMock.$transaction).toHaveBeenCalledTimes(1);
expect(dbMock.rolePermission.deleteMany).toHaveBeenCalledWith({ where: { permissionId: "permission_1" } });
expect(dbMock.rolePermission.createMany).not.toHaveBeenCalled();
});
it("deletes role-permission links by composite keys or id", async () => {
dbMock.rolePermission.deleteMany.mockResolvedValueOnce({ count: 1 } as never);
dbMock.rolePermission.delete.mockResolvedValueOnce({ id: "rp_1" } as never);
await expect(deleteRolePermission("role_1", "permission_1")).resolves.toEqual({ count: 1 });
await expect(deleteRolePermissionById("rp_1")).resolves.toEqual({ id: "rp_1" });
expect(dbMock.rolePermission.deleteMany).toHaveBeenCalledWith({ where: { roleId: "role_1", permissionId: "permission_1" } });
expect(dbMock.rolePermission.delete).toHaveBeenCalledWith({ where: { id: "rp_1" } });
});
});