/** @jest-environment node */ import { acquireTaskNoAssignmentLock, assignTaskNoOnComplete, } from '@/lib/product-package' import { prisma } from '@/lib/prisma' jest.mock('@/lib/prisma', () => ({ prisma: { $transaction: jest.fn(), material: { findFirst: jest.fn(), findMany: jest.fn(), }, }, })) describe('任务号完成写入并发锁', () => { beforeEach(() => { jest.clearAllMocks() ;(prisma.material.findFirst as jest.Mock).mockResolvedValue(null) ;(prisma.material.findMany as jest.Mock).mockResolvedValue([]) }) test('advisory lock 只返回 Prisma 可反序列化的整数列', async () => { const tx = { $queryRaw: jest.fn().mockResolvedValue([{ locked: 1 }]) } as any await acquireTaskNoAssignmentLock(tx, 'TASK202604230001') const [template, lockName] = tx.$queryRaw.mock.calls[0] const sql = Array.from(template as TemplateStringsArray).join('?') expect(sql).toContain('SELECT 1 AS locked') expect(sql).toContain('pg_advisory_xact_lock(hashtext(?)') expect(lockName).toBe('task-no:TASK202604230001') }) test('两个素材并发完成同一任务号时只能一个写入成功', async () => { let assignedMaterialId: string | null = null let transactionTail = Promise.resolve() const tx = { $queryRaw: jest.fn().mockResolvedValue([{ locked: 1 }]), material: { findFirst: jest.fn(async () => assignedMaterialId ? { id: assignedMaterialId, productModel: 'SPU-EXISTING', materialType: '任务-详情', } : null), update: jest.fn(async ({ where }: { where: { id: string } }) => { assignedMaterialId = where.id return { id: where.id } }), }, } ;(prisma.$transaction as jest.Mock).mockImplementation(async (callback) => { const previous = transactionTail let release!: () => void transactionTail = new Promise((resolve) => { release = resolve }) await previous try { return await callback(tx) } finally { release() } }) const results = await Promise.allSettled([ assignTaskNoOnComplete('material-a', 'TASK202604230001'), assignTaskNoOnComplete('material-b', 'TASK202604230001'), ]) expect(results.filter((result) => result.status === 'fulfilled')).toHaveLength(1) const rejected = results.find((result): result is PromiseRejectedResult => result.status === 'rejected') expect(rejected?.reason).toEqual(new Error( '任务号 TASK202604230001 已存在,对应素材:SPU-EXISTING(任务-详情)', )) expect(tx.material.update).toHaveBeenCalledTimes(1) expect(tx.$queryRaw).toHaveBeenCalledTimes(2) }) })