refactor(api): extract role read procedures

This commit is contained in:
2026-03-31 21:22:44 +02:00
parent cba4d44f16
commit 884f1012c9
5 changed files with 345 additions and 84 deletions
@@ -0,0 +1,62 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole } from "@capakraken/shared";
vi.mock("@capakraken/application", async (importOriginal) => {
const actual = await importOriginal<typeof import("@capakraken/application")>();
return {
...actual,
countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
getDashboardPeakTimes: vi.fn().mockResolvedValue([]),
listAssignmentBookings: vi.fn().mockResolvedValue([]),
};
});
import { countPlanningEntries } from "@capakraken/application";
import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-master-data-read-test-helpers.js";
describe("assistant master data roles read tool", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(countPlanningEntries).mockResolvedValue({ countsByRoleId: new Map() });
});
it("routes role reads through their backing router", async () => {
const db = {
role: {
findMany: vi.fn().mockResolvedValue([
{
id: "role_anim",
name: "Animation",
color: "#112233",
_count: { resourceRoles: 2 },
},
]),
},
};
const ctx = createToolContext(db, {
userRole: SystemRole.CONTROLLER,
permissions: [PermissionKey.VIEW_PLANNING],
});
const rolesResult = await executeTool("list_roles", "{}", ctx);
expect(db.role.findMany).toHaveBeenCalledWith({
where: {},
include: {
_count: {
select: { resourceRoles: true },
},
},
orderBy: { name: "asc" },
});
expect(JSON.parse(rolesResult.content)).toEqual([
{
id: "role_anim",
name: "Animation",
color: "#112233",
},
]);
});
});
@@ -4,10 +4,18 @@ import {
createRole,
deactivateRole,
deleteRole,
getRoleById,
getRoleByIdentifier,
listRoles,
resolveRoleByIdentifier,
RoleIdInputSchema,
RoleIdentifierInputSchema,
RoleListInputSchema,
ResolveRoleIdentifierInputSchema,
UpdateRoleProcedureInputSchema,
updateRole,
} from "../router/role-procedure-support.js";
import { RESOURCE_BRIEF_SELECT } from "../db/selects.js";
const { countPlanningEntries } = vi.hoisted(() => ({
countPlanningEntries: vi.fn(),
@@ -49,6 +57,163 @@ describe("role procedure support", () => {
emitRoleUpdated.mockReset();
});
it("lists roles with planning entry counts", async () => {
countPlanningEntries.mockResolvedValue({
countsByRoleId: new Map([["role_fx", 2]]),
});
const db = {
role: {
findMany: vi.fn().mockResolvedValue([
{
id: "role_fx",
name: "FX",
_count: { resourceRoles: 1 },
},
]),
},
demandRequirement: {},
assignment: {},
};
const result = await listRoles(
createContext(db),
RoleListInputSchema.parse({ search: "FX" }),
);
expect(result).toEqual([
{
id: "role_fx",
name: "FX",
_count: { resourceRoles: 1, allocations: 2 },
},
]);
expect(db.role.findMany).toHaveBeenCalledWith({
where: { name: { contains: "FX", mode: "insensitive" } },
include: { _count: { select: { resourceRoles: true } } },
orderBy: { name: "asc" },
});
});
it("resolves roles by identifier for protected read paths", async () => {
const db = {
role: {
findUnique: vi.fn().mockResolvedValue({
id: "role_fx",
name: "FX",
color: "#111111",
isActive: true,
}),
},
};
const result = await resolveRoleByIdentifier(
createContext(db),
ResolveRoleIdentifierInputSchema.parse({ identifier: "role_fx" }),
);
expect(result).toEqual({
id: "role_fx",
name: "FX",
color: "#111111",
isActive: true,
});
expect(db.role.findUnique).toHaveBeenCalledWith({
where: { id: "role_fx" },
select: {
id: true,
name: true,
color: true,
isActive: true,
},
});
});
it("loads a role by identifier and attaches planning counts", async () => {
countPlanningEntries.mockResolvedValue({
countsByRoleId: new Map([["role_fx", 3]]),
});
const db = {
role: {
findUnique: vi.fn().mockResolvedValue({
id: "role_fx",
name: "FX",
description: null,
color: "#111111",
isActive: true,
_count: { resourceRoles: 2 },
}),
},
demandRequirement: {},
assignment: {},
};
const result = await getRoleByIdentifier(
createContext(db),
RoleIdentifierInputSchema.parse({ identifier: "role_fx" }),
);
expect(result).toEqual({
id: "role_fx",
name: "FX",
description: null,
color: "#111111",
isActive: true,
_count: { resourceRoles: 2, allocations: 3 },
});
expect(db.role.findUnique).toHaveBeenCalledWith({
where: { id: "role_fx" },
select: {
id: true,
name: true,
description: true,
color: true,
isActive: true,
_count: { select: { resourceRoles: true } },
},
});
});
it("loads a role by id with resource role details and planning counts", async () => {
countPlanningEntries.mockResolvedValue({
countsByRoleId: new Map([["role_fx", 1]]),
});
const db = {
role: {
findUnique: vi.fn().mockResolvedValue({
id: "role_fx",
name: "FX",
_count: { resourceRoles: 1 },
resourceRoles: [],
}),
},
demandRequirement: {},
assignment: {},
};
const result = await getRoleById(
createContext(db),
RoleIdInputSchema.parse({ id: "role_fx" }),
);
expect(result).toEqual({
id: "role_fx",
name: "FX",
_count: { resourceRoles: 1, allocations: 1 },
resourceRoles: [],
});
expect(db.role.findUnique).toHaveBeenCalledWith({
where: { id: "role_fx" },
include: {
_count: { select: { resourceRoles: true } },
resourceRoles: {
include: {
resource: { select: RESOURCE_BRIEF_SELECT },
},
},
},
});
});
it("creates roles with audit and zero allocation counts", async () => {
const role = {
id: "role_fx",