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

216 lines
8.6 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../db.server", () => ({
db: {
role: {
aggregate: vi.fn(),
create: vi.fn(),
delete: vi.fn(),
findFirst: vi.fn(),
findMany: vi.fn(),
findUnique: vi.fn(),
update: vi.fn(),
},
},
}));
vi.mock("~/utils/cache.server", () => ({ cachified: vi.fn(), clearCacheKey: vi.fn() }));
vi.mock("~/utils/helpers/RowFiltersHelper", () => ({ default: { getFiltersCondition: vi.fn() } }));
import { cachified, clearCacheKey } from "~/utils/cache.server";
import RowFiltersHelper from "~/utils/helpers/RowFiltersHelper";
import { db } from "../../db.server";
import {
createRole,
deleteRole,
getAllRoles,
getAllRolesNames,
getAllRolesWithoutPermissions,
getAllRolesWithUsers,
getNextRolesOrder,
getRole,
getRoleByName,
getRoles,
getRolesByName,
getTenantAppRoleByName,
getTenantAssignableAppRole,
getTenantMutableAppRole,
updateRole,
} from "./roles.db.server";
const dbMock = vi.mocked(db);
describe("role persistence", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(cachified).mockImplementation((async (options: { getFreshValue: () => unknown }) =>
options.getFreshValue()) as never);
});
it("lists global roles with permissions and supports type filtering", async () => {
vi.mocked(dbMock.role.findMany).mockResolvedValue([] as never);
await getAllRoles();
await getAllRoles("admin");
await getAllRolesNames();
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
where: {},
include: { permissions: { include: { permission: true }, orderBy: { permission: { name: "asc" } } } },
orderBy: [{ type: "asc" }, { order: "asc" }],
})
);
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
where: { type: "admin", tenantId: null },
include: { permissions: { include: { permission: true }, orderBy: { permission: { name: "asc" } } } },
})
);
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(3, { select: { id: true, name: true }, orderBy: [{ type: "asc" }, { order: "asc" }] });
});
it("caches role lists without permissions", async () => {
vi.mocked(dbMock.role.findMany).mockResolvedValue([{ id: "role_1" }] as never);
await expect(getAllRolesWithoutPermissions("app")).resolves.toEqual([{ id: "role_1" }]);
await expect(getAllRolesWithoutPermissions()).resolves.toEqual([{ id: "role_1" }]);
expect(cachified).toHaveBeenNthCalledWith(1, expect.objectContaining({ key: "roles:app", ttl: 1000 * 60 * 60 * 24 }));
expect(cachified).toHaveBeenNthCalledWith(2, expect.objectContaining({ key: "roles:undefined", ttl: 1000 * 60 * 60 * 24 }));
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(1, {
where: { type: "app", tenantId: null },
orderBy: [{ type: "asc" }, { order: "asc" }],
});
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(2, {
where: {},
orderBy: [{ type: "asc" }, { order: "asc" }],
});
});
it("lists roles with users using tenant scope and permission filters", async () => {
vi.mocked(RowFiltersHelper.getFiltersCondition).mockReturnValue({ name: { contains: "editor" } } as never);
vi.mocked(dbMock.role.findMany).mockResolvedValueOnce([] as never);
await getAllRolesWithUsers(
"app",
{ properties: [{ name: "permissionId", value: "permission_1" }] } as never,
"tenant_1"
);
expect(dbMock.role.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: {
AND: [
{ type: "app", OR: [{ tenantId: null }, { tenantId: "tenant_1" }] },
{ OR: [{ name: { contains: "editor" } }, { permissions: { some: { permissionId: "permission_1" } } }] },
],
},
include: expect.objectContaining({
permissions: { include: { permission: true }, orderBy: { permission: { name: "asc" } } },
users: expect.objectContaining({ where: { tenantId: "tenant_1" } }),
}),
orderBy: [{ type: "asc" }, { order: "asc" }],
})
);
});
it("lists global roles with users when filters do not target a permission", async () => {
vi.mocked(RowFiltersHelper.getFiltersCondition).mockReturnValue({ name: { contains: "member" } } as never);
vi.mocked(dbMock.role.findMany).mockResolvedValueOnce([] as never);
await getAllRolesWithUsers("app", { properties: [{ name: "name", value: "member" }] } as never);
expect(dbMock.role.findMany).toHaveBeenCalledWith(
expect.objectContaining({
where: {
AND: [{ type: "app", tenantId: null }, { name: { contains: "member" } }],
},
include: expect.objectContaining({
users: expect.not.objectContaining({ where: expect.anything() }),
}),
})
);
});
it("queries role lookup variants with permission and user details", async () => {
vi.mocked(dbMock.role.findMany).mockResolvedValue([] as never);
vi.mocked(dbMock.role.findUnique).mockResolvedValue({ id: "role_1" } as never);
vi.mocked(dbMock.role.findFirst).mockResolvedValue({ id: "role_1" } as never);
await getRoles(["role_1"]);
await getRolesByName(["Admin", "Member"]);
await getRole("role_1");
await getTenantAssignableAppRole("role_1", "tenant_1");
await getTenantMutableAppRole("role_1", "tenant_1");
await getRoleByName("Admin");
await getTenantAppRoleByName("Support", "tenant_1");
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(1, expect.objectContaining({ where: { id: { in: ["role_1"] } } }));
expect(dbMock.role.findMany).toHaveBeenNthCalledWith(2, expect.objectContaining({ where: { name: { in: ["Admin", "Member"] } } }));
expect(dbMock.role.findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "role_1" } }));
expect(dbMock.role.findFirst).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ where: { id: "role_1", type: "app", OR: [{ isDefault: true, tenantId: null }, { tenantId: "tenant_1" }] } })
);
expect(dbMock.role.findFirst).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ where: { id: "role_1", type: "app", isDefault: false, tenantId: "tenant_1" } })
);
expect(dbMock.role.findFirst).toHaveBeenNthCalledWith(3, expect.objectContaining({ where: { name: "Admin", tenantId: null } }));
expect(dbMock.role.findFirst).toHaveBeenNthCalledWith(4, expect.objectContaining({ where: { name: "Support", tenantId: "tenant_1", type: "app" } }));
});
it("calculates next role order for global and tenant-scoped roles", async () => {
vi.mocked(dbMock.role.aggregate)
.mockResolvedValueOnce({ _max: { order: 4 } } as never)
.mockResolvedValueOnce({ _max: { order: null } } as never);
await expect(getNextRolesOrder("app", "tenant_1")).resolves.toBe(5);
await expect(getNextRolesOrder()).resolves.toBe(1);
expect(dbMock.role.aggregate).toHaveBeenNthCalledWith(1, { where: { type: "app", tenantId: "tenant_1" }, _max: { order: true } });
expect(dbMock.role.aggregate).toHaveBeenNthCalledWith(2, { where: {}, _max: { order: true } });
});
it("clears role-list caches after create, update, and delete mutations", async () => {
vi.mocked(dbMock.role.create).mockResolvedValueOnce({ id: "role_1", type: "app" } as never);
vi.mocked(dbMock.role.update).mockResolvedValueOnce({ id: "role_1", type: "admin" } as never);
vi.mocked(dbMock.role.delete).mockResolvedValueOnce({ id: "role_1", type: "app" } as never);
await createRole({
order: 1,
name: "Support",
description: "Support role",
type: "app",
assignToNewUsers: false,
isDefault: false,
tenantId: "tenant_1",
});
await updateRole("role_1", { name: "Admin", description: "Admin role", type: "admin", assignToNewUsers: true });
await deleteRole("role_1");
expect(dbMock.role.create).toHaveBeenCalledWith({
data: {
order: 1,
name: "Support",
description: "Support role",
type: "app",
assignToNewUsers: false,
isDefault: false,
tenantId: "tenant_1",
},
});
expect(dbMock.role.update).toHaveBeenCalledWith({
where: { id: "role_1" },
data: { name: "Admin", description: "Admin role", type: "admin", assignToNewUsers: true },
});
expect(dbMock.role.delete).toHaveBeenCalledWith({ where: { id: "role_1" } });
expect(clearCacheKey).toHaveBeenNthCalledWith(1, "roles:app");
expect(clearCacheKey).toHaveBeenNthCalledWith(2, "roles:admin");
expect(clearCacheKey).toHaveBeenNthCalledWith(3, "roles:app");
});
});