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:
@@ -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" });
|
||||
|
||||
Reference in New Issue
Block a user