test(api): cover assistant role mutations

This commit is contained in:
2026-04-01 00:28:30 +02:00
parent a154cd8658
commit 40bf22a01a
4 changed files with 354 additions and 0 deletions
@@ -0,0 +1,61 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole } from "@capakraken/shared";
import {
createRoleRecord,
createToolContext,
executeTool,
resetRoleMutationMocks,
} from "./assistant-tools-role-mutation-test-helpers.js";
describe("assistant role delete tool - success", () => {
beforeEach(() => {
resetRoleMutationMocks();
});
it("routes deletion through the backing router and returns the expected assistant payload", async () => {
const roleRecord = createRoleRecord();
const db = {
role: {
findUnique: vi.fn()
.mockResolvedValueOnce(roleRecord)
.mockResolvedValueOnce(roleRecord)
.mockResolvedValueOnce({
id: "role_1",
name: "Senior CG Artist",
description: "Pipeline lead",
color: "#222222",
isActive: true,
_count: { resourceRoles: 0 },
}),
delete: vi.fn().mockResolvedValue({ id: "role_1" }),
},
auditLog: {
create: vi.fn().mockResolvedValue({ id: "audit_1" }),
},
demandRequirement: {
groupBy: vi.fn().mockResolvedValue([]),
},
assignment: {
groupBy: vi.fn().mockResolvedValue([]),
},
};
const ctx = createToolContext(db, {
userRole: SystemRole.ADMIN,
permissions: [PermissionKey.MANAGE_ROLES],
});
const deleteRoleResult = await executeTool(
"delete_role",
JSON.stringify({ id: "role_1" }),
ctx,
);
expect(JSON.parse(deleteRoleResult.content)).toEqual(expect.objectContaining({
success: true,
message: "Deleted role: Senior CG Artist",
}));
expect(db.role.delete).toHaveBeenCalledWith({ where: { id: "role_1" } });
expect(db.auditLog.create).toHaveBeenCalled();
});
});
@@ -0,0 +1,37 @@
import { vi } from "vitest";
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 as executeAssistantTool } from "../router/assistant-tools.js";
export { createToolContext } from "./assistant-tools-master-data-mutation-test-helpers.js";
export function resetRoleMutationMocks() {
vi.clearAllMocks();
vi.mocked(countPlanningEntries).mockResolvedValue({ countsByRoleId: new Map() });
}
export function createRoleRecord(overrides: Record<string, unknown> = {}) {
return {
id: "role_1",
name: "Senior CG Artist",
description: "Pipeline lead",
color: "#222222",
isActive: true,
_count: { resourceRoles: 0 },
resourceRoles: [],
...overrides,
};
}
export const executeTool = executeAssistantTool;
@@ -0,0 +1,160 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole } from "@capakraken/shared";
import {
createRoleRecord,
createToolContext,
executeTool,
resetRoleMutationMocks,
} from "./assistant-tools-role-mutation-test-helpers.js";
describe("assistant role create and update tools - success", () => {
beforeEach(() => {
resetRoleMutationMocks();
});
it("routes create and update role mutations through their backing router", async () => {
const db = {
role: {
findUnique: vi.fn().mockImplementation(async (args?: { where?: { name?: string; id?: string } }) => {
if (args?.where?.name === "Pipeline TD") return null;
if (args?.where?.name === "Pipeline Lead") return null;
if (args?.where?.id === "role_pipeline") {
return {
id: "role_pipeline",
name: "Pipeline TD",
description: "Pipeline craft",
color: "#123456",
isActive: true,
_count: { resourceRoles: 0 },
resourceRoles: [],
};
}
return null;
}),
create: vi.fn().mockResolvedValue({
id: "role_pipeline",
name: "Pipeline TD",
description: "Pipeline craft",
color: "#123456",
isActive: true,
_count: { resourceRoles: 0 },
}),
update: vi.fn().mockResolvedValue({
id: "role_pipeline",
name: "Pipeline Lead",
description: "Lead pipeline craft",
color: "#654321",
isActive: false,
_count: { resourceRoles: 0 },
}),
},
auditLog: {
create: vi.fn().mockResolvedValue({ id: "audit_1" }),
},
};
const ctx = createToolContext(db, {
userRole: SystemRole.ADMIN,
permissions: [PermissionKey.MANAGE_ROLES],
});
const createRoleResult = await executeTool(
"create_role",
JSON.stringify({ name: "Pipeline TD", description: "Pipeline craft", color: "#123456" }),
ctx,
);
const updateRoleResult = await executeTool(
"update_role",
JSON.stringify({
id: "role_pipeline",
name: "Pipeline Lead",
description: "Lead pipeline craft",
color: "#654321",
isActive: false,
}),
ctx,
);
expect(JSON.parse(createRoleResult.content)).toEqual(expect.objectContaining({
success: true,
roleId: "role_pipeline",
message: "Created role: Pipeline TD",
}));
expect(JSON.parse(updateRoleResult.content)).toEqual(expect.objectContaining({
success: true,
roleId: "role_pipeline",
message: "Updated role: Pipeline Lead",
}));
expect(db.role.create).toHaveBeenCalled();
expect(db.role.update).toHaveBeenCalled();
expect(db.auditLog.create).toHaveBeenCalled();
});
it("returns the expected assistant payloads for create and update role mutations", async () => {
const roleRecord = createRoleRecord();
const db = {
role: {
findUnique: vi.fn()
.mockResolvedValueOnce(null)
.mockResolvedValueOnce({ id: "role_1", name: "CG Artist", description: null, color: "#111111", isActive: true })
.mockResolvedValueOnce(null)
.mockResolvedValueOnce(roleRecord),
create: vi.fn().mockResolvedValue({
id: "role_1",
name: "CG Artist",
description: null,
color: "#111111",
isActive: true,
_count: { resourceRoles: 0 },
}),
update: vi.fn().mockResolvedValue({
id: "role_1",
name: "Senior CG Artist",
description: "Pipeline lead",
color: "#222222",
isActive: true,
_count: { resourceRoles: 0 },
}),
},
auditLog: {
create: vi.fn().mockResolvedValue({ id: "audit_1" }),
},
demandRequirement: {
groupBy: vi.fn().mockResolvedValue([]),
},
assignment: {
groupBy: vi.fn().mockResolvedValue([]),
},
};
const ctx = createToolContext(db, {
userRole: SystemRole.ADMIN,
permissions: [PermissionKey.MANAGE_ROLES],
});
const createRoleResult = await executeTool(
"create_role",
JSON.stringify({ name: "CG Artist", color: "#111111" }),
ctx,
);
const updateRoleResult = await executeTool(
"update_role",
JSON.stringify({
id: "role_1",
name: "Senior CG Artist",
description: "Pipeline lead",
color: "#222222",
}),
ctx,
);
expect(JSON.parse(createRoleResult.content)).toEqual(expect.objectContaining({
success: true,
message: "Created role: CG Artist",
}));
expect(JSON.parse(updateRoleResult.content)).toEqual(expect.objectContaining({
success: true,
message: "Updated role: Senior CG Artist",
}));
});
});
@@ -0,0 +1,96 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PermissionKey, SystemRole } from "@capakraken/shared";
import {
createRoleRecord,
createToolContext,
executeTool,
resetRoleMutationMocks,
} from "./assistant-tools-role-mutation-test-helpers.js";
describe("assistant role mutation tools", () => {
beforeEach(() => {
resetRoleMutationMocks();
});
it("returns a stable error when creating a duplicate role", async () => {
const ctx = createToolContext(
{
role: {
findUnique: vi.fn().mockResolvedValue({ id: "role_existing", name: "CG Artist" }),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_ROLES],
},
);
const result = await executeTool(
"create_role",
JSON.stringify({ name: "CG Artist" }),
ctx,
);
expect(JSON.parse(result.content)).toEqual(expect.objectContaining({
error: "A role with this name already exists.",
}));
});
it("returns a stable error when updating a missing role", async () => {
const ctx = createToolContext(
{
role: {
findUnique: vi.fn().mockResolvedValue(null),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_ROLES],
},
);
const result = await executeTool(
"update_role",
JSON.stringify({ id: "role_missing", name: "Senior CG Artist" }),
ctx,
);
expect(JSON.parse(result.content)).toEqual(expect.objectContaining({
error: "Role not found with the given criteria.",
}));
});
it("returns a stable error when deleting an assigned role", async () => {
const roleRecord = createRoleRecord({ _count: { resourceRoles: 1 } });
const ctx = createToolContext(
{
role: {
findUnique: vi.fn()
.mockResolvedValueOnce(roleRecord)
.mockResolvedValueOnce(roleRecord),
},
demandRequirement: {
groupBy: vi.fn().mockResolvedValue([]),
},
assignment: {
groupBy: vi.fn().mockResolvedValue([]),
},
},
{
userRole: SystemRole.MANAGER,
permissions: [PermissionKey.MANAGE_ROLES],
},
);
const result = await executeTool(
"delete_role",
JSON.stringify({ id: "role_1" }),
ctx,
);
expect(JSON.parse(result.content)).toEqual(expect.objectContaining({
error: "Role cannot be deleted while it is still assigned. Deactivate it instead.",
}));
});
});