Files
order_site/apps/backend/src/services/scheduler/data-retention-job.test.ts
T

100 lines
3.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.
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import test from 'node:test'
async function readJobSource() {
return readFile(new URL('./data-retention-job.ts', import.meta.url), 'utf8')
}
test('数据维护不得覆盖原始报文或删除永久业务事实', async () => {
const src = await readJobSource()
assert.equal(src.includes('raw_payload_json ='), false)
assert.equal(src.includes('DELETE FROM'), false)
for (const table of [
'orders',
'kuaishou_industry_vouchers',
'task_events',
'webhook_events',
'admin_audit_logs',
'work_order_events',
]) {
assert.equal(
new RegExp(`(?:UPDATE|DELETE)\\s+${table}\\b`, 'i').test(src),
false,
`${table} 属于永久业务事实,不能在维护任务中清理或覆盖`,
)
}
assert.ok(src.includes('永久业务事实未清理'))
})
test('成本异常只归档已处理的行,open 队列不能被藏起来', async () => {
const job = await readJobSource()
assert.ok(
job.includes('archiveResolvedCostExceptions'),
'成本异常必须走归档,不能直接 DELETE 掉审计痕迹',
)
assert.ok(job.includes('retention.costExceptionDays'), '保留天数必须走配置项')
// 归档 SQL 在独立服务里,检查服务源码而不是任务源码。
const service = await readFile(
new URL('../cost-center/cost-exception-archive-service.ts', import.meta.url),
'utf8',
)
assert.ok(service.includes("status <> 'open'"), 'open 行是待办队列,不能因超期被归档')
assert.ok(
service.includes('COALESCE(resolved_at, created_at)'),
'归档时钟优先用 resolved_at,ignored 行没有 resolved_at 时回退 created_at',
)
assert.ok(
service.includes('INSERT INTO cost_exceptions_archive'),
'归档必须落到 cost_exceptions_archive,保持可追溯',
)
})
test('保留任务要回填漏写的付款派生列,并在有命中时打日志', async () => {
const src = await readJobSource()
assert.ok(
src.includes('source_order_amount_fen IS NULL'),
'只处理派生列为空的行,避免把合法 0 或已落列的金额改掉',
)
assert.ok(
src.includes("material_json->'source'->>'orderAmountFen'"),
'回填口径必须与 083 / resolveSourceOrderAmountFen 相同',
)
assert.ok(src.includes("~ '^-?[0-9]{1,15}$'"), '超长数字不能 ::bigint,否则整次保留任务失败')
assert.ok(
src.includes('source_order_amount_fen 出现漏写'),
'有命中必须打日志,否则金额静默回退 orders.total_amount',
)
})
test('数据维护只保留可派生数据的成本异常归档窗口', async () => {
const defaults = await readFile(new URL('../../config/defaults.ts', import.meta.url), 'utf8')
assert.ok(defaults.includes('costExceptionDays: 60'))
for (const line of [
'rawPayloadDays:',
'taskEventDays:',
'webhookEventDays:',
'auditLogDays:',
'workOrderEventDays:',
]) {
assert.equal(defaults.includes(line), false, `永久事实不能存在清理窗口 ${line}`)
}
const overrides = await readFile(
new URL('../../config/env-overrides.ts', import.meta.url),
'utf8',
)
assert.equal(overrides.includes('RAW_PAYLOAD_RETENTION_DAYS'), false)
assert.equal(overrides.includes('TASK_EVENT_RETENTION_DAYS'), false)
assert.equal(overrides.includes('WEBHOOK_EVENT_RETENTION_DAYS'), false)
assert.equal(overrides.includes('AUDIT_LOG_RETENTION_DAYS'), false)
assert.equal(overrides.includes('WORK_ORDER_EVENT_RETENTION_DAYS'), false)
assert.ok(
overrides.includes(
"integerEnv('COST_EXCEPTION_RETENTION_DAYS', ['retention', 'costExceptionDays'])",
),
'COST_EXCEPTION_RETENTION_DAYS 需要环境变量出口,否则生产无法调整归档窗口',
)
})