Files
waoowaoo/tests/integration/billing/service.integration.test.ts

138 lines
4.7 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import { beforeEach, describe, expect, it } from 'vitest'
import { calcVoice } from '@/lib/billing/cost'
import { buildDefaultTaskBillingInfo } from '@/lib/billing/task-policy'
import { prepareTaskBilling, rollbackTaskBilling, settleTaskBilling } from '@/lib/billing/service'
import { TASK_TYPE, type TaskBillingInfo } from '@/lib/task/types'
import { prisma } from '../../helpers/prisma'
import { resetBillingState } from '../../helpers/db-reset'
import { createTestProject, createTestUser, seedBalance } from '../../helpers/billing-fixtures'
function expectBillableInfo(info: TaskBillingInfo | null | undefined): Extract<TaskBillingInfo, { billable: true }> {
expect(info?.billable).toBe(true)
if (!info || !info.billable) {
throw new Error('Expected billable task billing info')
}
return info
}
describe('billing/service integration', () => {
beforeEach(async () => {
await resetBillingState()
})
it('marks task billing as skipped in OFF mode', async () => {
process.env.BILLING_MODE = 'OFF'
const user = await createTestUser()
const project = await createTestProject(user.id)
await seedBalance(user.id, 10)
const info = buildDefaultTaskBillingInfo(TASK_TYPE.VOICE_LINE, { maxSeconds: 5 })!
const result = await prepareTaskBilling({
id: randomUUID(),
userId: user.id,
projectId: project.id,
billingInfo: info,
})
expect(result?.billable).toBe(true)
expect((result as TaskBillingInfo & { status: string }).status).toBe('skipped')
})
it('records shadow audit in SHADOW mode and does not consume balance', async () => {
process.env.BILLING_MODE = 'SHADOW'
const user = await createTestUser()
const project = await createTestProject(user.id)
await seedBalance(user.id, 10)
const info = buildDefaultTaskBillingInfo(TASK_TYPE.VOICE_LINE, { maxSeconds: 5 })!
const taskId = randomUUID()
const prepared = expectBillableInfo(await prepareTaskBilling({
id: taskId,
userId: user.id,
projectId: project.id,
billingInfo: info,
}))
expect(prepared.status).toBe('quoted')
const settled = expectBillableInfo(await settleTaskBilling({
id: taskId,
userId: user.id,
projectId: project.id,
billingInfo: prepared,
}, {
result: { actualDurationSeconds: 2 },
}))
expect(settled.status).toBe('settled')
expect(settled.chargedCost).toBe(0)
const balance = await prisma.userBalance.findUnique({ where: { userId: user.id } })
expect(balance?.balance).toBeCloseTo(10, 8)
expect(balance?.totalSpent).toBeCloseTo(0, 8)
expect(await prisma.balanceTransaction.count({ where: { type: 'shadow_consume' } })).toBe(1)
})
it('freezes and settles in ENFORCE mode with actual usage', async () => {
process.env.BILLING_MODE = 'ENFORCE'
const user = await createTestUser()
const project = await createTestProject(user.id)
await seedBalance(user.id, 10)
const info = buildDefaultTaskBillingInfo(TASK_TYPE.VOICE_LINE, { maxSeconds: 5 })!
const taskId = randomUUID()
const prepared = expectBillableInfo(await prepareTaskBilling({
id: taskId,
userId: user.id,
projectId: project.id,
billingInfo: info,
}))
expect(prepared.status).toBe('frozen')
expect(prepared.freezeId).toBeTruthy()
const settled = expectBillableInfo(await settleTaskBilling({
id: taskId,
userId: user.id,
projectId: project.id,
billingInfo: prepared,
}, {
result: { actualDurationSeconds: 2 },
}))
expect(settled.status).toBe('settled')
expect(settled.chargedCost).toBeCloseTo(calcVoice(2), 8)
const balance = await prisma.userBalance.findUnique({ where: { userId: user.id } })
expect(balance?.totalSpent).toBeCloseTo(calcVoice(2), 8)
expect(balance?.frozenAmount).toBeCloseTo(0, 8)
})
it('rolls back frozen billing in ENFORCE mode', async () => {
process.env.BILLING_MODE = 'ENFORCE'
const user = await createTestUser()
const project = await createTestProject(user.id)
await seedBalance(user.id, 10)
const info = buildDefaultTaskBillingInfo(TASK_TYPE.VOICE_LINE, { maxSeconds: 5 })!
const taskId = randomUUID()
const prepared = expectBillableInfo(await prepareTaskBilling({
id: taskId,
userId: user.id,
projectId: project.id,
billingInfo: info,
}))
const rolled = expectBillableInfo(await rollbackTaskBilling({
id: taskId,
billingInfo: prepared,
}))
expect(rolled.status).toBe('rolled_back')
const balance = await prisma.userBalance.findUnique({ where: { userId: user.id } })
expect(balance?.balance).toBeCloseTo(10, 8)
expect(balance?.frozenAmount).toBeCloseTo(0, 8)
})
})