import { BlueprintTarget, FieldType, SystemRole } from "@capakraken/shared"; import { describe, expect, it, vi } from "vitest"; import { blueprintRouter } from "../router/blueprint.js"; import { createCallerFactory } from "../trpc.js"; const createCaller = createCallerFactory(blueprintRouter); function createPlanningCaller(db: Record) { return createCaller({ session: { user: { email: "planning@example.com", name: "Planning", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_planning", systemRole: SystemRole.MANAGER, permissionOverrides: null, }, permissions: new Set(["view:planning"]), }); } function createAdminCaller(db: Record) { return createCaller({ session: { user: { email: "admin@example.com", name: "Admin", image: null }, expires: "2099-01-01T00:00:00.000Z", }, db: db as never, dbUser: { id: "user_admin", systemRole: SystemRole.ADMIN, permissionOverrides: null, }, }); } function sampleBlueprint(overrides: Record = {}) { return { id: "bp_1", name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, description: "Default setup", fieldDefs: [], defaults: {}, validationRules: [], rolePresets: [], isActive: true, isGlobal: false, createdAt: new Date(), updatedAt: new Date(), ...overrides, }; } describe("blueprint router", () => { it("lists active blueprints with the expected filters", async () => { const findMany = vi.fn().mockResolvedValue([sampleBlueprint()]); const caller = createPlanningCaller({ blueprint: { findMany }, }); const result = await caller.list({ target: BlueprintTarget.PROJECT }); expect(findMany).toHaveBeenCalledWith({ where: { target: BlueprintTarget.PROJECT, isActive: true, }, orderBy: { name: "asc" }, }); expect(result).toHaveLength(1); }); it("resolves a blueprint by identifier through the protected router query", async () => { const findUnique = vi.fn().mockResolvedValue(null); const findFirst = vi.fn().mockResolvedValueOnce(null).mockResolvedValueOnce({ id: "bp_1", name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, isActive: true, }); const caller = createPlanningCaller({ blueprint: { findUnique, findFirst }, }); const result = await caller.resolveByIdentifier({ identifier: " consulting " }); expect(findUnique).toHaveBeenCalledWith({ where: { id: "consulting" }, select: { id: true, name: true, target: true, isActive: true, }, }); expect(findFirst).toHaveBeenNthCalledWith(2, { where: { name: { contains: "consulting", mode: "insensitive" } }, select: { id: true, name: true, target: true, isActive: true, }, }); expect(result.name).toBe("Consulting Blueprint"); }); it("creates a blueprint and writes an audit entry", async () => { const create = vi.fn().mockResolvedValue(sampleBlueprint()); const auditCreate = vi.fn().mockResolvedValue({}); const caller = createAdminCaller({ blueprint: { create }, auditLog: { create: auditCreate }, }); const result = await caller.create({ name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, description: "Default setup", fieldDefs: [], defaults: { market: "EU" }, validationRules: [], }); expect(create).toHaveBeenCalledWith({ data: { name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, description: "Default setup", fieldDefs: [], defaults: { market: "EU" }, validationRules: [], }, }); expect(auditCreate).toHaveBeenCalledTimes(1); expect(result.id).toBe("bp_1"); }); it("updates a blueprint through the router and preserves the before snapshot", async () => { const update = vi.fn().mockResolvedValue( sampleBlueprint({ name: "Updated Blueprint", description: "Updated setup", }), ); const auditCreate = vi.fn().mockResolvedValue({}); const caller = createAdminCaller({ blueprint: { findUnique: vi.fn().mockResolvedValue(sampleBlueprint()), update, }, auditLog: { create: auditCreate }, }); const result = await caller.update({ id: "bp_1", data: { name: "Updated Blueprint", description: "Updated setup", }, }); expect(update).toHaveBeenCalledWith({ where: { id: "bp_1" }, data: { name: "Updated Blueprint", description: "Updated setup", }, }); expect(auditCreate).toHaveBeenCalledTimes(1); expect(result.name).toBe("Updated Blueprint"); }); it("updates role presets with the dedicated mutation payload", async () => { const update = vi.fn().mockResolvedValue( sampleBlueprint({ rolePresets: [{ roleId: "role_1" }], }), ); const auditCreate = vi.fn().mockResolvedValue({}); const caller = createAdminCaller({ blueprint: { findUnique: vi.fn().mockResolvedValue(sampleBlueprint({ rolePresets: [] })), update, }, auditLog: { create: auditCreate }, }); const rolePresets = [{ roleId: "role_1", allocation: 0.5 }]; const result = await caller.updateRolePresets({ id: "bp_1", rolePresets, }); expect(update).toHaveBeenCalledWith({ where: { id: "bp_1" }, data: { rolePresets }, }); expect(auditCreate).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ summary: "Updated role presets", }), }), ); expect(result.rolePresets).toEqual([{ roleId: "role_1" }]); }); it("soft deletes blueprints in batch and audits each deleted blueprint", async () => { const update = vi .fn() .mockResolvedValueOnce(sampleBlueprint({ id: "bp_1" })) .mockResolvedValueOnce(sampleBlueprint({ id: "bp_2", name: "Animation Blueprint" })); const auditCreate = vi.fn().mockResolvedValue({}); const transaction = vi .fn() .mockImplementation(async (operations: Promise[]) => Promise.all(operations)); const caller = createAdminCaller({ blueprint: { update }, auditLog: { create: auditCreate }, $transaction: transaction, }); const result = await caller.batchDelete({ ids: ["bp_1", "bp_2"] }); expect(transaction).toHaveBeenCalledTimes(1); expect(update).toHaveBeenNthCalledWith(1, { where: { id: "bp_1" }, data: { isActive: false }, }); expect(update).toHaveBeenNthCalledWith(2, { where: { id: "bp_2" }, data: { isActive: false }, }); expect(auditCreate).toHaveBeenCalledTimes(2); expect(result).toEqual({ count: 2 }); }); it("expands global field definitions with blueprint metadata", async () => { const findMany = vi.fn().mockResolvedValue([ { id: "bp_global", name: "Global Project Blueprint", fieldDefs: [ { id: "field_market", key: "market", label: "Market", order: 0, type: FieldType.TEXT, required: false, }, ], }, ]); const caller = createPlanningCaller({ blueprint: { findMany }, }); const result = await caller.getGlobalFieldDefs({ target: BlueprintTarget.PROJECT }); expect(findMany).toHaveBeenCalledWith({ where: { target: BlueprintTarget.PROJECT, isGlobal: true, isActive: true }, select: { id: true, name: true, fieldDefs: true }, }); expect(result).toEqual([ expect.objectContaining({ blueprintId: "bp_global", blueprintName: "Global Project Blueprint", key: "market", }), ]); }); });