improve: tests

This commit is contained in:
2026-06-22 12:40:00 +02:00
parent 215d67ce89
commit b4e92b9b7b
3 changed files with 317 additions and 27 deletions
+58 -9
View File
@@ -1,5 +1,6 @@
import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals';
import { spawn } from 'node:child_process';
import type { ChildProcess } from 'node:child_process';
import { FfmpegProcessError } from '../src';
import { resolveFfmpegExecutable, startFfmpeg } from '../src/ffmpeg';
@@ -52,6 +53,26 @@ function createMockChildProcess(): MockChildProcess {
};
}
function mockSpawnReturn(childProcess: MockChildProcess): void {
mockSpawn.mockReturnValue(childProcess as unknown as ChildProcess);
}
function getProcessHandler(childProcess: MockChildProcess, eventName: string): (...args: unknown[]) => void {
return childProcess.once.mock.calls.find(([event]) => event === eventName)?.[1] as (...args: unknown[]) => void;
}
function getStdoutHandler(childProcess: MockChildProcess, eventName: string): (...args: unknown[]) => void {
return childProcess.stdout.once.mock.calls.find(([event]) => event === eventName)?.[1] as (
...args: unknown[]
) => void;
}
function getStderrHandler(childProcess: MockChildProcess, eventName: string): (...args: unknown[]) => void {
return childProcess.stderr.on.mock.calls.find(([event]) => event === eventName)?.[1] as (
...args: unknown[]
) => void;
}
describe('ffmpeg helpers', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -72,7 +93,7 @@ describe('ffmpeg helpers', () => {
it('spawns ffmpeg with the default argument set', () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(childProcess);
startFfmpeg('https://synradiode.stream.laut.fm/synradiode');
@@ -105,10 +126,10 @@ describe('ffmpeg helpers', () => {
it('rejects readiness when ffmpeg startup emits an error', async () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
const errorHandler = childProcess.once.mock.calls.find(([event]) => event === 'error')?.[1];
const errorHandler = getProcessHandler(childProcess, 'error');
errorHandler(new Error('spawn ENOENT'));
@@ -118,11 +139,11 @@ describe('ffmpeg helpers', () => {
it('includes stderr when ffmpeg exits before producing audio', async () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(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];
const stderrHandler = getStderrHandler(childProcess, 'data');
const exitHandler = getProcessHandler(childProcess, 'exit');
stderrHandler('invalid input');
exitHandler(1, null);
@@ -130,9 +151,37 @@ describe('ffmpeg helpers', () => {
await expect(handle.ready).rejects.toThrow('invalid input');
});
it('keeps the useful tail of long ffmpeg stderr output', async () => {
const childProcess = createMockChildProcess();
mockSpawnReturn(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
const stderrHandler = getStderrHandler(childProcess, 'data');
const exitHandler = getProcessHandler(childProcess, 'exit');
stderrHandler(`${'x'.repeat(5_000)}final diagnostic`);
exitHandler(1, null);
await expect(handle.ready).rejects.toThrow('final diagnostic');
});
it('keeps readiness resolved when ffmpeg exits after producing audio', async () => {
const childProcess = createMockChildProcess();
mockSpawnReturn(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
const readableHandler = getStdoutHandler(childProcess, 'readable');
const exitHandler = getProcessHandler(childProcess, 'exit');
readableHandler();
exitHandler(1, null);
await expect(handle.ready).resolves.toBeUndefined();
});
it('uses custom executable and argument overrides when provided', () => {
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(childProcess);
startFfmpeg('tests/audio.mp3', {
executablePath: '/custom/ffmpeg',
@@ -150,7 +199,7 @@ describe('ffmpeg helpers', () => {
it('stops a running child process and schedules a force kill fallback', () => {
jest.useFakeTimers();
const childProcess = createMockChildProcess();
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
handle.stop();
@@ -168,7 +217,7 @@ describe('ffmpeg helpers', () => {
it('does not signal a process that already exited', () => {
const childProcess = createMockChildProcess();
childProcess.exitCode = 0;
mockSpawn.mockReturnValue(childProcess);
mockSpawnReturn(childProcess);
const handle = startFfmpeg('tests/audio.mp3');
handle.stop();