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
@@ -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);