Files
lindyson-ai/apps/api/tests/mediaProcessor.test.ts
Juno ChowandClaude Fable 5.1 f665082dad chore: 建立产品仓库,完成开源前的敏感信息清理
前端(Vite + React)、API/worker(Fastify + BullMQ)、api-contracts 契约包、
迁移脚本、测试与自托管 docker-compose 示例。

开源准备:运维脚本与机器信息已拆到私有仓库;公司名、备案号、隐私邮箱改读
publicCompliance.local.json(仓库只保留占位模板);站点地址与客服二维码改为
VITE_ 环境变量;fixtures 中的真实用户标识已匿名化;三个 package.json 标注
Apache-2.0。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-16 02:10:46 +08:00

86 lines
2.7 KiB
TypeScript

import { Readable } from 'node:stream';
import { describe, expect, it } from 'vitest';
import {
createImageThumbnailBuffer,
parseMediaProbeOutput,
runMediaCommandBuffer,
} from '../src/storage/mediaProcessor.js';
describe('mediaProcessor', () => {
it('图片缩略图通过内存流生成 512px 内的 WebP', async () => {
const png = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
'base64',
);
const output = await createImageThumbnailBuffer(Readable.from([png]));
expect(output.subarray(0, 4).toString('ascii')).toBe('RIFF');
expect(output.subarray(8, 12).toString('ascii')).toBe('WEBP');
});
it('媒体子进程超时时使用 SIGKILL 终止并只拒绝一次', async () => {
await expect(runMediaCommandBuffer(
process.execPath,
['-e', 'setInterval(() => {}, 1000)'],
{ timeoutMs: 30, maxOutputBytes: 1024 },
)).rejects.toThrow('执行超时');
});
it('媒体子进程 stdout 超限时立即终止,不继续缓存', async () => {
await expect(runMediaCommandBuffer(
process.execPath,
['-e', "process.stdout.write('x'.repeat(4096))"],
{ timeoutMs: 1000, maxOutputBytes: 128 },
)).rejects.toThrow('stdout 超出 128 字节上限');
});
it('音频内的 attached picture 不被误识别为可播放视频流', () => {
const metadata = parseMediaProbeOutput(JSON.stringify({
streams: [
{ codec_type: 'audio', codec_name: 'mp3' },
{
codec_type: 'video', codec_name: 'mjpeg', width: 600, height: 600,
disposition: { attached_pic: 1 },
},
],
format: { duration: '12.5', format_name: 'mp3' },
}));
expect(metadata).toMatchObject({
hasAudio: true,
hasVideo: false,
width: null,
height: null,
durationSeconds: 12.5,
videoCodec: null,
});
});
it('同时存在封面和真实视频时只读取可播放视频元数据', () => {
const metadata = parseMediaProbeOutput(JSON.stringify({
streams: [
{
codec_type: 'video', codec_name: 'mjpeg', width: 600, height: 600,
disposition: { attached_pic: 1 },
},
{
codec_type: 'video', codec_name: 'h264', width: 1920, height: 1080,
duration: '5', nb_frames: '120', disposition: { attached_pic: 0 },
},
{ codec_type: 'audio', codec_name: 'aac' },
],
format: { duration: '5', format_name: 'mov,mp4,m4a,3gp,3g2,mj2' },
}));
expect(metadata).toMatchObject({
hasAudio: true,
hasVideo: true,
width: 1920,
height: 1080,
durationSeconds: 5,
frameCount: 120,
videoCodec: 'h264',
});
});
});