import { beforeEach, describe, expect, it, vi } from 'vitest'; import { createFFmpegProfile, deleteFFmpegProfile, getFFmpegProfile, getHardwareAccelerationKinds, updateFFmpegProfile, type CreateFFmpegProfileRequest } from './ffmpegProfiles'; function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { headers: { 'Content-Type': 'application/json' }, status }); } function noContent(): Response { return new Response(null, { status: 204 }); } const sampleRequest: CreateFFmpegProfileRequest = { allowBFrames: false, audioBitrate: 192, audioBufferSize: 384, audioChannels: 2, audioFormat: 'Aac', audioSampleRate: 48, bitDepth: 'EightBit', deinterlaceVideo: false, hardwareAcceleration: 'None', name: 'New', normalizeAudio: true, normalizeColors: true, normalizeFramerate: false, normalizeLoudnessMode: 'Off', normalizeVideo: true, padMode: 'Software', qsvExtraHardwareFrames: null, resolutionId: 1, scalingBehavior: 'ScaleAndPad', tonemapAlgorithm: 'Linear', targetLoudness: null, threadCount: 0, vaapiDevice: '', vaapiDisplay: '', vaapiDriver: 'Default', videoBitrate: 2000, videoBufferSize: 4000, videoFormat: 'H264', videoPreset: '', videoProfile: 'high' }; describe('ffmpeg profiles api client', () => { beforeEach(() => { window.localStorage.clear(); vi.restoreAllMocks(); }); it('getFFmpegProfile fetches a profile by id', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 4, name: 'Default' })); await expect(getFFmpegProfile(4)).resolves.toMatchObject({ id: 4 }); expect(fetchMock).toHaveBeenCalledWith('/api/ffmpeg/profiles/4', expect.objectContaining({ method: 'GET' })); }); it('createFFmpegProfile POSTs the request body', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 7, name: 'New' }, 201)); await expect(createFFmpegProfile(sampleRequest)).resolves.toMatchObject({ id: 7 }); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/ffmpeg/profiles'); expect(init).toMatchObject({ method: 'POST' }); expect(JSON.parse(String(init?.body)).resolutionId).toBe(1); }); it('updateFFmpegProfile PUTs to the id route', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3, name: 'Edited' })); await updateFFmpegProfile(3, sampleRequest); const [url, init] = fetchMock.mock.calls[0]; expect(url).toBe('/api/ffmpeg/profiles/3'); expect(init).toMatchObject({ method: 'PUT' }); }); it('deleteFFmpegProfile issues a DELETE and resolves on 204', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContent()); await expect(deleteFFmpegProfile(9)).resolves.toBeUndefined(); expect(fetchMock).toHaveBeenCalledWith('/api/ffmpeg/profiles/9', expect.objectContaining({ method: 'DELETE' })); }); it('getHardwareAccelerationKinds fetches the lookup', async () => { const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse(['None', 'Nvenc'])); await expect(getHardwareAccelerationKinds()).resolves.toEqual(['None', 'Nvenc']); expect(fetchMock).toHaveBeenCalledWith( '/api/ffmpeg/hardware-acceleration-kinds', expect.objectContaining({ method: 'GET' }) ); }); });