fix(api): wrap critical mutations in transactions and fix TOCTOU race conditions
- applyProjectScenario: wrap assignment loop in db.$transaction to prevent partial updates - vacation approve/reject: fix TOCTOU race via updateMany with status-guard in WHERE + CONFLICT on count=0 - vacation cancel: wrap vacation.update + entitlement.updateMany in $transaction - batchApprove: collect mutations, wrap in $transaction, dispatch SSE/notifications after commit - Fix dead-code bug in createHappyPathDb where $transaction was assigned after return - Add atomicity and concurrency tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -71,7 +71,7 @@ export function createHappyPathDb() {
|
||||
return null;
|
||||
});
|
||||
|
||||
return {
|
||||
const db = {
|
||||
user: {
|
||||
findUnique: vi.fn().mockResolvedValue({ id: "user_1", systemRole: "MANAGER" }),
|
||||
},
|
||||
@@ -111,6 +111,11 @@ export function createHappyPathDb() {
|
||||
},
|
||||
vacation: {
|
||||
findUnique: vacationFindUnique,
|
||||
findUniqueOrThrow: vi.fn().mockImplementation(async (args?: any) => {
|
||||
const result = await vacationFindUnique({ where: { id: args?.where?.id } });
|
||||
if (!result) throw new Error("Vacation not found");
|
||||
return result;
|
||||
}),
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
findFirst: vi.fn().mockResolvedValue(null),
|
||||
create: vi.fn().mockResolvedValue({
|
||||
@@ -147,6 +152,7 @@ export function createHappyPathDb() {
|
||||
approvedById: args?.data?.approvedById ?? existing?.approvedById ?? null,
|
||||
};
|
||||
}),
|
||||
updateMany: vi.fn().mockResolvedValue({ count: 1 }),
|
||||
},
|
||||
notification: {
|
||||
updateMany: vi.fn().mockResolvedValue({ count: 1 }),
|
||||
@@ -158,9 +164,9 @@ export function createHappyPathDb() {
|
||||
webhook: {
|
||||
findMany: vi.fn().mockResolvedValue([]),
|
||||
},
|
||||
};
|
||||
} as any;
|
||||
|
||||
(db as Record<string, unknown>).$transaction = vi.fn(
|
||||
db.$transaction = vi.fn(
|
||||
async (callback: (tx: typeof db) => Promise<unknown>) => callback(db),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user