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

208 lines
9.5 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../db.server", () => ({
db: {
permission: {
aggregate: vi.fn(),
delete: vi.fn(),
deleteMany: vi.fn(),
findMany: vi.fn(),
findUnique: vi.fn(),
create: vi.fn(),
update: vi.fn(),
},
},
}));
vi.mock("./roles.db.server", () => ({ getAllRolesNames: vi.fn(), getRoleByName: vi.fn() }));
vi.mock("./rolePermissions.db.server", () => ({ createRolePermission: vi.fn(), getAllRolePermissions: vi.fn() }));
vi.mock("~/utils/cache.server", () => ({ cachified: vi.fn(), clearCacheKey: vi.fn() }));
vi.mock("~/utils/helpers/PermissionsHelper", () => ({ getEntityPermissions: vi.fn() }));
vi.mock("~/utils/helpers/RowFiltersHelper", () => ({ default: { getFiltersCondition: vi.fn() } }));
import { cachified, clearCacheKey } from "~/utils/cache.server";
import { getEntityPermissions } from "~/utils/helpers/PermissionsHelper";
import RowFiltersHelper from "~/utils/helpers/RowFiltersHelper";
import { db } from "../../db.server";
import { createRolePermission, getAllRolePermissions } from "./rolePermissions.db.server";
import { getAllRolesNames, getRoleByName } from "./roles.db.server";
import {
createEntityPermissions,
createPermissions,
deleteEntityPermissions,
deletePermission,
getAllPermissions,
getAllPermissionsIdsAndNames,
getNextPermissionsOrder,
getPermission,
getPermissionByName,
getPermissionByNames,
getPermissionName,
updatePermission,
} from "./permissions.db.server";
describe("permission persistence", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(cachified).mockImplementation((async (options: { getFreshValue: () => unknown }) =>
options.getFreshValue()) as never);
});
it("lists permissions with filters, role filters, and type constraints", async () => {
vi.mocked(RowFiltersHelper.getFiltersCondition).mockReturnValue({ name: { contains: "admin" } } as never);
vi.mocked(db.permission.findMany).mockResolvedValueOnce([] as never);
await getAllPermissions("admin", {
properties: [{ name: "roleId", value: "role_1" }],
} as never);
expect(db.permission.findMany).toHaveBeenCalledWith({
where: {
AND: ["admin", { OR: [{ name: { contains: "admin" } }, { inRoles: { some: { roleId: "role_1" } } }] }],
},
include: { inRoles: { include: { role: true } } },
orderBy: [{ type: "asc" }, { name: "asc" }],
});
});
it("queries permission lookup variants and cached permission names", async () => {
vi.mocked(db.permission.findMany).mockResolvedValue([] as never);
vi.mocked(db.permission.findUnique).mockResolvedValue({ id: "permission_1" } as never);
await getAllPermissionsIdsAndNames();
await getPermission("permission_1");
await getPermissionByName("admin.users.view");
await getPermissionName("admin.users.view");
await getPermissionByNames(["admin.users.view", "admin.users.edit"]);
expect(db.permission.findMany).toHaveBeenNthCalledWith(1, {
select: { id: true, name: true },
orderBy: { name: "asc" },
});
expect(db.permission.findUnique).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ where: { id: "permission_1" } })
);
expect(db.permission.findUnique).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ where: { name: "admin.users.view" } })
);
expect(cachified).toHaveBeenCalledWith(expect.objectContaining({ key: "permission:admin.users.view" }));
expect(db.permission.findMany).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ where: { name: { in: ["admin.users.view", "admin.users.edit"] } } })
);
});
it("calculates the next permission order with and without a type filter", async () => {
vi.mocked(db.permission.aggregate)
.mockResolvedValueOnce({ _max: { order: 4 } } as never)
.mockResolvedValueOnce({ _max: { order: null } } as never);
await expect(getNextPermissionsOrder("admin")).resolves.toBe(5);
await expect(getNextPermissionsOrder()).resolves.toBe(1);
expect(db.permission.aggregate).toHaveBeenNthCalledWith(1, { where: { type: "admin" }, _max: { order: true } });
expect(db.permission.aggregate).toHaveBeenNthCalledWith(2, { where: {}, _max: { order: true } });
});
it("repairs role links for existing permissions instead of returning early", async () => {
vi.mocked(db.permission.findMany).mockResolvedValue([{ id: "permission_1", name: "admin.users.view" }] as never);
vi.mocked(getAllRolePermissions).mockResolvedValue([]);
vi.mocked(getRoleByName).mockResolvedValue({ id: "role_1", name: "SuperAdmin" } as never);
vi.mocked(createRolePermission).mockResolvedValue({ id: "role_permission_1" } as never);
await createPermissions([{ name: "admin.users.view", description: "View users", type: "admin", inRoles: ["SuperAdmin"] }]);
expect(db.permission.create).not.toHaveBeenCalled();
expect(createRolePermission).toHaveBeenCalledWith({ permissionId: "permission_1", roleId: "role_1" });
});
it("clears the permission lookup cache after creating a previously missing permission", async () => {
vi.mocked(db.permission.findMany).mockResolvedValue([]);
vi.mocked(db.permission.create).mockResolvedValue({ id: "permission_1", name: "admin.users.view" } as never);
vi.mocked(getAllRolePermissions).mockResolvedValue([]);
vi.mocked(getRoleByName).mockResolvedValue({ id: "role_1", name: "SuperAdmin" } as never);
vi.mocked(createRolePermission).mockResolvedValue({ id: "role_permission_1" } as never);
await createPermissions([{ name: "admin.users.view", description: "View users", type: "admin", inRoles: ["SuperAdmin"] }]);
expect(clearCacheKey).toHaveBeenCalledWith("permission:admin.users.view");
expect(createRolePermission).toHaveBeenCalledWith({ permissionId: "permission_1", roleId: "role_1" });
});
it("skips duplicate permission inputs after creating the first instance", async () => {
vi.mocked(db.permission.findMany).mockResolvedValue([]);
vi.mocked(db.permission.create).mockResolvedValue({ id: "permission_1", name: "admin.users.view" } as never);
vi.mocked(getAllRolePermissions).mockResolvedValue([]);
vi.mocked(getRoleByName).mockResolvedValue({ id: "role_1", name: "SuperAdmin" } as never);
await createPermissions([
{ name: "admin.users.view", description: "View users", type: "admin", inRoles: ["SuperAdmin"] },
{ name: "admin.users.view", description: "View users", type: "admin", inRoles: ["SuperAdmin"] },
]);
expect(db.permission.create).toHaveBeenCalledTimes(1);
});
it("throws when a requested role link cannot be resolved", async () => {
vi.mocked(db.permission.findMany).mockResolvedValue([]);
vi.mocked(db.permission.create).mockResolvedValue({ id: "permission_1", name: "admin.users.view" } as never);
vi.mocked(getAllRolePermissions).mockResolvedValue([]);
vi.mocked(getRoleByName).mockResolvedValue(null);
await expect(
createPermissions([{ name: "admin.users.view", description: "View users", type: "admin", inRoles: ["Missing"] }])
).rejects.toThrow("Role required: Missing");
});
it("updates and deletes permissions while clearing the permission cache", async () => {
vi.mocked(db.permission.update).mockResolvedValueOnce({ id: "permission_1", name: "admin.users.view" } as never);
vi.mocked(db.permission.delete).mockResolvedValueOnce({ id: "permission_1", name: "admin.users.delete" } as never);
await updatePermission("permission_1", { name: "admin.users.view" });
await deletePermission("permission_1");
expect(clearCacheKey).toHaveBeenCalledWith("permission:admin.users.view");
expect(clearCacheKey).toHaveBeenCalledWith("permission:admin.users.delete");
});
it("deletes entity permissions only when generated permission names exist", async () => {
vi.mocked(getEntityPermissions)
.mockResolvedValueOnce([{ name: "entity.view" }, { name: "entity.edit" }] as never)
.mockResolvedValueOnce([] as never);
vi.mocked(db.permission.deleteMany).mockResolvedValueOnce({ count: 2 } as never);
await expect(deleteEntityPermissions({ id: "entity_1" } as never)).resolves.toEqual({ count: 2 });
await expect(deleteEntityPermissions({ id: "entity_1" } as never)).resolves.toBeUndefined();
expect(db.permission.deleteMany).toHaveBeenCalledTimes(1);
expect(db.permission.deleteMany).toHaveBeenCalledWith({
where: { name: { in: ["entity.view", "entity.edit"] } },
});
});
it("creates entity permissions for every generated permission and all app roles", async () => {
vi.mocked(getAllRolesNames).mockResolvedValue([{ id: "role_1", name: "Member" }] as never);
vi.mocked(getEntityPermissions).mockResolvedValue([{ name: "entity.view", description: "View entity" }] as never);
vi.mocked(getAllRolePermissions).mockResolvedValue([]);
vi.mocked(db.permission.findMany).mockResolvedValue([]);
vi.mocked(db.permission.create).mockResolvedValue({ id: "permission_1", name: "entity.view" } as never);
vi.mocked(getRoleByName).mockResolvedValue({ id: "role_1", name: "Member" } as never);
await createEntityPermissions({ id: "entity_1" } as never);
expect(db.permission.create).toHaveBeenCalledWith({
data: {
order: 2,
name: "entity.view",
description: "View entity",
type: "app",
isDefault: true,
entityId: "entity_1",
},
});
expect(createRolePermission).toHaveBeenCalledWith({ permissionId: "permission_1", roleId: "role_1" });
});
});