import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { inspectVrm } from '../src/models/vrm-inspector'; const dir = mkdtempSync(join(tmpdir(), 'opendance-vrm-')); /** 构造合法 GLB:magic + version + length + JSON chunk */ function buildGlb(json: unknown): Buffer { const jsonBuf = Buffer.from(JSON.stringify(json), 'utf8'); const pad = (4 - (jsonBuf.length % 4)) % 4; const padded = Buffer.concat([jsonBuf, Buffer.alloc(pad, 0x20)]); const header = Buffer.alloc(12); header.write('glTF', 0, 'ascii'); header.writeUInt32LE(2, 4); header.writeUInt32LE(12 + 8 + padded.length, 8); const chunkHeader = Buffer.alloc(8); chunkHeader.writeUInt32LE(padded.length, 0); chunkHeader.writeUInt32LE(0x4e4f534a, 4); return Buffer.concat([header, chunkHeader, padded]); } function write(name: string, content: Buffer): string { const path = join(dir, name); writeFileSync(path, content); return path; } describe('VRM 版本识别', () => { it('识别 VRM 1.0(VRMC_vrm 扩展)', async () => { const path = write('v1.vrm', buildGlb({ extensions: { VRMC_vrm: { specVersion: '1.0' } } })); const result = await inspectVrm(path); expect(result.isGlb).toBe(true); expect(result.vrmVersion).toBe('1.0'); }); it('识别 VRM 0.x(VRM 扩展)', async () => { const path = write('v0.vrm', buildGlb({ extensions: { VRM: { specVersion: '0.0', exporterVersion: '0.79' } } })); const result = await inspectVrm(path); expect(result.vrmVersion).toBe('0.0'); }); it('VRM 扩展缺失时版本为 null(普通 GLB)', async () => { const path = write('plain.glb', buildGlb({ asset: { version: '2.0' }, meshes: [] })); const result = await inspectVrm(path); expect(result.isGlb).toBe(true); expect(result.vrmVersion).toBeNull(); expect(result.generator).toBeNull(); }); it('文本 glTF 不按 GLB 处理', async () => { const path = write('scene.gltf', Buffer.from('{"asset":{"version":"2.0"}}', 'utf8')); const result = await inspectVrm(path); expect(result.isGlb).toBe(false); expect(result.vrmVersion).toBeNull(); }); it('文件过小时报错', async () => { const path = write('tiny.glb', Buffer.from('glTF', 'ascii')); await expect(inspectVrm(path)).rejects.toThrow(/过小/); }); it('首 chunk 非 JSON 时报错', async () => { const header = Buffer.alloc(12); header.write('glTF', 0, 'ascii'); header.writeUInt32LE(2, 4); header.writeUInt32LE(32, 8); const chunk = Buffer.alloc(8); chunk.writeUInt32LE(4, 0); chunk.writeUInt32LE(0x004e4942, 4); // 'BIN\0' await expect(inspectVrm(write('bin-first.glb', Buffer.concat([header, chunk, Buffer.alloc(4)])))).rejects.toThrow( /JSON/, ); }); });