125 lines
5.6 KiB
TypeScript
125 lines
5.6 KiB
TypeScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { createApiFieldCompatChecker } from '../src/shared/api-field-compat.ts'
|
|
|
|
const batchSchema = {
|
|
taskId: { type: 'number', required: true },
|
|
status: { type: 'string', required: true },
|
|
progress: { type: 'number' },
|
|
message: { type: 'string' },
|
|
items: { type: 'array' },
|
|
}
|
|
|
|
test('test_task_096_api_compat_normal_default_path', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
const violations = checker.check({ taskId: 1, status: 'SUCCESS', progress: 50 })
|
|
assert.deepEqual(violations, [], '全部必填字段存在且类型正确')
|
|
assert.equal(checker.lastResult().violations.length, 0)
|
|
assert.equal(checker.lastResult().checkedCount, 1)
|
|
assert.equal(checker.lastResult().passed, true)
|
|
assert.deepEqual(checker.checkedFields(), ['taskId', 'status', 'progress', 'message', 'items'])
|
|
})
|
|
|
|
test('test_task_096_api_compat_normal_multiple_items', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
const rows = [
|
|
{ taskId: 1, status: 'RUNNING' },
|
|
{ taskId: 2, status: 'SUCCESS', progress: 100 },
|
|
{ taskId: 3, status: 'FAILED', message: 'err' },
|
|
]
|
|
for (const row of rows) {
|
|
assert.deepEqual(checker.check(row), [])
|
|
}
|
|
assert.equal(checker.stats().checkedCount, 3)
|
|
assert.equal(checker.stats().violationCount, 0)
|
|
// 顺序稳定:检查记录按输入顺序
|
|
assert.deepEqual(checker.stats().checkedIds, [1, 2, 3])
|
|
})
|
|
|
|
test('test_task_096_api_compat_normal_repeated_operation_is_idempotent', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
const row = { taskId: 1, status: 'SUCCESS' }
|
|
assert.deepEqual(checker.check(row), [])
|
|
assert.deepEqual(checker.check(row), [], '同一输入重复检查结果一致')
|
|
assert.equal(checker.stats().checkedCount, 2)
|
|
assert.equal(checker.stats().violationCount, 0)
|
|
// 输入对象不被修改
|
|
assert.equal('items' in row, false)
|
|
// 无 schema 的纯遍历模式
|
|
const free = createApiFieldCompatChecker({ schema: {} })
|
|
assert.deepEqual(free.check({ anything: 1, other: 'x' }), [])
|
|
})
|
|
|
|
test('test_task_096_api_compat_boundary_empty_input', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
// 空对象:必填字段缺失
|
|
const violations = checker.check({})
|
|
assert.equal(violations.length, 2)
|
|
assert.ok(violations.some((v) => v.field === 'taskId' && v.kind === 'missing'))
|
|
assert.ok(violations.some((v) => v.field === 'status' && v.kind === 'missing'))
|
|
// 空数组响应:无字段可查,跳过
|
|
const checker2 = createApiFieldCompatChecker({ schema: batchSchema })
|
|
checker2.checkBatch([])
|
|
assert.equal(checker2.stats().checkedCount, 0)
|
|
assert.equal(checker2.stats().violationCount, 0)
|
|
assert.equal(checker2.lastResult().passed, true)
|
|
})
|
|
|
|
test('test_task_096_api_compat_boundary_single_item', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
const violations = checker.check({ taskId: 7, status: 'SUCCESS' })
|
|
assert.deepEqual(violations, [], '单条最小合法记录通过')
|
|
assert.equal(checker.checkedFields().includes('progress'), true, '可选字段也纳入检查清单')
|
|
assert.equal(checker.lastResult().checkedCount, 1)
|
|
})
|
|
|
|
test('test_task_096_api_compat_boundary_limit_and_overflow', () => {
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema, maxChecked: 2 })
|
|
checker.check({ taskId: 1, status: 'a' })
|
|
checker.check({ taskId: 2, status: 'b' })
|
|
checker.check({ taskId: 3, status: 'c' })
|
|
assert.equal(checker.stats().checkedCount, 2, '超过 maxChecked 后不再检查')
|
|
assert.equal(checker.stats().skippedCount, 1)
|
|
assert.deepEqual(checker.stats().checkedIds, [1, 2])
|
|
// 类型错误累计
|
|
const checker2 = createApiFieldCompatChecker({ schema: batchSchema })
|
|
checker2.check({ taskId: 'x' as never, status: 5 as never })
|
|
assert.equal(checker2.stats().violationCount, 2)
|
|
})
|
|
|
|
test('test_task_096_api_compat_invalid_input_rejected', () => {
|
|
assert.throws(() => createApiFieldCompatChecker({} as never), /schema 必须是对象/)
|
|
assert.throws(() => createApiFieldCompatChecker({ schema: null as never }), /schema 必须是对象/)
|
|
assert.throws(() => createApiFieldCompatChecker({ schema: [] as never }), /schema 必须是对象/)
|
|
assert.throws(
|
|
() => createApiFieldCompatChecker({ schema: { a: { type: 'unknown' } } }),
|
|
/不支持的字段类型: unknown/,
|
|
)
|
|
assert.throws(
|
|
() => createApiFieldCompatChecker({ schema: batchSchema, maxChecked: 0 }),
|
|
/maxChecked 必须为正数/,
|
|
)
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
assert.throws(() => checker.check(null as never), /对象必须是普通对象/)
|
|
assert.throws(() => checker.check([1, 2] as never), /对象必须是普通对象/)
|
|
assert.throws(() => checker.checkBatch('x' as never), /对象数组必须是数组/)
|
|
})
|
|
|
|
test('test_task_096_api_compat_dependency_failure_releases_resources', () => {
|
|
// 检查对象读取失败:check 抛错但状态不被污染,恢复后可用
|
|
let broken = false
|
|
const checker = createApiFieldCompatChecker({ schema: batchSchema })
|
|
const poisoned = new Proxy({ taskId: 1, status: 'SUCCESS' }, {
|
|
get(target, prop, receiver) {
|
|
if (broken && prop === 'taskId') throw new Error('row getter down')
|
|
return Reflect.get(target, prop, receiver)
|
|
},
|
|
})
|
|
broken = true
|
|
assert.throws(() => checker.check(poisoned), /row getter down/)
|
|
assert.equal(checker.stats().checkedCount, 0, '失败不产生检查记录')
|
|
broken = false
|
|
assert.deepEqual(checker.check(poisoned), [], '依赖恢复后同一检查器继续可用')
|
|
assert.equal(checker.stats().checkedCount, 1)
|
|
})
|