@cerbos/orm-prisma
Version:
Prisma adapter for Cerbos query plans
1,955 lines (1,765 loc) • 130 kB
text/typescript
import { queryPlanToPrisma, PlanKind, QueryPlanToPrismaResult } from ".";
import {
beforeAll,
beforeEach,
afterEach,
describe,
test,
expect,
} from "@jest/globals";
import {
PlanExpression,
PlanExpressionOperand,
PlanResourcesConditionalResponse,
PlanResourcesResponse,
} from "@cerbos/core";
import { Prisma, PrismaClient } from "@prisma/client";
import { GRPC as Cerbos } from "@cerbos/grpc";
const prisma = new PrismaClient();
const cerbos = new Cerbos("127.0.0.1:3593", { tls: false });
function createConditionalPlan(
condition: PlanExpressionOperand
): PlanResourcesConditionalResponse {
return {
kind: PlanKind.CONDITIONAL,
condition,
cerbosCallId: "",
requestId: "",
validationErrors: [],
metadata: undefined,
};
}
function getExpressionOperand(
expression: PlanExpression,
index: number
): PlanExpressionOperand {
const operand = expression.operands[index];
if (!operand) {
throw new Error(`Missing operand at index ${index}`);
}
return operand;
}
const fixtureUsers: Prisma.UserCreateInput[] = [
{
id: "user1",
aBool: true,
aNumber: 1,
aString: "string",
},
{
id: "user2",
aBool: true,
aNumber: 2,
aString: "string",
},
];
const fixtureNextLevelResources: Prisma.NextLevelNestedResourceCreateInput[] = [
{
id: "nextLevel1",
aBool: true,
aNumber: 1,
aString: "string",
},
{
id: "nextLevel2",
aBool: false,
aNumber: 1,
aString: "string",
},
{
id: "nextLevel3",
aBool: true,
aNumber: 1,
aString: "string",
},
];
const fixtureTags: Prisma.TagCreateInput[] = [
{
id: "tag1",
name: "public",
},
{
id: "tag2",
name: "private",
},
{
id: "tag3",
name: "draft",
},
];
const fixtureNestedResources: Prisma.NestedResourceCreateInput[] = [
{
id: "nested1",
aBool: true,
aNumber: 1,
aString: "string",
nextlevel: {
connect: {
id: "nextLevel1",
},
},
},
{
id: "nested2",
aBool: false,
aNumber: 1,
aString: "string",
nextlevel: {
connect: {
id: "nextLevel2",
},
},
},
{
id: "nested3",
aBool: true,
aNumber: 1,
aString: "string",
nextlevel: {
connect: {
id: "nextLevel3",
},
},
},
];
const fixtureLabels: Prisma.LabelCreateInput[] = [
{ id: "label1", name: "important" },
{ id: "label2", name: "archived" },
{ id: "label3", name: "flagged" },
];
const fixtureSubCategories: Prisma.SubCategoryCreateInput[] = [
{
id: "sub1",
name: "finance",
labels: {
connect: [{ id: "label1" }, { id: "label2" }],
},
},
{
id: "sub2",
name: "tech",
labels: {
connect: [{ id: "label2" }, { id: "label3" }],
},
},
];
const fixtureCategories: Prisma.CategoryCreateInput[] = [
{
id: "cat1",
name: "business",
subCategories: {
connect: [{ id: "sub1" }],
},
},
{
id: "cat2",
name: "development",
subCategories: {
connect: [{ id: "sub2" }],
},
},
];
const fixtureResources: Prisma.ResourceCreateInput[] = [
{
id: "resource1",
aBool: true,
aNumber: 1,
aString: "string",
aOptionalString: "optionalString",
createdBy: {
connect: {
id: "user1",
},
},
ownedBy: {
connect: [
{
id: "user1",
},
],
},
nested: {
connect: {
id: "nested1",
},
},
tags: {
connect: [
{ id: "tag1" }, // public
],
},
categories: {
connect: [{ id: "cat1" }],
},
},
{
id: "resource2",
aBool: false,
aNumber: 2,
aString: "string2",
createdBy: {
connect: {
id: "user2",
},
},
ownedBy: {
connect: [
{
id: "user2",
},
],
},
nested: {
connect: {
id: "nested3",
},
},
tags: {
connect: [
{ id: "tag2" }, // private
],
},
categories: {
connect: [{ id: "cat2" }],
},
},
{
id: "resource3",
aBool: false,
aNumber: 3,
aString: "string3",
createdBy: {
connect: {
id: "user1",
},
},
ownedBy: {
connect: [
{
id: "user1",
},
{
id: "user2",
},
],
},
nested: {
connect: {
id: "nested3",
},
},
tags: {
connect: [
{ id: "tag1" }, // public
{ id: "tag3" }, // draft
],
},
categories: {
connect: [{ id: "cat1" }, { id: "cat2" }],
},
},
];
beforeAll(async () => {
await prisma.resource.deleteMany();
await prisma.nextLevelNestedResource.deleteMany();
await prisma.nestedResource.deleteMany();
await prisma.tag.deleteMany();
await prisma.user.deleteMany();
});
beforeEach(async () => {
for (const tag of fixtureTags) {
await prisma.tag.create({ data: tag });
}
for (const user of fixtureUsers) {
await prisma.user.create({ data: user });
}
for (const resource of fixtureNextLevelResources) {
await prisma.nextLevelNestedResource.create({ data: resource });
}
for (const resource of fixtureNestedResources) {
await prisma.nestedResource.create({ data: resource });
}
for (const label of fixtureLabels) {
await prisma.label.create({ data: label });
}
for (const subCategory of fixtureSubCategories) {
await prisma.subCategory.create({ data: subCategory });
}
for (const category of fixtureCategories) {
await prisma.category.create({ data: category });
}
for (const resource of fixtureResources) {
await prisma.resource.create({ data: resource });
}
});
afterEach(async () => {
await prisma.resource.deleteMany();
await prisma.nestedResource.deleteMany();
await prisma.nextLevelNestedResource.deleteMany();
await prisma.tag.deleteMany();
await prisma.user.deleteMany();
await prisma.category.deleteMany();
await prisma.subCategory.deleteMany();
await prisma.label.deleteMany();
});
// Core Functionality
describe("Basic Plan Types", () => {
test("always allowed", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "always-allow",
});
expect(queryPlan.kind).toEqual(PlanKind.ALWAYS_ALLOWED);
const result = queryPlanToPrisma({
queryPlan,
});
expect(result).toStrictEqual({
kind: PlanKind.ALWAYS_ALLOWED,
});
const query = await prisma.resource.findMany({});
expect(query.length).toEqual(fixtureResources.length);
});
test("always denied", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "always-deny",
});
expect(queryPlan.kind).toEqual(PlanKind.ALWAYS_DENIED);
const result = queryPlanToPrisma({
queryPlan,
mapper: {},
});
expect(result).toEqual({
kind: PlanKind.ALWAYS_DENIED,
});
});
});
// Field Operations
describe("Field Operations", () => {
describe("Basic Field Tests", () => {
test("conditional - eq", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "equal",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "eq",
operands: [{ name: "request.resource.attr.aBool" }, { value: true }],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aBool": { field: "aBool" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aBool: { equals: true } },
});
const query = await prisma.resource.findMany({
where:
result.kind === PlanKind.CONDITIONAL ? { ...result.filters } : {},
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources.filter((a) => a.aBool).map((r) => r.id)
);
});
test("conditional - eq - inverted order", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "equal",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "eq",
operands: [{ name: "request.resource.attr.aBool" }, { value: true }],
}
);
const typeQp = queryPlan as PlanResourcesConditionalResponse;
const invertedQueryPlan: PlanResourcesConditionalResponse = {
...typeQp,
condition: {
...typeQp.condition,
operands: [
getExpressionOperand(typeQp.condition as PlanExpression, 1),
getExpressionOperand(typeQp.condition as PlanExpression, 0),
],
},
};
const result = queryPlanToPrisma({
queryPlan: invertedQueryPlan,
mapper: {
"request.resource.attr.aBool": { field: "aBool" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aBool: { equals: true } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources.filter((a) => a.aBool).map((r) => r.id)
);
});
test("conditional - ne", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "ne",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "ne",
operands: [
{ name: "request.resource.attr.aString" },
{ value: "string" },
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aString: { not: "string" } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources.filter((a) => a.aString != "string").map((r) => r.id)
);
});
test("conditional - explicit-deny", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "explicit-deny",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "not",
operands: [
{
operator: "eq",
operands: [
{ name: "request.resource.attr.aBool" },
{ value: true },
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aBool": { field: "aBool" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { NOT: { aBool: { equals: true } } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources.filter((a) => !a.aBool).map((r) => r.id)
);
});
});
describe("Comparison Tests", () => {
test("conditional - gt", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "gt",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "gt",
operands: [{ name: "request.resource.attr.aNumber" }, { value: 1 }],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aNumber": { field: "aNumber" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
aNumber: { gt: 1 },
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
return r.aNumber > 1;
})
.map((r) => r.id)
);
});
test("conditional - lt", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "lt",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "lt",
operands: [{ name: "request.resource.attr.aNumber" }, { value: 2 }],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aNumber": { field: "aNumber" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
aNumber: { lt: 2 },
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
return r.aNumber < 2;
})
.map((r) => r.id)
);
});
test("conditional - gte", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "gte",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "ge",
operands: [{ name: "request.resource.attr.aNumber" }, { value: 1 }],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aNumber": { field: "aNumber" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
aNumber: { gte: 1 },
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
return r.aNumber >= 1;
})
.map((r) => r.id)
);
});
test("conditional - lte", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "lte",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "le",
operands: [{ name: "request.resource.attr.aNumber" }, { value: 2 }],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aNumber": { field: "aNumber" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
aNumber: { lte: 2 },
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
return r.aNumber <= 2;
})
.map((r) => r.id)
);
});
});
describe("String Tests", () => {
test("conditional - contains", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "contains",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operands: [
{
name: "request.resource.attr.aString",
},
{
value: "str",
},
],
operator: "contains",
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aString: { contains: "str" } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((a) => a.aString.includes("str"))
.map((r) => r.id)
);
});
test("conditional - startsWith", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "starts-with",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operands: [
{
name: "request.resource.attr.aString",
},
{
value: "str",
},
],
operator: "startsWith",
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aString: { startsWith: "str" } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((a) => a.aString.startsWith("str"))
.map((r) => r.id)
);
});
test("conditional - endsWith", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "ends-with",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operands: [
{
name: "request.resource.attr.aString",
},
{
value: "ing",
},
],
operator: "endsWith",
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aString: { endsWith: "ing" } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((a) => a.aString.endsWith("ing"))
.map((r) => r.id)
);
});
test("conditional - isSet", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "is-set",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operands: [
{
name: "request.resource.attr.aOptionalString",
},
{
value: null,
},
],
operator: "ne",
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aOptionalString": { field: "aOptionalString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aOptionalString: { not: null } },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources.filter((a) => a.aOptionalString).map((r) => r.id)
);
});
});
});
// Collection Operations
describe("Collection Operations", () => {
describe("Basic Collections", () => {
test("conditional - in", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "in",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "in",
operands: [
{ name: "request.resource.attr.aString" },
{ value: ["string", "anotherString"] },
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
aString: { in: ["string", "anotherString"] },
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
return ["string", "anotherString"].includes(r.aString);
})
.map((r) => r.id)
);
});
test("conditional - in - scalar value", async () => {
const queryPlan = createConditionalPlan({
operator: "in",
operands: [
{ name: "request.resource.attr.aString" },
{ value: "string" },
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.aString": { field: "aString" },
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: { aString: "string" },
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id).sort()).toEqual(
fixtureResources
.filter((r) => r.aString === "string")
.map((r) => r.id)
.sort()
);
});
test("conditional - relation in", async () => {
const queryPlan = createConditionalPlan({
operator: "in",
operands: [
{ name: "request.resource.attr.categories.name" },
{ value: ["business"] },
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.categories": {
relation: {
name: "categories",
type: "many",
fields: {
name: { field: "name" },
},
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
categories: {
some: {
name: "business",
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id).sort()).toEqual(
fixtureResources
.filter((resource) => {
const categoryRefs =
(resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ??
[];
return categoryRefs.some((categoryRef) => {
const category = fixtureCategories.find(
(fc) => fc.id === categoryRef.id
);
return category?.name === "business";
});
})
.map((r) => r.id)
.sort()
);
});
test("conditional - relation in multiple values", async () => {
const queryPlan = createConditionalPlan({
operator: "in",
operands: [
{ name: "request.resource.attr.categories.name" },
{ value: ["business", "development"] },
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.categories": {
relation: {
name: "categories",
type: "many",
fields: {
name: { field: "name" },
},
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
categories: {
some: {
name: { in: ["business", "development"] },
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id).sort()).toEqual(
fixtureResources
.filter((resource) => {
const categoryRefs =
(resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ??
[];
return categoryRefs.some((categoryRef) => {
const category = fixtureCategories.find(
(fc) => fc.id === categoryRef.id
);
return ["business", "development"].includes(category?.name ?? "");
});
})
.map((r) => r.id)
.sort()
);
});
test("conditional - except relation subset", async () => {
const queryPlan = createConditionalPlan({
operator: "except",
operands: [
{ name: "request.resource.attr.categories" },
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{ name: "cat.name" },
{ value: "business" },
],
},
{ name: "cat" },
],
},
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.categories": {
relation: {
name: "categories",
type: "many",
fields: {
name: { field: "name" },
},
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
categories: {
some: {
NOT: {
name: { equals: "business" },
},
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id).sort()).toEqual(
fixtureResources
.filter((resource) => {
const categoryRefs =
(resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ??
[];
return categoryRefs.some((categoryRef) => {
const category = fixtureCategories.find(
(fc) => fc.id === categoryRef.id
);
return category?.name !== "business";
});
})
.map((r) => r.id)
.sort()
);
});
test("conditional - except nested relation subset", async () => {
const queryPlan = createConditionalPlan({
operator: "except",
operands: [
{ name: "request.resource.attr.categories" },
{
operator: "lambda",
operands: [
{
operator: "exists",
operands: [
{ name: "cat.subCategories" },
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{ name: "sub.name" },
{ value: "finance" },
],
},
{ name: "sub" },
],
},
],
},
{ name: "cat" },
],
},
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.categories": {
relation: {
name: "categories",
type: "many",
fields: {
subCategories: {
relation: {
name: "subCategories",
type: "many",
fields: {
name: { field: "name" },
},
},
},
},
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
categories: {
some: {
NOT: {
subCategories: {
some: {
name: { equals: "finance" },
},
},
},
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id).sort()).toEqual(
fixtureResources
.filter((resource) => {
const categoryRefs =
(resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ??
[];
return categoryRefs.some((categoryRef) => {
const category = fixtureCategories.find(
(fc) => fc.id === categoryRef.id
);
if (!category) {
return false;
}
const subCategoryRefs =
(category.subCategories?.connect as Prisma.SubCategoryWhereUniqueInput[]) ??
[];
return !subCategoryRefs.some((subCategoryRef) => {
const subCategory = fixtureSubCategories.find(
(fsc) => fsc.id === subCategoryRef.id
);
return subCategory?.name === "finance";
});
});
})
.map((r) => r.id)
.sort()
);
});
test("conditional - exists single", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "exists-single",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "exists",
operands: [
{
name: "request.resource.attr.tags",
},
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{
name: "tag.id",
},
{
value: "tag1",
},
],
},
{
name: "tag",
},
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
some: {
id: {
equals: "tag1",
},
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter(
(a) =>
Array.isArray(a.tags?.connect) &&
a.tags?.connect
.map((t) => {
return fixtureTags.find((f) => f.id === t.id);
})
.filter((t) => t?.id == "tag1").length > 0
)
.map((r) => r.id)
);
});
test("conditional - exists multiple", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "exists-multiple",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "exists",
operands: [
{
name: "request.resource.attr.tags",
},
{
operator: "lambda",
operands: [
{
operator: "and",
operands: [
{
operator: "eq",
operands: [
{
name: "tag.id",
},
{
value: "tag1",
},
],
},
{
operator: "eq",
operands: [
{
name: "tag.name",
},
{
value: "public",
},
],
},
],
},
{
name: "tag",
},
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
some: {
AND: [
{
id: {
equals: "tag1",
},
},
{
name: {
equals: "public",
},
},
],
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter(
(a) =>
Array.isArray(a.tags?.connect) &&
a.tags?.connect
.map((t) => {
return fixtureTags.find((f) => f.id === t.id);
})
.filter((t) => t?.id === "tag1" && t?.name === "public")
.length > 0
)
.map((r) => r.id)
);
});
test("conditional - exists_one", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "exists-one",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "exists_one",
operands: [
{
name: "request.resource.attr.tags",
},
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{
name: "tag.name",
},
{
value: "public",
},
],
},
{
name: "tag",
},
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
some: {
name: { equals: "public" },
},
},
AND: [
{
tags: {
every: {
OR: [
{ name: { equals: "public" } },
{ NOT: { name: { equals: "public" } } },
],
},
},
},
],
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter(
(a) =>
Array.isArray(a.tags?.connect) &&
a.tags?.connect
.map((t) => {
return fixtureTags.find((f) => f.id === t.id);
})
.filter((t) => t?.id === "tag1" && t?.name === "public")
.length > 0
)
.map((r) => r.id)
);
});
test("conditional - all", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "all",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "all",
operands: [
{
name: "request.resource.attr.tags",
},
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{
name: "tag.name",
},
{
value: "public",
},
],
},
{
name: "tag",
},
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
every: {
name: { equals: "public" },
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter(
(a) =>
Array.isArray(a.tags?.connect) &&
a.tags?.connect
.map((t) => {
return fixtureTags.find((f) => f.id === t.id);
})
.every((t) => t?.name === "public")
)
.map((r) => r.id)
);
});
test("conditional - filter", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "filter",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "filter",
operands: [
{
name: "request.resource.attr.tags",
},
{
operator: "lambda",
operands: [
{
operator: "eq",
operands: [
{
name: "tag.name",
},
{
value: "public",
},
],
},
{
name: "tag",
},
],
},
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
some: {
name: { equals: "public" },
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter(
(a) =>
Array.isArray(a.tags?.connect) &&
a.tags?.connect
.map((t) => {
return fixtureTags.find((f) => f.id === t.id);
})
.filter((t) => t?.name === "public").length > 0
)
.map((r) => r.id)
);
});
test("conditional - map collection", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "map-collection",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual(
{
operator: "hasIntersection",
operands: [
{
operator: "map",
operands: [
{ name: "request.resource.attr.tags" },
{
operator: "lambda",
operands: [{ name: "tag.name" }, { name: "tag" }],
},
],
},
{ value: ["public", "private"] },
],
}
);
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
fields: {
name: { field: "name" },
},
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
tags: {
some: {
name: { in: ["public", "private"] },
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
// Should return resources that have either "public" or "private" tags
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
const tagNames = Array.isArray(r.tags?.connect)
? r.tags.connect.map(
(t) => fixtureTags.find((ft) => ft.id === t.id)?.name
) || []
: [];
return tagNames.some((name) =>
["public", "private"].includes(name || "")
);
})
.map((r) => r.id)
);
});
});
});
// Relations
describe("Relations", () => {
describe("Simple Relations", () => {
describe("One-to-One", () => {
test("conditional - relation is", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "relation-is",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
const conditions = (queryPlan as PlanResourcesConditionalResponse)
.condition;
expect(conditions).toEqual({
operator: "eq",
operands: [
{ name: "request.resource.attr.createdBy" },
{ value: "user1" },
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.resource.attr.createdBy": {
relation: {
name: "createdBy",
type: "one",
field: "id",
},
},
},
});
expect(result).toStrictEqual({
kind: PlanKind.CONDITIONAL,
filters: {
createdBy: {
is: {
id: {
equals: "user1",
},
},
},
},
});
if (result.kind !== PlanKind.CONDITIONAL) {
throw new Error("Expected CONDITIONAL result");
}
const query = await prisma.resource.findMany({
where: { ...result.filters },
});
expect(query.map((r) => r.id)).toEqual(
fixtureResources
.filter((r) => {
if (!r.createdBy?.connect) return false;
return (r.createdBy.connect as { id: string }).id == "user1";
})
.map((r) => r.id)
);
});
test("conditional - relation is not", async () => {
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "resource" },
action: "relation-is-not",
});
expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL);
const conditions = (queryPlan as PlanResourcesConditionalResponse)
.condition;
expect(conditions).toEqual({
operator: "not",
operands: [
{
operator: "eq",
operands: [
{ name: "request.resource.attr.createdBy" },
{ value: "user1" },
],
},
],
});
const result = queryPlanToPrisma({
queryPlan,
mapper: {
"request.r