320 lines
9.7 KiB
TypeScript
320 lines
9.7 KiB
TypeScript
/**
|
||
* ZIP 解析器测试
|
||
*
|
||
* 测试范围:
|
||
* - ZIP 文件验证
|
||
* - hardware.json 格式解析
|
||
* - 错误处理
|
||
*/
|
||
|
||
import { describe, it, expect } from '@jest/globals';
|
||
import JSZip from 'jszip';
|
||
|
||
describe('ZIP 解析器测试', () => {
|
||
describe('文件验证', () => {
|
||
it('应该拒绝非 ZIP 文件', async () => {
|
||
const { quickValidateZip } = await import('@/lib/zip-parser');
|
||
|
||
const notZipFile = new File(['test content'], 'test.txt', { type: 'text/plain' });
|
||
const result = await quickValidateZip(notZipFile);
|
||
|
||
expect(result.valid).toBe(false);
|
||
expect(result.error).toContain('ZIP');
|
||
});
|
||
|
||
it('应该拒绝超过 100MB 的文件', async () => {
|
||
const { quickValidateZip } = await import('@/lib/zip-parser');
|
||
|
||
// 创建一个模拟的大文件
|
||
const largeFile = {
|
||
name: 'large.zip',
|
||
size: 101 * 1024 * 1024, // 101MB
|
||
} as File;
|
||
|
||
const result = await quickValidateZip(largeFile);
|
||
|
||
expect(result.valid).toBe(false);
|
||
expect(result.error).toContain('100MB');
|
||
});
|
||
|
||
it('应该接受有效的 ZIP 文件', async () => {
|
||
const { quickValidateZip } = await import('@/lib/zip-parser');
|
||
|
||
const validZipFile = new File([''], 'valid.zip', { type: 'application/zip' });
|
||
// 注意:这里的 File 对象大小为 0,小于 100MB
|
||
Object.defineProperty(validZipFile, 'size', { value: 1024 * 1024 }); // 1MB
|
||
|
||
const result = await quickValidateZip(validZipFile);
|
||
|
||
expect(result.valid).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('hardware.json 格式', () => {
|
||
it('应该正确解析 hardware.json 数据', async () => {
|
||
const hardwareData = {
|
||
version: '2.0',
|
||
exportedAt: '2024-01-01T00:00:00Z',
|
||
software: 'AutoCAD',
|
||
modelFile: 'model.glb',
|
||
panels: [],
|
||
holes: [],
|
||
hardwareTypes: []
|
||
};
|
||
|
||
expect(hardwareData.version).toBe('2.0');
|
||
expect(hardwareData.software).toBe('AutoCAD');
|
||
expect(Array.isArray(hardwareData.panels)).toBe(true);
|
||
expect(Array.isArray(hardwareData.holes)).toBe(true);
|
||
});
|
||
|
||
it('应该处理缺少可选字段的数据', () => {
|
||
const minimalData: {
|
||
modelFile: string
|
||
hardwareTypes: unknown[]
|
||
version?: string
|
||
exportedAt?: string
|
||
software?: string
|
||
panels?: unknown[]
|
||
holes?: unknown[]
|
||
} = {
|
||
modelFile: 'model.glb',
|
||
hardwareTypes: []
|
||
};
|
||
|
||
// 使用默认值
|
||
const processedData = {
|
||
version: minimalData.version ?? '2.0',
|
||
exportedAt: minimalData.exportedAt ?? new Date().toISOString(),
|
||
software: minimalData.software ?? 'AutoCAD',
|
||
modelFile: minimalData.modelFile,
|
||
panels: minimalData.panels ?? [],
|
||
holes: minimalData.holes ?? [],
|
||
};
|
||
|
||
expect(processedData.version).toBe('2.0');
|
||
expect(processedData.software).toBe('AutoCAD');
|
||
expect(processedData.panels).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('错误处理', () => {
|
||
it('应该处理无效的 JSON 格式', () => {
|
||
const invalidJson = 'not a json';
|
||
|
||
expect(() => JSON.parse(invalidJson)).toThrow();
|
||
});
|
||
|
||
it('应该检测缺少必需的 hardware.json', async () => {
|
||
// 创建一个没有 hardware.json 的 ZIP
|
||
const zip = new JSZip();
|
||
zip.file('model.glb', 'model content');
|
||
// 不添加 hardware.json
|
||
|
||
const content = await zip.generateAsync({ type: 'arraybuffer' });
|
||
const fileNames = Object.keys(zip.files);
|
||
|
||
const hasHardwareJson = fileNames.some(name =>
|
||
name.toLowerCase() === 'hardware.json' ||
|
||
name.toLowerCase().endsWith('/hardware.json')
|
||
);
|
||
|
||
expect(hasHardwareJson).toBe(false);
|
||
});
|
||
|
||
it('应该检测缺少模型文件', async () => {
|
||
// 创建一个没有模型文件的 ZIP
|
||
const zip = new JSZip();
|
||
zip.file('hardware.json', JSON.stringify({ version: '2.0' }));
|
||
// 不添加模型文件
|
||
|
||
const fileNames = Object.keys(zip.files);
|
||
|
||
const hasModelFile = fileNames.some(name => {
|
||
const lower = name.toLowerCase();
|
||
return lower.endsWith('.glb') || lower.endsWith('.obj');
|
||
});
|
||
|
||
expect(hasModelFile).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('支持的文件类型', () => {
|
||
it('应该支持 GLB 文件', () => {
|
||
const allowedExtensions = ['.glb', '.obj', '.json'];
|
||
expect(allowedExtensions).toContain('.glb');
|
||
});
|
||
|
||
it('应该支持 OBJ 文件', () => {
|
||
const allowedExtensions = ['.glb', '.obj', '.json'];
|
||
expect(allowedExtensions).toContain('.obj');
|
||
});
|
||
|
||
it('应该拒绝不支持的文件类型', () => {
|
||
const allowedExtensions = ['.glb', '.obj', '.json'];
|
||
const unsupportedExtension = '.fbx';
|
||
|
||
expect(allowedExtensions).not.toContain(unsupportedExtension);
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('数据格式转换', () => {
|
||
it('应该将 hardware.json 转换为内部格式', () => {
|
||
const hardwareJson = {
|
||
version: '2.2.0',
|
||
pluginVersion: '2.2.0',
|
||
exportedAt: '2024-01-01T00:00:00Z',
|
||
software: 'AutoCAD 2024',
|
||
modelFile: 'model.glb',
|
||
panels: [
|
||
{
|
||
id: 'panel-1',
|
||
sourceHandle: 'handle-1',
|
||
position: [0, 0, 0],
|
||
size: [100, 50, 18],
|
||
normal: [0, 0, 1],
|
||
}
|
||
],
|
||
holes: [
|
||
{
|
||
id: 'hole-1',
|
||
panelId: 'panel-1',
|
||
position: [10, 20, 0],
|
||
diameter: 5,
|
||
depth: 10,
|
||
normal: [0, 0, 1],
|
||
type: 'through',
|
||
inferredUsage: 'screw',
|
||
}
|
||
],
|
||
hardwareTypes: []
|
||
};
|
||
|
||
// 转换后的格式
|
||
const internalFormat = {
|
||
version: hardwareJson.version,
|
||
exportedAt: hardwareJson.exportedAt,
|
||
software: hardwareJson.software,
|
||
modelFile: hardwareJson.modelFile,
|
||
holes: hardwareJson.holes,
|
||
panels: hardwareJson.panels,
|
||
};
|
||
|
||
expect(internalFormat.version).toBe('2.2.0');
|
||
expect(internalFormat.panels.length).toBe(1);
|
||
expect(internalFormat.holes.length).toBe(1);
|
||
expect(internalFormat.holes[0].panelId).toBe('panel-1');
|
||
});
|
||
|
||
it('应该支持 v2.2+ 导出(position/normal 为数组)', async () => {
|
||
const { parseZipFile } = await import('@/lib/zip-parser');
|
||
|
||
const zip = new JSZip();
|
||
zip.file('model.glb', 'model content');
|
||
zip.file('hardware.json', JSON.stringify({
|
||
version: '2.2.14',
|
||
pluginVersion: '2.2.14',
|
||
exportedAt: '2026-02-05T00:00:00Z',
|
||
software: 'AutoCAD 2026',
|
||
sourceFile: 'demo.dwg',
|
||
modelFile: 'model.glb',
|
||
panelCount: 1,
|
||
units: 'mm',
|
||
panels: [
|
||
{
|
||
id: 'panel-1',
|
||
sourceHandle: 'handle-1',
|
||
position: [0, 0, 0],
|
||
size: [100, 50, 18],
|
||
normal: [0, 0, 1],
|
||
panelType: 'panel',
|
||
},
|
||
],
|
||
holes: [
|
||
{
|
||
id: 'hole-1',
|
||
panelId: 'panel-1',
|
||
position: [10, 20, 0],
|
||
normal: [0, 0, 1],
|
||
diameter: 5,
|
||
depth: 10,
|
||
type: 'through',
|
||
inferredUsage: 'screw',
|
||
},
|
||
],
|
||
}));
|
||
|
||
const content = await zip.generateAsync({ type: 'arraybuffer' });
|
||
const file = new File([content], 'demo.zip', { type: 'application/zip' });
|
||
Object.defineProperty(file, 'size', { value: content.byteLength });
|
||
// Jest/jsdom 的 File 可能没有 arrayBuffer 方法,这里补一个最小实现
|
||
;(file as any).arrayBuffer = async () => content;
|
||
|
||
const result = await parseZipFile(file);
|
||
|
||
expect(result.success).toBe(true);
|
||
expect(result.cadExportData?.holes?.length).toBe(1);
|
||
expect(result.cadExportData?.holes?.[0].position).toEqual([10, 20, 0]);
|
||
expect(result.cadExportData?.holes?.[0].normal).toEqual([0, 0, 1]);
|
||
expect(result.cadExportData?.holes?.[0].panelId).toBe('panel-1');
|
||
expect(result.cadExportData?.holes?.[0].inferredUsage).toBe('screw');
|
||
});
|
||
|
||
it('应该拒绝 v2.1 及以下导出(开发阶段不做旧版本兼容)', async () => {
|
||
const { parseZipFile } = await import('@/lib/zip-parser');
|
||
|
||
const zip = new JSZip();
|
||
zip.file('model.glb', 'model content');
|
||
zip.file('hardware.json', JSON.stringify({
|
||
version: '2.1.0',
|
||
pluginVersion: '2.1.0',
|
||
exportedAt: '2026-02-05T00:00:00Z',
|
||
software: 'AutoCAD 2026',
|
||
sourceFile: 'demo.dwg',
|
||
modelFile: 'model.glb',
|
||
panelCount: 0,
|
||
units: 'mm',
|
||
}));
|
||
|
||
const content = await zip.generateAsync({ type: 'arraybuffer' });
|
||
const file = new File([content], 'old.zip', { type: 'application/zip' });
|
||
Object.defineProperty(file, 'size', { value: content.byteLength });
|
||
;(file as any).arrayBuffer = async () => content;
|
||
|
||
const result = await parseZipFile(file);
|
||
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toContain('v2.2+');
|
||
});
|
||
|
||
it('应该拒绝低于 v2.2.14 的插件版本(模型分组已知问题)', async () => {
|
||
const { parseZipFile } = await import('@/lib/zip-parser');
|
||
|
||
const zip = new JSZip();
|
||
zip.file('model.glb', 'model content');
|
||
zip.file('hardware.json', JSON.stringify({
|
||
version: '2.2.0',
|
||
pluginVersion: '2.2.0',
|
||
exportedAt: '2026-02-05T00:00:00Z',
|
||
software: 'AutoCAD 2026',
|
||
sourceFile: 'demo.dwg',
|
||
modelFile: 'model.glb',
|
||
panelCount: 1,
|
||
units: 'mm',
|
||
panels: [],
|
||
holes: [],
|
||
}));
|
||
|
||
const content = await zip.generateAsync({ type: 'arraybuffer' });
|
||
const file = new File([content], 'old-2-2.zip', { type: 'application/zip' });
|
||
Object.defineProperty(file, 'size', { value: content.byteLength });
|
||
;(file as any).arrayBuffer = async () => content;
|
||
|
||
const result = await parseZipFile(file);
|
||
|
||
expect(result.success).toBe(false);
|
||
expect(result.error).toContain('v2.2.14+');
|
||
});
|
||
});
|