import { describe, expect, it, vi } from 'vitest'; import { DEFAULT_JOB_TIMEOUT_MILLISECONDS, EngineManager, MAX_CONTACT_SHEETS_PER_ENGINE, MAX_EXECUTIONS_PER_ENGINE, MAX_PROBE_TIMEOUT_MILLISECONDS, MIN_PROBE_TIMEOUT_MILLISECONDS, MULTITHREAD_CODEC_THREAD_LIMIT, engineJobTimeoutMilliseconds, prepareEngineArguments, probeTimeoutMilliseconds, shouldRecycleExecutionCore, } from '../../src/ffmpeg/EngineManager'; describe('prepareEngineArguments', () => { it('leaves single-thread commands unchanged', () => { const args = ['-i', '/input/source.mp4', '/work/output.mp4']; expect( prepareEngineArguments( args, 'single-thread', ['/input/source.mp4'], ['/work/output.mp4'] ) ).toEqual(args); }); it('bounds multithread input and output codec threads', () => { expect( prepareEngineArguments( ['-i', '/input/source.mp4', '-c:v', 'libx264', '/work/output.mp4'], 'multithread', ['/input/source.mp4'], ['/work/output.mp4'] ) ).toEqual([ '-threads', String(MULTITHREAD_CODEC_THREAD_LIMIT), '-i', '/input/source.mp4', '-c:v', 'libx264', '-threads', String(MULTITHREAD_CODEC_THREAD_LIMIT), '/work/output.mp4', ]); }); it('bounds a generated output pattern in the final output position', () => { expect( prepareEngineArguments( ['-i', '/input/source.mp4', '-f', 'segment', '/work/part-%03d.mp4'], 'multithread', ['/input/source.mp4'], ['/work/part-001.mp4', '/work/part-002.mp4'] ) ).toEqual([ '-threads', '2', '-i', '/input/source.mp4', '-f', 'segment', '-threads', '2', '/work/part-%03d.mp4', ]); }); }); describe('engineJobTimeoutMilliseconds', () => { it('uses a bounded duration-aware watchdog', () => { expect(engineJobTimeoutMilliseconds({ expectedDurationSeconds: 2 })).toBe( 120_000 ); expect( engineJobTimeoutMilliseconds({ expectedDurationSeconds: 10_000 }) ).toBe(DEFAULT_JOB_TIMEOUT_MILLISECONDS); }); it('honors a shorter explicit timeout and caps a longer one', () => { expect( engineJobTimeoutMilliseconds({ expectedDurationSeconds: 30, timeoutMilliseconds: 5_000, }) ).toBe(5_000); expect( engineJobTimeoutMilliseconds({ timeoutMilliseconds: DEFAULT_JOB_TIMEOUT_MILLISECONDS * 2, }) ).toBe(DEFAULT_JOB_TIMEOUT_MILLISECONDS); }); }); describe('probeTimeoutMilliseconds', () => { it('keeps small probes responsive, scales real media, and caps the watchdog', () => { expect(probeTimeoutMilliseconds(34_057)).toBe( MIN_PROBE_TIMEOUT_MILLISECONDS ); expect(probeTimeoutMilliseconds(153_019_068)).toBe(72_966); expect(probeTimeoutMilliseconds(2 * 1024 * 1024 * 1024)).toBe( MAX_PROBE_TIMEOUT_MILLISECONDS ); }); it('rejects an already-cancelled probe before loading an engine', async () => { const factory = vi.fn(async () => { throw new Error('The engine factory must not be called.'); }); const manager = new EngineManager(factory); const controller = new AbortController(); controller.abort(); await expect( manager.probe( new File(['media'], 'cancelled.mp4', { type: 'video/mp4' }), undefined, controller.signal ) ).rejects.toMatchObject({ code: 'cancelled', message: 'The media probe was cancelled.', }); expect(factory).not.toHaveBeenCalled(); }); }); describe('execution core recycling policy', () => { it('refreshes only after the bounded idle-core job budget is exhausted', () => { expect(shouldRecycleExecutionCore(MAX_EXECUTIONS_PER_ENGINE - 1)).toBe( false ); expect(shouldRecycleExecutionCore(MAX_EXECUTIONS_PER_ENGINE)).toBe(true); }); it('refreshes before a fourth contact sheet hits retained tile-filter state', () => { expect( shouldRecycleExecutionCore( 3, 'contact-sheet', MAX_CONTACT_SHEETS_PER_ENGINE - 1 ) ).toBe(false); expect( shouldRecycleExecutionCore( 3, 'contact-sheet', MAX_CONTACT_SHEETS_PER_ENGINE ) ).toBe(true); expect( shouldRecycleExecutionCore( 3, 'thumbnail-series', MAX_CONTACT_SHEETS_PER_ENGINE ) ).toBe(false); }); });