import assert from 'node:assert/strict'; import { test } from 'node:test'; import { categoryTreeOrder } from '../src/categories/category-input'; import { parseCategoryExport } from '../src/categories/category-import'; const root = { _id: 'math', name: '数感启蒙', icon: '📐', color: '#CDB4DB', sortOrder: 0, parentId: null, }; test('分类导入:兼容 JSON Lines/数组,保留原 ID、Emoji 和带时区时间', () => { const child = { ...root, _id: 'Math', parentId: 'math', createdAt: '2025-01-01T08:00:00+08:00', }; const lines = `\uFEFF${JSON.stringify(child)}\n\n${JSON.stringify(root)}\n`; const rows = parseCategoryExport(lines); assert.deepEqual( rows.map((row) => row.id), ['Math', 'math'], ); assert.equal(rows[0]?.createdAt?.toISOString(), '2025-01-01T00:00:00.000Z'); assert.equal(rows[1]?.createdAt, undefined); assert.equal(rows[1]?.icon, '📐'); assert.deepEqual(categoryTreeOrder(rows), ['math', 'Math']); assert.equal( parseCategoryExport(JSON.stringify([{ ...root, parentId: '' }]))[0] ?.parentId, null, ); }); test('分类导入:拒绝重复、未知字段、类型转换、越界及未迁移文件图标', () => { const bad = [ [{ ...root, id: 'other' }], [root, root], [{ ...root, role: 'admin' }], [{ ...root, name: 42 }], [{ ...root, icon: 'https://private.example/icon.png' }], [{ ...root, icon: 'cloud://secret/icon' }], [{ ...root, _id: ' math' }], [{ ...root, sortOrder: '2' }], [{ ...root, sortOrder: 0.5 }], [{ ...root, sortOrder: 2147483648 }], [{ ...root, parentId: false }], [{ ...root, createdAt: '2025-01-01T00:00:00' }], [{ ...root, createdAt: { $date: '2025-01-01' } }], [ { ...root, createdAt: '2025-01-02T00:00:00Z', updatedAt: '2025-01-01T00:00:00Z', }, ], ]; for (const value of bad) assert.throws(() => parseCategoryExport(JSON.stringify(value))); for (const value of ['', '{}', '[]', '{broken']) assert.throws(() => parseCategoryExport(value)); }); test('分类树:缺失父级、自引用和多层循环均拒绝,深层树不递归', () => { for (const rows of [ [{ id: 'a', parentId: 'missing' }], [{ id: 'a', parentId: 'a' }], [ { id: 'a', parentId: 'b' }, { id: 'b', parentId: 'c' }, { id: 'c', parentId: 'a' }, ], ]) assert.throws(() => categoryTreeOrder(rows)); const deep = Array.from({ length: 10_000 }, (_, i) => ({ id: String(i), parentId: i ? String(i - 1) : null, })).reverse(); assert.equal(categoryTreeOrder(deep).length, 10_000); });