216 lines
8.6 KiB
TypeScript
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");
|
|
});
|
|
});
|