gongxiangp2/lib/__tests__/product-package-task-no-lock.test.ts

83 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @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<void>((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)
})
})