feat(auth): introduce explicit planning read permission
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { OrderType, AllocationType, ProjectStatus, SystemRole } from "@capakraken/shared";
|
||||
import { OrderType, AllocationType, PermissionKey, ProjectStatus, SystemRole } from "@capakraken/shared";
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import { invalidateDashboardCache } from "../lib/cache.js";
|
||||
import { logger } from "../lib/logger.js";
|
||||
@@ -110,6 +110,24 @@ function createProtectedCaller(db: Record<string, unknown>) {
|
||||
});
|
||||
}
|
||||
|
||||
function createProtectedCallerWithOverrides(
|
||||
db: Record<string, unknown>,
|
||||
overrides: { granted?: PermissionKey[]; denied?: PermissionKey[] } | null,
|
||||
) {
|
||||
return createCaller({
|
||||
session: {
|
||||
user: { email: "user@example.com", name: "User", image: null },
|
||||
expires: "2099-01-01T00:00:00.000Z",
|
||||
},
|
||||
db: db as never,
|
||||
dbUser: {
|
||||
id: "user_1",
|
||||
systemRole: SystemRole.USER,
|
||||
permissionOverrides: overrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const sampleProject = {
|
||||
id: "project_1",
|
||||
shortCode: "PRJ-001",
|
||||
@@ -748,6 +766,61 @@ describe("project router", () => {
|
||||
).rejects.toThrow(expect.objectContaining({ code: "FORBIDDEN" }));
|
||||
});
|
||||
|
||||
it("allows explicit viewPlanning overrides to access lightweight project search summaries", async () => {
|
||||
const db = {
|
||||
project: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: "project_1",
|
||||
shortCode: "GDM",
|
||||
name: "Gelddruckmaschine",
|
||||
status: ProjectStatus.ACTIVE,
|
||||
startDate: new Date("2026-01-01T00:00:00.000Z"),
|
||||
endDate: new Date("2026-03-31T00:00:00.000Z"),
|
||||
client: { name: "Acme Mobility" },
|
||||
},
|
||||
]),
|
||||
},
|
||||
};
|
||||
|
||||
const caller = createProtectedCallerWithOverrides(db, {
|
||||
granted: [PermissionKey.VIEW_PLANNING],
|
||||
});
|
||||
const result = await caller.searchSummaries({ search: "Gelddruckmaschine", limit: 10 });
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: "project_1",
|
||||
code: "GDM",
|
||||
name: "Gelddruckmaschine",
|
||||
status: "ACTIVE",
|
||||
start: "2026-01-01",
|
||||
end: "2026-03-31",
|
||||
client: "Acme Mobility",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not treat viewCosts as a substitute for viewPlanning on lightweight project search summaries", async () => {
|
||||
const db = {
|
||||
project: {
|
||||
findMany: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const caller = createProtectedCallerWithOverrides(db, {
|
||||
granted: [PermissionKey.VIEW_COSTS],
|
||||
});
|
||||
await expect(
|
||||
caller.searchSummaries({ search: "Gelddruckmaschine", limit: 10 }),
|
||||
).rejects.toThrow(
|
||||
expect.objectContaining({
|
||||
code: "FORBIDDEN",
|
||||
message: "Planning read access required",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns lightweight project identifier reads from the canonical router", async () => {
|
||||
const db = {
|
||||
project: {
|
||||
|
||||
Reference in New Issue
Block a user