fix(api): wrap audit log writes inside their parent transactions

Prevents mutations from committing without an audit trail if the
auditLog.create call fails after the main write already succeeded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 16:40:10 +02:00
parent a01f99561d
commit 3c0179fcec
25 changed files with 758 additions and 656 deletions
@@ -19,7 +19,7 @@ export function createToolContext(
},
): ToolContext {
const userRole = options?.userRole ?? SystemRole.ADMIN;
const mergedDb = {
const mergedDb: Record<string, unknown> = {
...defaultDbDefaults,
...db,
blueprint: {
@@ -27,6 +27,9 @@ export function createToolContext(
...(db.blueprint as Record<string, unknown> | undefined),
},
};
if (!mergedDb["$transaction"]) {
mergedDb["$transaction"] = vi.fn(async (fn: (tx: unknown) => unknown) => fn(mergedDb));
}
return {
db: mergedDb as ToolContext["db"],
userId: "user_1",
@@ -21,8 +21,10 @@ export function createToolContext(
},
): ToolContext {
const userRole = options?.userRole ?? SystemRole.ADMIN;
const mergedDb: Record<string, unknown> = { ...db };
mergedDb["$transaction"] = vi.fn(async (fn: (tx: unknown) => unknown) => fn(mergedDb));
return {
db: db as ToolContext["db"],
db: mergedDb as ToolContext["db"],
userId: "user_1",
userRole,
permissions: new Set(options?.permissions ?? []),
@@ -1,4 +1,5 @@
import { PermissionKey, SystemRole } from "@capakraken/shared";
import { vi } from "vitest";
import type { ToolContext } from "../router/assistant-tools.js";
@@ -10,8 +11,12 @@ export function createToolContext(
},
): ToolContext {
const userRole = options?.userRole ?? SystemRole.ADMIN;
const mergedDb: Record<string, unknown> = { ...db };
if (!mergedDb["$transaction"]) {
mergedDb["$transaction"] = vi.fn(async (fn: (tx: unknown) => unknown) => fn(mergedDb));
}
return {
db: db as ToolContext["db"],
db: mergedDb as ToolContext["db"],
userId: "user_1",
userRole,
permissions: new Set(options?.permissions ?? []),
@@ -161,7 +161,7 @@ describe("effort rule procedure support", () => {
const createMany = vi.fn().mockResolvedValue({ count: 1 });
const auditCreate = vi.fn().mockResolvedValue({});
const result = await applyEffortRules(createManagerContext({
const db: Record<string, unknown> = {
estimate: {
findUnique: vi.fn().mockResolvedValue({
id: "est_1",
@@ -195,7 +195,10 @@ describe("effort rule procedure support", () => {
auditLog: {
create: auditCreate,
},
}), {
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await applyEffortRules(createManagerContext(db), {
estimateId: "est_1",
ruleSetId: "ers_1",
mode: "replace",
@@ -416,7 +416,7 @@ describe("effortRule.applyRules", () => {
it("replaces existing demand lines in replace mode", async () => {
const estimate = makeEstimate("WORKING", [{ id: "dl_old" }]);
const ruleSet = sampleRuleSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
effortRuleSet: { findUnique: vi.fn().mockResolvedValue(ruleSet) },
estimateDemandLine: {
@@ -424,6 +424,7 @@ describe("effortRule.applyRules", () => {
createMany: vi.fn().mockResolvedValue({ count: 1 }),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -444,7 +445,7 @@ describe("effortRule.applyRules", () => {
it("does not delete existing lines in append mode", async () => {
const estimate = makeEstimate("WORKING", [{ id: "dl_old" }]);
const ruleSet = sampleRuleSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
effortRuleSet: { findUnique: vi.fn().mockResolvedValue(ruleSet) },
estimateDemandLine: {
@@ -452,6 +453,7 @@ describe("effortRule.applyRules", () => {
createMany: vi.fn().mockResolvedValue({ count: 1 }),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -513,7 +515,7 @@ describe("effortRule.applyRules", () => {
it("creates demand lines with correct metadata shape", async () => {
const estimate = makeEstimate("WORKING");
const ruleSet = sampleRuleSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
effortRuleSet: { findUnique: vi.fn().mockResolvedValue(ruleSet) },
estimateDemandLine: {
@@ -521,6 +523,7 @@ describe("effortRule.applyRules", () => {
createMany: vi.fn().mockResolvedValue({ count: 1 }),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -434,9 +434,10 @@ describe("estimate router", () => {
const estimateCreate = vi.fn().mockResolvedValue(created);
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: { create: estimateCreate },
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -474,10 +475,11 @@ describe("estimate router", () => {
});
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: { create: estimateCreate },
project: { findUnique: projectFindUnique },
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -699,7 +701,7 @@ describe("estimate router", () => {
const updateEstimate = vi.fn().mockResolvedValue({});
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: {
findUnique,
update: updateEstimate,
@@ -709,13 +711,8 @@ describe("estimate router", () => {
update: updateVersion,
},
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) =>
callback({
estimateVersion: { updateMany, update: updateVersion },
estimate: { update: updateEstimate },
}),
),
};
db["$transaction"] = vi.fn(async (callback: (tx: unknown) => unknown) => callback(db));
const caller = createManagerCaller(db);
const result = await caller.submitVersion({ estimateId: "est_1" });
@@ -803,7 +800,7 @@ describe("estimate router", () => {
const updateEstimate = vi.fn().mockResolvedValue({});
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: {
findUnique,
update: updateEstimate,
@@ -813,13 +810,8 @@ describe("estimate router", () => {
update: updateVersion,
},
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) =>
callback({
estimateVersion: { updateMany, update: updateVersion },
estimate: { update: updateEstimate },
}),
),
};
db["$transaction"] = vi.fn(async (callback: (tx: unknown) => unknown) => callback(db));
const caller = createManagerCaller(db);
const result = await caller.approveVersion({ estimateId: "est_1" });
@@ -903,7 +895,7 @@ describe("estimate router", () => {
const updateEstimate = vi.fn().mockResolvedValue({});
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: {
findUnique,
update: updateEstimate,
@@ -915,18 +907,8 @@ describe("estimate router", () => {
resourceCostSnapshot: { createMany: createSnapshots },
estimateMetric: { createMany: createMetrics },
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) =>
callback({
estimateVersion: { create: createVersion },
estimateAssumption: { createMany: createAssumptions },
scopeItem: { create: createScopeItem },
estimateDemandLine: { createMany: createDemandLines },
resourceCostSnapshot: { createMany: createSnapshots },
estimateMetric: { createMany: createMetrics },
estimate: { update: updateEstimate },
}),
),
};
db["$transaction"] = vi.fn(async (callback: (tx: unknown) => unknown) => callback(db));
const caller = createManagerCaller(db);
const result = await caller.createRevision({ estimateId: "est_1" });
@@ -1008,12 +990,13 @@ describe("estimate router", () => {
const estimateCreate = vi.fn().mockResolvedValue(cloned);
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: {
findUnique: estimateFindUnique,
create: estimateCreate,
},
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -1026,9 +1009,10 @@ describe("estimate router", () => {
it("throws NOT_FOUND when source estimate does not exist", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique },
auditLog: { create: vi.fn() },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -1130,10 +1114,11 @@ describe("estimate router", () => {
const updateVersion = vi.fn().mockResolvedValue({});
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: estimateFindUnique },
estimateVersion: { update: updateVersion },
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -1279,10 +1264,11 @@ describe("estimate router", () => {
const createExport = vi.fn().mockResolvedValue({ id: "exp_1" });
const auditLogCreate = vi.fn().mockResolvedValue({});
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: estimateFindUnique },
estimateExport: { create: createExport },
auditLog: { create: auditLogCreate },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -1300,10 +1286,11 @@ describe("estimate router", () => {
it("throws NOT_FOUND when estimate does not exist", async () => {
const findUnique = vi.fn().mockResolvedValue(null);
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique },
estimateExport: { create: vi.fn() },
auditLog: { create: vi.fn() },
$transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
};
const caller = createManagerCaller(db);
@@ -165,7 +165,7 @@ describe("experience multiplier procedure support", () => {
const update = vi.fn().mockResolvedValue({});
const auditCreate = vi.fn().mockResolvedValue({});
const result = await applyExperienceMultiplierRules(createManagerContext({
const db: Record<string, unknown> = {
estimate: {
findUnique: vi.fn().mockResolvedValue({
id: "est_1",
@@ -199,7 +199,10 @@ describe("experience multiplier procedure support", () => {
auditLog: {
create: auditCreate,
},
}), {
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await applyExperienceMultiplierRules(createManagerContext(db), {
estimateId: "est_1",
multiplierSetId: "ems_1",
});
@@ -462,13 +462,14 @@ describe("experienceMultiplier.applyRules", () => {
it("updates demand lines with adjusted rates and creates audit log", async () => {
const estimate = makeEstimate("WORKING");
const multiplierSet = sampleMultiplierSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
experienceMultiplierSet: { findUnique: vi.fn().mockResolvedValue(multiplierSet) },
estimateDemandLine: {
update: vi.fn().mockResolvedValue({}),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -528,13 +529,14 @@ describe("experienceMultiplier.applyRules", () => {
const estimate = makeEstimate("WORKING");
const multiplierSet = sampleMultiplierSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
experienceMultiplierSet: { findUnique: vi.fn().mockResolvedValue(multiplierSet) },
estimateDemandLine: {
update: vi.fn(),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -544,7 +546,7 @@ describe("experienceMultiplier.applyRules", () => {
});
expect(result.linesUpdated).toBe(0);
expect(db.estimateDemandLine.update).not.toHaveBeenCalled();
expect((db.estimateDemandLine as Record<string, ReturnType<typeof vi.fn>>).update).not.toHaveBeenCalled();
});
it("rejects applying to a non-WORKING version", async () => {
@@ -613,13 +615,14 @@ describe("experienceMultiplier.applyRules", () => {
});
const estimate = makeEstimate("WORKING", [lineWithMetadata]);
const multiplierSet = sampleMultiplierSet();
const db = {
const db: Record<string, unknown> = {
estimate: { findUnique: vi.fn().mockResolvedValue(estimate) },
experienceMultiplierSet: { findUnique: vi.fn().mockResolvedValue(multiplierSet) },
estimateDemandLine: {
update: vi.fn().mockResolvedValue({}),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -133,20 +133,19 @@ describe("import-export procedure support", () => {
.mockResolvedValueOnce(null);
const resourceUpdate = vi.fn().mockResolvedValue({ id: "res_1" });
const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" });
const db: Record<string, unknown> = {
resource: {
findFirst: resourceFindFirst,
update: resourceUpdate,
},
auditLog: {
create: auditCreate,
},
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await importCsv(
createContext(
{
resource: {
findFirst: resourceFindFirst,
update: resourceUpdate,
},
auditLog: {
create: auditCreate,
},
},
[PermissionKey.IMPORT_DATA],
),
createContext(db, [PermissionKey.IMPORT_DATA]),
{
entityType: "resources",
rows: [
@@ -73,17 +73,19 @@ describe("import-export router", () => {
});
const resourceUpdate = vi.fn().mockResolvedValue({ id: "res_1" });
const auditCreate = vi.fn().mockResolvedValue({ id: "audit_1" });
const importDb: Record<string, unknown> = {
resource: {
findFirst: resourceFindFirst,
update: resourceUpdate,
},
auditLog: {
create: auditCreate,
},
};
importDb["$transaction"] = vi.fn(async (fn: (tx: unknown) => unknown) => fn(importDb));
const caller = createProtectedCaller(
{
resource: {
findFirst: resourceFindFirst,
update: resourceUpdate,
},
auditLog: {
create: auditCreate,
},
},
importDb,
{
role: SystemRole.MANAGER,
granted: [PermissionKey.IMPORT_DATA],
@@ -221,13 +221,14 @@ describe("project router", () => {
describe("create", () => {
it("creates a project and returns its id", async () => {
const created = { ...sampleProject, id: "project_new" };
const db = {
const db: Record<string, unknown> = {
project: {
findUnique: vi.fn().mockResolvedValue(null), // no shortCode conflict
create: vi.fn().mockResolvedValue(created),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
webhook: { findMany: vi.fn().mockResolvedValue([]) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -253,13 +254,14 @@ describe("project router", () => {
vi.mocked(dispatchWebhooks).mockRejectedValueOnce(new Error("webhook unavailable"));
const created = { ...sampleProject, id: "project_safe_create" };
const db = {
const db: Record<string, unknown> = {
project: {
findUnique: vi.fn().mockResolvedValue(null),
create: vi.fn().mockResolvedValue(created),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
webhook: { findMany: vi.fn().mockResolvedValue([]) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -451,12 +453,13 @@ describe("project router", () => {
describe("update", () => {
it("updates project fields", async () => {
const updated = { ...sampleProject, name: "Updated Name" };
const db = {
const db: Record<string, unknown> = {
project: {
findUnique: vi.fn().mockResolvedValue(sampleProject),
update: vi.fn().mockResolvedValue(updated),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -558,14 +561,12 @@ describe("project router", () => {
describe("batchUpdateStatus", () => {
it("updates multiple projects and returns count", async () => {
const db = {
const db: Record<string, unknown> = {
project: {
update: vi.fn().mockResolvedValue(sampleProject),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn((calls: unknown[]) =>
Promise.all((calls as Promise<unknown>[]).map(() => sampleProject)),
),
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -575,7 +576,7 @@ describe("project router", () => {
});
expect(result.count).toBe(3);
expect(db.auditLog.create).toHaveBeenCalled();
expect((db.auditLog as Record<string, ReturnType<typeof vi.fn>>).create).toHaveBeenCalled();
});
});
@@ -328,12 +328,13 @@ describe("resource router CRUD", () => {
describe("create", () => {
it("creates a resource and returns it", async () => {
const created = { ...sampleResource, id: "res_new", resourceRoles: [] };
const db = {
const db: Record<string, unknown> = {
resource: {
findFirst: vi.fn().mockResolvedValue(null),
create: vi.fn().mockResolvedValue(created),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -399,13 +400,14 @@ describe("resource router CRUD", () => {
describe("update", () => {
it("updates resource fields", async () => {
const updated = { ...sampleResource, displayName: "Alice Updated" };
const db = {
const db: Record<string, unknown> = {
resource: {
findUnique: vi.fn().mockResolvedValue(sampleResource),
update: vi.fn().mockResolvedValue(updated),
},
resourceRole: { deleteMany: vi.fn().mockResolvedValue({ count: 0 }) },
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -443,11 +445,12 @@ describe("resource router CRUD", () => {
describe("deactivate", () => {
it("sets isActive to false", async () => {
const deactivated = { ...sampleResource, isActive: false };
const db = {
const db: Record<string, unknown> = {
resource: {
update: vi.fn().mockResolvedValue(deactivated),
},
auditLog: { create: vi.fn().mockResolvedValue({}) },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -469,12 +472,13 @@ describe("resource router CRUD", () => {
.fn()
.mockResolvedValueOnce({ ...sampleResource, id: "res_1", isActive: false })
.mockResolvedValueOnce({ ...sampleResource, id: "res_2", isActive: false });
const db = {
const auditCreate = vi.fn().mockResolvedValue({});
const db: Record<string, unknown> = {
resource: {
update,
},
$transaction: vi.fn(async (operations: Promise<unknown>[]) => Promise.all(operations)),
auditLog: { create: vi.fn().mockResolvedValue({}) },
auditLog: { create: auditCreate },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createManagerCaller(db);
@@ -482,7 +486,7 @@ describe("resource router CRUD", () => {
expect(result).toEqual({ count: 2 });
expect(db.$transaction).toHaveBeenCalledTimes(1);
expect(db.auditLog.create).toHaveBeenCalledWith(
expect(auditCreate).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
entityType: "Resource",
@@ -222,7 +222,7 @@ describe("role procedure support", () => {
color: "#111111",
_count: { resourceRoles: 2 },
};
const db = {
const db: Record<string, unknown> = {
role: {
findUnique: vi.fn().mockResolvedValue(null),
create: vi.fn().mockResolvedValue(role),
@@ -230,6 +230,7 @@ describe("role procedure support", () => {
auditLog: {
create: vi.fn().mockResolvedValue(undefined),
},
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await createRole(createContext(db), {
@@ -300,7 +301,7 @@ describe("role procedure support", () => {
isActive: true,
_count: { resourceRoles: 1 },
};
const db = {
const db: Record<string, unknown> = {
role: {
findUnique: vi.fn()
.mockResolvedValueOnce(existing)
@@ -312,6 +313,7 @@ describe("role procedure support", () => {
auditLog: {
create: vi.fn().mockResolvedValue(undefined),
},
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await updateRole(createContext(db), UpdateRoleProcedureInputSchema.parse({
@@ -371,7 +373,7 @@ describe("role procedure support", () => {
countPlanningEntries.mockResolvedValue({
countsByRoleId: new Map([["role_fx", 4]]),
});
const db = {
const db: Record<string, unknown> = {
role: {
update: vi.fn().mockResolvedValue({
id: "role_fx",
@@ -385,6 +387,7 @@ describe("role procedure support", () => {
auditLog: {
create: vi.fn().mockResolvedValue(undefined),
},
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const result = await deactivateRole(createContext(db), RoleIdInputSchema.parse({ id: "role_fx" }));
@@ -143,15 +143,14 @@ describe("role router authorization", () => {
// planningEntry count queries (attachZeroAllocationCount path)
const planningEntryFindMany = vi.fn().mockResolvedValue([]);
const db: Record<string, unknown> = {
role: { create: roleCreate, findUnique: roleFindUnique },
auditLog: { create: auditLogCreate },
planningEntry: { findMany: planningEntryFindMany },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(db)),
};
const caller = createCaller(
createContext(
{
role: { create: roleCreate, findUnique: roleFindUnique },
auditLog: { create: auditLogCreate },
planningEntry: { findMany: planningEntryFindMany },
},
{ role: SystemRole.MANAGER },
),
createContext(db, { role: SystemRole.MANAGER }),
);
// Should not throw UNAUTHORIZED or FORBIDDEN
@@ -180,19 +179,18 @@ describe("role router authorization", () => {
const demandRequirementFindMany = vi.fn().mockResolvedValue([]);
const assignmentFindMany = vi.fn().mockResolvedValue([]);
const adminDb: Record<string, unknown> = {
role: {
findUnique: roleFindUnique,
delete: roleDelete,
},
auditLog: { create: auditLogCreate },
demandRequirement: { findMany: demandRequirementFindMany },
assignment: { findMany: assignmentFindMany },
$transaction: vi.fn(async (fn: (tx: unknown) => unknown) => fn(adminDb)),
};
const caller = createCaller(
createContext(
{
role: {
findUnique: roleFindUnique,
delete: roleDelete,
},
auditLog: { create: auditLogCreate },
demandRequirement: { findMany: demandRequirementFindMany },
assignment: { findMany: assignmentFindMany },
},
{ role: SystemRole.ADMIN },
),
createContext(adminDb, { role: SystemRole.ADMIN }),
);
const result = await caller.delete({ id: "role_1" });