157 lines
5.6 KiB
TypeScript
157 lines
5.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
createService: vi.fn(() => ({ marker: "service" })),
|
|
getPermissions: vi.fn<() => Promise<string[]>>(),
|
|
getUserInfo: vi.fn(),
|
|
rlsContexts: [] as { userId?: string; tenantId?: string }[],
|
|
operatorFind: vi.fn(),
|
|
operatorList: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/platform-leads-admin.server", () => ({
|
|
createPrismaPlatformLeadService: mocks.createService,
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/rls-context.server", () => ({
|
|
runWithRlsContext: async <T>(context: { userId?: string; tenantId?: string }, callback: () => Promise<T>) => {
|
|
mocks.rlsContexts.push(context);
|
|
return callback();
|
|
},
|
|
}));
|
|
|
|
vi.mock("~/utils/db.server", () => ({
|
|
db: {
|
|
adminUser: {
|
|
findUnique: mocks.operatorFind,
|
|
findMany: mocks.operatorList,
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@hivegrid/db/admin-rbac.server", () => ({ listAdminPermissionNames: mocks.getPermissions }));
|
|
vi.mock("~/utils/session.server", () => ({ getUserInfo: mocks.getUserInfo }));
|
|
vi.mock("~/utils/audit/withAuditContext", () => ({
|
|
extractWebActorIp: () => "203.0.113.8",
|
|
extractWebActorUserAgent: () => "test-agent",
|
|
extractWebRequestId: () => "request_1",
|
|
}));
|
|
|
|
import {
|
|
assertPlatformLeadVisibleToOperator,
|
|
listPlatformLeadAssignees,
|
|
requirePlatformLeadAdmin,
|
|
requirePlatformLeadRead,
|
|
requirePlatformLeadWrite,
|
|
runAsPlatformLeadOperator,
|
|
} from "./platform-leads.server";
|
|
|
|
async function expectNotFound(work: () => unknown | Promise<unknown>) {
|
|
try {
|
|
await work();
|
|
throw new Error("Expected a 404 Response");
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(Response);
|
|
expect((error as Response).status).toBe(404);
|
|
}
|
|
}
|
|
|
|
describe("platform lead admin boundary", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.rlsContexts.length = 0;
|
|
mocks.getUserInfo.mockResolvedValue({ userId: "operator_1" });
|
|
mocks.operatorFind.mockResolvedValue({ id: "operator_1", email: "operator@example.test", status: "active" });
|
|
mocks.getPermissions.mockResolvedValue([
|
|
"admin_sales.crm.read",
|
|
"admin_sales.crm.read_all",
|
|
"admin_sales.crm.write",
|
|
"admin_sales.crm.write_all",
|
|
"admin_sales.crm.assign",
|
|
"admin_sales.communication.read_all",
|
|
]);
|
|
});
|
|
|
|
it("returns 404 before touching the database for an unauthenticated request", async () => {
|
|
mocks.getUserInfo.mockResolvedValue({ userId: null });
|
|
await expectNotFound(() => runAsPlatformLeadOperator(new Request("https://admin.example.test/leads"), async () => null));
|
|
expect(mocks.operatorFind).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("revalidates AdminUser and keeps the whole callback inside user-bound RLS", async () => {
|
|
const result = await runAsPlatformLeadOperator(new Request("https://admin.example.test/leads"), async ({ access, service }) => {
|
|
expect(service).toEqual({ marker: "service" });
|
|
expect(access).toMatchObject({
|
|
actorAdminUserId: "operator_1",
|
|
canReadOwn: true,
|
|
canReadAll: true,
|
|
canWrite: true,
|
|
canAdmin: true,
|
|
});
|
|
expect(access.audit("A useful audit reason")).toEqual({
|
|
actorAdminUserId: "operator_1",
|
|
actorEmail: "operator@example.test",
|
|
actorIp: "203.0.113.8",
|
|
actorUserAgent: "test-agent",
|
|
requestId: "request_1",
|
|
reason: "A useful audit reason",
|
|
});
|
|
return "ok";
|
|
});
|
|
|
|
expect(result).toBe("ok");
|
|
expect(mocks.rlsContexts).toEqual([{ userId: "operator_1" }]);
|
|
expect(mocks.getPermissions).toHaveBeenCalledWith("operator_1");
|
|
expect(mocks.createService).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
{
|
|
adminUserId: "operator_1",
|
|
canReadAll: true,
|
|
canWriteAll: true,
|
|
canReadAllCommunication: true,
|
|
}
|
|
);
|
|
});
|
|
|
|
it("hides the surface when the bound user is not a platform operator", async () => {
|
|
mocks.operatorFind.mockResolvedValue(null);
|
|
await expectNotFound(() => runAsPlatformLeadOperator(new Request("https://admin.example.test/leads"), async () => null));
|
|
});
|
|
|
|
it("enforces read, write, admin and read-own visibility as 404", async () => {
|
|
const base = {
|
|
actorAdminUserId: "operator_1",
|
|
actorEmail: "operator@example.test",
|
|
canReadOwn: false,
|
|
canReadAll: false,
|
|
canWrite: false,
|
|
canAdmin: false,
|
|
audit: vi.fn(),
|
|
};
|
|
await expectNotFound(() => requirePlatformLeadRead(base));
|
|
await expectNotFound(() => requirePlatformLeadWrite({ ...base, canReadOwn: true }));
|
|
await expectNotFound(() => requirePlatformLeadAdmin({ ...base, canReadOwn: true }));
|
|
await expectNotFound(() => assertPlatformLeadVisibleToOperator({ assignedToAdminUserId: "operator_2" }, { ...base, canReadOwn: true }));
|
|
expect(() => assertPlatformLeadVisibleToOperator({ assignedToAdminUserId: "operator_2" }, { ...base, canReadAll: true })).not.toThrow();
|
|
});
|
|
|
|
it("exposes only operator ids and safe display labels to the VM layer", async () => {
|
|
mocks.operatorList.mockResolvedValue([
|
|
{ id: "operator_1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test" },
|
|
{ id: "operator_2", firstName: "", lastName: "", email: "fallback@example.test" },
|
|
]);
|
|
await expect(listPlatformLeadAssignees()).resolves.toEqual([
|
|
{ id: "operator_1", label: "Ada Lovelace" },
|
|
{ id: "operator_2", label: "fallback@example.test" },
|
|
]);
|
|
expect(mocks.operatorList).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: expect.objectContaining({
|
|
status: "active",
|
|
roles: expect.objectContaining({ some: expect.any(Object) }),
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
});
|