77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
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/,
|
||
);
|
||
});
|
||
});
|