109 lines
3.4 KiB
TypeScript
109 lines
3.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { TASK_STATUS, TASK_TYPE } from '@/lib/task/types'
|
|
import { createTask } from '@/lib/task/service'
|
|
import { prisma } from '../helpers/prisma'
|
|
import { resetBillingState } from '../helpers/db-reset'
|
|
import { createTestProject, createTestUser } from '../helpers/billing-fixtures'
|
|
|
|
const reconcileMock = vi.hoisted(() => ({
|
|
isJobAlive: vi.fn(async () => true),
|
|
}))
|
|
|
|
vi.mock('@/lib/task/reconcile', () => reconcileMock)
|
|
|
|
describe('regression - task dedupe recovery', () => {
|
|
beforeEach(async () => {
|
|
await resetBillingState()
|
|
vi.clearAllMocks()
|
|
reconcileMock.isJobAlive.mockResolvedValue(true)
|
|
})
|
|
|
|
it('replaces locale-less queued task instead of deduping forever', async () => {
|
|
const user = await createTestUser()
|
|
const project = await createTestProject(user.id)
|
|
const stale = await prisma.task.create({
|
|
data: {
|
|
userId: user.id,
|
|
projectId: project.id,
|
|
type: TASK_TYPE.SCRIPT_TO_STORYBOARD_RUN,
|
|
targetType: 'NovelPromotionEpisode',
|
|
targetId: 'episode-regression-1',
|
|
status: TASK_STATUS.QUEUED,
|
|
payload: { episodeId: 'episode-regression-1' },
|
|
dedupeKey: 'script_to_storyboard_run:episode-regression-1',
|
|
queuedAt: new Date(),
|
|
},
|
|
})
|
|
|
|
const replacement = await createTask({
|
|
userId: user.id,
|
|
projectId: project.id,
|
|
type: TASK_TYPE.SCRIPT_TO_STORYBOARD_RUN,
|
|
targetType: 'NovelPromotionEpisode',
|
|
targetId: 'episode-regression-1',
|
|
payload: {
|
|
episodeId: 'episode-regression-1',
|
|
meta: { locale: 'zh' },
|
|
},
|
|
dedupeKey: 'script_to_storyboard_run:episode-regression-1',
|
|
})
|
|
|
|
expect(replacement.deduped).toBe(false)
|
|
expect(replacement.task.id).not.toBe(stale.id)
|
|
|
|
const failedStale = await prisma.task.findUnique({ where: { id: stale.id } })
|
|
expect(failedStale).toMatchObject({
|
|
status: TASK_STATUS.FAILED,
|
|
errorCode: 'TASK_LOCALE_REQUIRED',
|
|
dedupeKey: null,
|
|
})
|
|
})
|
|
|
|
it('replaces orphaned queued task when queue job is gone', async () => {
|
|
const user = await createTestUser()
|
|
const project = await createTestProject(user.id)
|
|
const orphan = await prisma.task.create({
|
|
data: {
|
|
userId: user.id,
|
|
projectId: project.id,
|
|
type: TASK_TYPE.VIDEO_PANEL,
|
|
targetType: 'NovelPromotionPanel',
|
|
targetId: 'panel-regression-1',
|
|
status: TASK_STATUS.QUEUED,
|
|
payload: {
|
|
storyboardId: 'storyboard-regression-1',
|
|
panelIndex: 1,
|
|
meta: { locale: 'zh' },
|
|
},
|
|
dedupeKey: 'video_panel:panel-regression-1',
|
|
queuedAt: new Date(),
|
|
},
|
|
})
|
|
reconcileMock.isJobAlive.mockResolvedValue(false)
|
|
|
|
const replacement = await createTask({
|
|
userId: user.id,
|
|
projectId: project.id,
|
|
type: TASK_TYPE.VIDEO_PANEL,
|
|
targetType: 'NovelPromotionPanel',
|
|
targetId: 'panel-regression-1',
|
|
payload: {
|
|
storyboardId: 'storyboard-regression-1',
|
|
panelIndex: 1,
|
|
meta: { locale: 'zh' },
|
|
},
|
|
dedupeKey: 'video_panel:panel-regression-1',
|
|
})
|
|
|
|
expect(replacement.deduped).toBe(false)
|
|
expect(replacement.task.id).not.toBe(orphan.id)
|
|
|
|
const failedOrphan = await prisma.task.findUnique({ where: { id: orphan.id } })
|
|
expect(failedOrphan).toMatchObject({
|
|
status: TASK_STATUS.FAILED,
|
|
errorCode: 'RECONCILE_ORPHAN',
|
|
dedupeKey: null,
|
|
})
|
|
})
|
|
})
|