import { test } from 'node:test' import assert from 'node:assert/strict' import { toParsePreview } from '../src/shared/parse-preview.ts' interface Row { rowIndex: number sourceId: string asin: string } interface ParseVo { taskId: number sourceFilename?: string sourceFileCount?: number totalRows: number acceptedRows: number droppedRows: number groupCount?: number aiPrompt?: string imgSwitch?: boolean categorySwitch?: boolean items: Row[] groups?: unknown[] } const row = (n: number): Row => ({ rowIndex: n, sourceId: `s${n}`, asin: `B00${n}` }) const vo = (over: Partial = {}): ParseVo => ({ taskId: 1, sourceFilename: 'a.xlsx', sourceFileCount: 2, totalRows: 100, acceptedRows: 90, droppedRows: 10, groupCount: 5, aiPrompt: 'prompt', imgSwitch: true, categorySwitch: false, items: [row(1), row(2), row(3)], groups: [{ g: 1 }, { g: 2 }], ...over, }) test('test_task_088_payload_preview_frontend_normal_default_path', () => { const input = vo() const preview = toParsePreview(input) assert.equal(preview.taskId, 1) assert.equal(preview.sourceFilename, 'a.xlsx') assert.equal(preview.sourceFileCount, 2) assert.equal(preview.totalRows, 100) assert.equal(preview.acceptedRows, 90) assert.equal(preview.droppedRows, 10) assert.equal(preview.groupCount, 5) assert.equal(preview.aiPrompt, 'prompt') assert.equal(preview.imgSwitch, true) assert.equal(preview.categorySwitch, false) assert.equal('items' in preview, false, 'items 不得进入预览') assert.equal('groups' in preview, false, 'groups 不得进入预览') assert.equal('previewItems' in preview, false, '默认不保留预览行') // 输入对象不被修改 assert.equal(input.items.length, 3) assert.ok(Array.isArray(input.groups) && input.groups.length === 2) }) test('test_task_088_payload_preview_frontend_normal_multiple_items', () => { const input = vo({ items: [row(1), row(2), row(3), row(4), row(5)], groups: [{ g: 1 }, { g: 2 }, { g: 3 }] }) const preview = toParsePreview(input, { previewRowLimit: 3, previewGroupLimit: 2 }) assert.equal(preview.totalRows, 100) assert.equal(preview.acceptedRows, 90) assert.ok(preview.previewItems, '有界保留预览行') assert.equal(preview.previewItems.length, 3) assert.deepEqual( preview.previewItems.map((r) => r.rowIndex), [1, 2, 3], ) assert.equal(preview.previewGroups?.length, 2) // 摘要字段不受截断影响,顺序稳定 assert.equal(preview.previewItems[0].asin, 'B001') }) test('test_task_088_payload_preview_frontend_normal_repeated_operation_is_idempotent', () => { const input = vo() const once = toParsePreview(input) const twice = toParsePreview(input) assert.deepEqual(once, twice) assert.deepEqual(toParsePreview(once), twice, '对预览再裁剪结果不变') assert.deepEqual(vo(), input, '输入对象不被修改') const withLimit = toParsePreview(input, { previewRowLimit: 2 }) assert.deepEqual(toParsePreview(input, { previewRowLimit: 2 }), withLimit) }) test('test_task_088_payload_preview_frontend_boundary_empty_input', () => { const empty = toParsePreview(vo({ items: [], groups: [] })) assert.equal(empty.totalRows, 100) assert.equal('items' in empty, false) assert.equal('groups' in empty, false) assert.equal('previewItems' in empty, false) const limited = toParsePreview(vo({ items: [] }), { previewRowLimit: 5 }) assert.deepEqual(limited.previewItems, []) assert.equal(limited.acceptedRows, 90) }) test('test_task_088_payload_preview_frontend_boundary_single_item', () => { const input = vo({ items: [row(7)] }) const preview = toParsePreview(input, { previewRowLimit: 1 }) assert.equal(preview.previewItems?.length, 1) assert.equal(preview.previewItems?.[0].rowIndex, 7) assert.equal(preview.taskId, 1) }) test('test_task_088_payload_preview_frontend_boundary_limit_and_overflow', () => { const input = vo({ items: Array.from({ length: 10 }, (_, i) => row(i + 1)) }) // limit=0 不保留任何行 const none = toParsePreview(input, { previewRowLimit: 0 }) assert.equal('previewItems' in none, false) // 行数超 limit 截断前 N const some = toParsePreview(input, { previewRowLimit: 3 }) assert.equal(some.previewItems?.length, 3) assert.deepEqual( some.previewItems?.map((r) => r.rowIndex), [1, 2, 3], ) // 非法 limit 抛错 assert.throws(() => toParsePreview(input, { previewRowLimit: -1 }), /previewRowLimit 不能为负数/) assert.throws(() => toParsePreview(input, { previewGroupLimit: -1 }), /previewGroupLimit 不能为负数/) assert.throws(() => toParsePreview(input, { previewRowLimit: NaN }), /previewRowLimit 不能为负数/) }) test('test_task_088_payload_preview_frontend_invalid_input_rejected', () => { assert.throws(() => toParsePreview(null as never), /解析结果必须是对象/) assert.throws(() => toParsePreview(undefined as never), /解析结果必须是对象/) assert.throws(() => toParsePreview('x' as never), /解析结果必须是对象/) assert.throws(() => toParsePreview(vo({ taskId: 0 })), /taskId 必须是正整数/) assert.throws(() => toParsePreview(vo({ taskId: -1 })), /taskId 必须是正整数/) assert.throws(() => toParsePreview(vo({ taskId: NaN })), /taskId 必须是正整数/) }) test('test_task_088_payload_preview_frontend_dependency_failure_releases_resources', () => { const input = vo() let broken = true const poisoned = new Proxy(input, { get(target, prop, receiver) { if (broken && prop === 'totalRows') throw new Error('getter down') return Reflect.get(target, prop, receiver) }, }) assert.throws(() => toParsePreview(poisoned), /getter down/) // 输入未被修改 assert.deepEqual(input, vo()) // 错误可恢复:修复后同一输入成功 broken = false const preview = toParsePreview(poisoned) assert.equal(preview.totalRows, 100) assert.equal(preview.acceptedRows, 90) // 提取行数据失败(items 读取抛错)不影响其余摘要字段的读取路径 const badItems = new Proxy([row(1)], { get(target, prop) { if (prop === 'length') throw new Error('items getter down') return Reflect.get(target, prop) }, }) assert.throws(() => toParsePreview(vo({ items: badItems }), { previewRowLimit: 3 }), /items getter down/) })