feat:Strengthen the testing framework

This commit is contained in:
saturn
2026-03-15 18:15:25 +08:00
parent eec27fbabf
commit ecbd183a77
31 changed files with 2326 additions and 85 deletions

View File

@@ -0,0 +1,50 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { callRoute } from '../integration/api/helpers/call-route'
import { installAuthMocks, mockAuthenticated, resetAuthMockState } from '../helpers/auth'
import { resetSystemState } from '../helpers/db-reset'
import { prisma } from '../helpers/prisma'
import { seedMinimalDomainState } from '../system/helpers/seed'
describe('regression - panel variant cross storyboard safety', () => {
beforeEach(async () => {
await resetSystemState()
installAuthMocks()
})
it('sourcePanelId from another storyboard -> explicit invalid params and no dirty panel', async () => {
const seeded = await seedMinimalDomainState()
mockAuthenticated(seeded.user.id)
const beforeCount = await prisma.novelPromotionPanel.count({
where: { storyboardId: seeded.storyboard.id },
})
const mod = await import('@/app/api/novel-promotion/[projectId]/panel-variant/route')
const response = await callRoute(
mod.POST,
'POST',
{
locale: 'zh',
storyboardId: seeded.storyboard.id,
insertAfterPanelId: seeded.panel.id,
sourcePanelId: seeded.foreignPanel.id,
variant: {
video_prompt: 'variant prompt',
description: 'variant description',
},
},
{ params: { projectId: seeded.project.id } },
)
expect(response.status).toBe(400)
const json = await response.json() as { error?: { code?: string } }
expect(json.error?.code).toBe('INVALID_PARAMS')
const afterCount = await prisma.novelPromotionPanel.count({
where: { storyboardId: seeded.storyboard.id },
})
expect(afterCount).toBe(beforeCount)
resetAuthMockState()
})
})

View File

@@ -0,0 +1,108 @@
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,
})
})
})

View File

@@ -0,0 +1,68 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { submitTask } from '@/lib/task/submitter'
import { TASK_TYPE } from '@/lib/task/types'
import { prisma } from '../helpers/prisma'
import { resetBillingState } from '../helpers/db-reset'
import { createTestUser, seedBalance } from '../helpers/billing-fixtures'
const queueState = vi.hoisted(() => ({
message: 'queue add failed',
}))
vi.mock('@/lib/task/queues', () => ({
addTaskJob: vi.fn(async () => {
throw new Error(queueState.message)
}),
}))
vi.mock('@/lib/task/publisher', () => ({
publishTaskEvent: vi.fn(async () => ({})),
}))
describe('regression - enqueue compensation', () => {
beforeEach(async () => {
await resetBillingState()
vi.clearAllMocks()
process.env.BILLING_MODE = 'ENFORCE'
queueState.message = 'queue unavailable'
})
it('rolls back frozen balance when queue submission fails', async () => {
const user = await createTestUser()
await seedBalance(user.id, 10)
await expect(
submitTask({
userId: user.id,
locale: 'en',
projectId: 'project-regression-enqueue',
type: TASK_TYPE.VOICE_LINE,
targetType: 'VoiceLine',
targetId: 'line-regression-enqueue',
payload: { maxSeconds: 6 },
}),
).rejects.toMatchObject({ code: 'EXTERNAL_ERROR' })
const task = await prisma.task.findFirst({
where: {
userId: user.id,
type: TASK_TYPE.VOICE_LINE,
},
orderBy: { createdAt: 'desc' },
})
const balance = await prisma.userBalance.findUnique({ where: { userId: user.id } })
const freeze = await prisma.balanceFreeze.findFirst({ orderBy: { createdAt: 'desc' } })
expect(task).toMatchObject({
status: 'failed',
errorCode: 'ENQUEUE_FAILED',
})
expect(task?.billingInfo).toMatchObject({
billable: true,
status: 'rolled_back',
})
expect(balance?.balance).toBeCloseTo(10, 8)
expect(balance?.frozenAmount).toBeCloseTo(0, 8)
expect(freeze?.status).toBe('rolled_back')
})
})