Files
crawler-plugin/frontend-vue/tests/api-field-compat.test.ts
T

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)
})