fix(blueprint): require planning access for detailed reads
This commit is contained in:
@@ -391,6 +391,7 @@ describe("assistant router tool gating", () => {
|
||||
expect(userWithoutPlanning).not.toContain("list_allocations");
|
||||
expect(userWithoutPlanning).not.toContain("list_demands");
|
||||
expect(userWithoutPlanning).not.toContain("list_blueprints");
|
||||
expect(userWithoutPlanning).not.toContain("get_blueprint");
|
||||
expect(userWithoutPlanning).not.toContain("list_clients");
|
||||
expect(userWithoutPlanning).not.toContain("list_roles");
|
||||
expect(userWithoutPlanning).not.toContain("list_management_levels");
|
||||
@@ -402,6 +403,7 @@ describe("assistant router tool gating", () => {
|
||||
expect(userWithPlanning).toContain("list_allocations");
|
||||
expect(userWithPlanning).toContain("list_demands");
|
||||
expect(userWithPlanning).toContain("list_blueprints");
|
||||
expect(userWithPlanning).toContain("get_blueprint");
|
||||
expect(userWithPlanning).toContain("list_clients");
|
||||
expect(userWithPlanning).toContain("list_roles");
|
||||
expect(userWithPlanning).toContain("list_management_levels");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { BlueprintTarget, PermissionKey, SystemRole } from "@capakraken/shared";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { blueprintRouter } from "../router/blueprint.js";
|
||||
import { clientRouter } from "../router/client.js";
|
||||
@@ -73,6 +73,99 @@ describe("master-data router authorization", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("requires planning read access for blueprint list and detailed reads", async () => {
|
||||
const findMany = vi.fn();
|
||||
const findUnique = vi.fn();
|
||||
const findFirst = vi.fn();
|
||||
const caller = createCallerFactory(blueprintRouter)(createProtectedContext({
|
||||
blueprint: {
|
||||
findMany,
|
||||
findUnique,
|
||||
findFirst,
|
||||
},
|
||||
}));
|
||||
|
||||
await expect(caller.list({ isActive: true })).rejects.toMatchObject({
|
||||
code: "FORBIDDEN",
|
||||
message: "Planning read access required",
|
||||
});
|
||||
await expect(caller.getByIdentifier({ identifier: "Consulting Blueprint" })).rejects.toMatchObject({
|
||||
code: "FORBIDDEN",
|
||||
message: "Planning read access required",
|
||||
});
|
||||
await expect(caller.getById({ id: "bp_1" })).rejects.toMatchObject({
|
||||
code: "FORBIDDEN",
|
||||
message: "Planning read access required",
|
||||
});
|
||||
|
||||
expect(findMany).not.toHaveBeenCalled();
|
||||
expect(findUnique).not.toHaveBeenCalled();
|
||||
expect(findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("allows blueprint list and detailed reads for users with planning access", async () => {
|
||||
const listFindMany = vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: "bp_1",
|
||||
name: "Consulting Blueprint",
|
||||
target: "PROJECT",
|
||||
description: "Default consulting setup",
|
||||
isActive: true,
|
||||
},
|
||||
]);
|
||||
const getByIdFindUnique = vi.fn()
|
||||
.mockResolvedValueOnce({
|
||||
id: "bp_1",
|
||||
name: "Consulting Blueprint",
|
||||
target: "PROJECT",
|
||||
description: "Default consulting setup",
|
||||
fieldDefs: [{ key: "market", type: "text" }],
|
||||
defaults: { market: "EU" },
|
||||
validationRules: [],
|
||||
rolePresets: [],
|
||||
isActive: true,
|
||||
})
|
||||
.mockResolvedValueOnce(null);
|
||||
const getByIdentifierFindFirst = vi.fn().mockResolvedValue({
|
||||
id: "bp_1",
|
||||
name: "Consulting Blueprint",
|
||||
target: "PROJECT",
|
||||
description: "Default consulting setup",
|
||||
fieldDefs: [{ key: "market", type: "text" }],
|
||||
defaults: { market: "EU" },
|
||||
validationRules: [],
|
||||
rolePresets: [],
|
||||
isActive: true,
|
||||
});
|
||||
const caller = createCallerFactory(blueprintRouter)(createProtectedContext({
|
||||
blueprint: {
|
||||
findMany: listFindMany,
|
||||
findUnique: getByIdFindUnique,
|
||||
findFirst: getByIdentifierFindFirst,
|
||||
},
|
||||
}, {
|
||||
granted: [PermissionKey.VIEW_PLANNING],
|
||||
}));
|
||||
|
||||
const listResult = await caller.list({ target: BlueprintTarget.PROJECT, isActive: true });
|
||||
const byIdResult = await caller.getById({ id: "bp_1" });
|
||||
const byIdentifierResult = await caller.getByIdentifier({ identifier: "Consulting Blueprint" });
|
||||
|
||||
expect(listResult).toHaveLength(1);
|
||||
expect(byIdResult.name).toBe("Consulting Blueprint");
|
||||
expect(byIdentifierResult.fieldDefs).toEqual([{ key: "market", type: "text" }]);
|
||||
expect(listFindMany).toHaveBeenCalledWith({
|
||||
where: { target: "PROJECT", isActive: true },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
expect(getByIdFindUnique).toHaveBeenCalledWith({
|
||||
where: { id: "bp_1" },
|
||||
});
|
||||
expect(getByIdentifierFindFirst).toHaveBeenCalledWith({
|
||||
where: { name: { equals: "Consulting Blueprint", mode: "insensitive" } },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps country lists available to authenticated users as safe lookup data", async () => {
|
||||
const findMany = vi.fn().mockResolvedValue([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user