import { BlueprintTarget, FieldType } from "@capakraken/shared"; import { beforeEach, describe, expect, it, vi } from "vitest"; const { createAuditEntry } = vi.hoisted(() => ({ createAuditEntry: vi.fn(), })); vi.mock("../lib/audit.js", () => ({ createAuditEntry, })); import { batchDeleteBlueprints, createBlueprint, getGlobalBlueprintFieldDefs, updateBlueprintRolePresets, } from "../router/blueprint-procedure-support.js"; function createContext(db: Record) { return { db: db as never, dbUser: { id: "user_admin" } as never, }; } describe("blueprint procedure support", () => { beforeEach(() => { createAuditEntry.mockReset(); }); it("creates a blueprint and audits it", async () => { const create = vi.fn().mockResolvedValue({ id: "bp_1", name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, }); const result = await createBlueprint( createContext({ blueprint: { 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(result).toEqual({ id: "bp_1", name: "Consulting Blueprint", target: BlueprintTarget.PROJECT, }); expect(createAuditEntry).toHaveBeenCalledWith( expect.objectContaining({ entityType: "Blueprint", action: "CREATE", entityId: "bp_1", userId: "user_admin", }), ); }); it("updates only role presets with the dedicated audit summary", async () => { const findUnique = vi.fn().mockResolvedValue({ id: "bp_1", name: "Consulting Blueprint", rolePresets: [], }); const update = vi.fn().mockResolvedValue({ id: "bp_1", name: "Consulting Blueprint", rolePresets: [{ roleId: "role_1", allocation: 0.5 }], }); const rolePresets = [{ roleId: "role_1", allocation: 0.5 }]; const result = await updateBlueprintRolePresets( createContext({ blueprint: { findUnique, update }, }), { id: "bp_1", rolePresets, }, ); expect(findUnique).toHaveBeenCalledWith({ where: { id: "bp_1" }, }); expect(update).toHaveBeenCalledWith({ where: { id: "bp_1" }, data: { rolePresets }, }); expect(result.rolePresets).toEqual(rolePresets); expect(createAuditEntry).toHaveBeenCalledWith( expect.objectContaining({ entityType: "Blueprint", summary: "Updated role presets", before: { rolePresets: [] }, after: { rolePresets }, }), ); }); it("soft deletes multiple blueprints inside one transaction and audits each record", async () => { const update = vi .fn() .mockResolvedValueOnce({ id: "bp_1", name: "Consulting Blueprint", isActive: false }) .mockResolvedValueOnce({ id: "bp_2", name: "Animation Blueprint", isActive: false }); const $transaction = vi .fn() .mockImplementation(async (operations: Promise[]) => Promise.all(operations)); const result = await batchDeleteBlueprints( createContext({ $transaction, blueprint: { update }, }), { 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(createAuditEntry).toHaveBeenCalledTimes(2); expect(result).toEqual({ count: 2 }); }); it("expands global field definitions from active global blueprints", 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 result = await getGlobalBlueprintFieldDefs( createContext({ blueprint: { findMany }, }), { 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", }), ]); }); });