Surface ffmpeg startup failures

This commit is contained in:
2026-06-22 12:16:21 +02:00
parent 1a77896c32
commit d10dae1b53
4 changed files with 118 additions and 3 deletions
+39
View File
@@ -1,6 +1,7 @@
import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals';
import { spawn } from 'node:child_process';
import { FfmpegProcessError } from '../src';
import { resolveFfmpegExecutable, startFfmpeg } from '../src/ffmpeg';
jest.mock('node:child_process', () => ({
@@ -12,12 +13,16 @@ const mockSpawn = jest.mocked(spawn);
type MockChildProcess = {
stdout: {
destroy: jest.Mock;
once: jest.Mock;
off: jest.Mock;
};
stderr: {
destroy: jest.Mock;
on: jest.Mock;
resume: jest.Mock;
};
once: jest.Mock;
off: jest.Mock;
removeAllListeners: jest.Mock;
kill: jest.Mock;
killed: boolean;
@@ -29,12 +34,16 @@ function createMockChildProcess(): MockChildProcess {
return {
stdout: {
destroy: jest.fn(),
once: jest.fn(),
off: jest.fn(),
},
stderr: {
destroy: jest.fn(),
on: jest.fn(),
resume: jest.fn(),
},
once: jest.fn(),
off: jest.fn(),
removeAllListeners: jest.fn(),
kill: jest.fn(),
killed: false,
@@ -88,9 +97,39 @@ describe('ffmpeg helpers', () => {
{ stdio: ['ignore', 'pipe', 'pipe'] },
);
expect(childProcess.once).toHaveBeenCalledWith('error', expect.any(Function));
expect(childProcess.once).toHaveBeenCalledWith('exit', expect.any(Function));
expect(childProcess.stdout.once).toHaveBeenCalledWith('readable', expect.any(Function));
expect(childProcess.stderr.on).toHaveBeenCalledWith('data', expect.any(Function));
expect(childProcess.stderr.resume).toHaveBeenCalledTimes(1);
});
it('rejects readiness when ffmpeg startup emits an error', async () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
const errorHandler = childProcess.once.mock.calls.find(([event]) => event === 'error')?.[1];
errorHandler(new Error('spawn ENOENT'));
await expect(handle.ready).rejects.toThrow(FfmpegProcessError);
await expect(handle.ready).rejects.toThrow('spawn ENOENT');
});
it('includes stderr when ffmpeg exits before producing audio', async () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
const stderrHandler = childProcess.stderr.on.mock.calls.find(([event]) => event === 'data')?.[1];
const exitHandler = childProcess.once.mock.calls.find(([event]) => event === 'exit')?.[1];
stderrHandler('invalid input');
exitHandler(1, null);
await expect(handle.ready).rejects.toThrow('invalid input');
});
it('uses custom executable and argument overrides when provided', () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);