import { test } from 'node:test' import assert from 'node:assert/strict' import { taskCoreSchema, resultFileSchema, taskWithResultSchema, apiFieldSchemas, getApiFieldSchema, createModuleFieldChecker, } from '../src/shared/api-field-schemas.ts' test('test_schema_task_required_fields', () => { assert.equal(taskCoreSchema.taskId.type, 'number') assert.equal(taskCoreSchema.taskId.required, true) assert.equal(taskCoreSchema.status.type, 'string') assert.equal(taskCoreSchema.status.required, true) assert.equal(taskCoreSchema.progress.type, 'number') assert.equal(taskCoreSchema.progress.required, undefined) }) test('test_schema_result_required_fields', () => { assert.equal(resultFileSchema.fileStatus.type, 'string') assert.equal(resultFileSchema.fileReady.type, 'boolean') assert.equal(resultFileSchema.fileProgress.type, 'number') assert.equal(resultFileSchema.downloadUrl.type, 'string') assert.equal(resultFileSchema.freshDownloadUrl.type, 'string') assert.ok('fileProgressLabel' in resultFileSchema) assert.ok('fileProgressStage' in resultFileSchema) }) test('test_checker_passes_valid', () => { const checker = createModuleFieldChecker('task') const violations = checker.check({ taskId: 1, status: 'SUCCESS', progress: 100 }) assert.deepEqual(violations, []) assert.equal(checker.lastResult().passed, true) }) test('test_checker_missing_required', () => { const checker = createModuleFieldChecker('task') const violations = checker.check({ taskId: 1 }) assert.equal(violations.length, 1) assert.equal(violations[0].field, 'status') assert.equal(violations[0].kind, 'missing') }) test('test_checker_wrong_type', () => { const checker = createModuleFieldChecker('task') const violations = checker.check({ taskId: 'x', status: 'SUCCESS' }) assert.equal(violations.length, 1) assert.equal(violations[0].field, 'taskId') assert.equal(violations[0].kind, 'type') assert.equal(violations[0].expected, 'number') assert.equal(violations[0].actual, 'string') }) test('test_checker_batch', () => { const checker = createModuleFieldChecker('task-result') checker.checkBatch([ { taskId: 1, status: 'SUCCESS', fileStatus: 'SUCCESS', fileReady: true }, { taskId: 2, status: 'RUNNING', downloadUrl: '/files/2.zip' }, { taskId: 'bad', status: 3 }, ]) const stats = checker.stats() assert.equal(stats.checkedCount, 3) assert.equal(stats.violationCount, 2) assert.equal(checker.lastResult().passed, false) }) test('test_schema_registered_modules', () => { const modules = [ 'task', 'result', 'task-result', 'dedupe', 'split', 'convert', 'publish', 'similar-asin', 'appearance-patent', 'delete-brand', 'price-track', 'shop-match', 'query-asin', 'withdraw', 'collect-data', 'image-video', ] for (const name of modules) { const def = getApiFieldSchema(name) assert.ok(def, `${name} 应注册 schema`) assert.ok(Object.keys(def.schema).length > 0, `${name} schema 非空`) } assert.equal(getApiFieldSchema('not-a-module'), null) }) test('test_schema_versioned', () => { const taskDef = apiFieldSchemas.task assert.equal(typeof taskDef.version, 'number') assert.ok(taskDef.version >= 1) const resultDef = apiFieldSchemas.result assert.ok(resultDef.version >= 1) // 版本号唯一性:不同注册项允许同版本,但同模块只能有一个定义 const versions = Object.values(apiFieldSchemas).map((d) => d.version) assert.ok(versions.every((v) => Number.isInteger(v) && v >= 1)) }) test('test_module_checker_uses_task_schema', () => { const checker = createModuleFieldChecker('dedupe') const violations = checker.check({ taskId: 1, status: 'SUCCESS', fileReady: true }) assert.deepEqual(violations, [], '模块 schema 应含 task + result 字段') assert.deepEqual(checker.checkedFields(), Object.keys(taskWithResultSchema)) }) test('test_checker_unknown_module_throws', () => { assert.throws(() => createModuleFieldChecker('ghost-module'), /未注册的模块/) })