Files
ersatztv/web/src/api/channels.test.ts
T
timothyandClaude Fable 5 7ffde6225f fix(web): read runtime-cased ffmpegProfileId/ffmpegProfile keys (#198)
Regenerate v1.d.ts from the aligned spec (fFmpegProfileId->ffmpegProfileId,
fFmpegProfile->ffmpegProfile) and update every SPA reader/writer and test mock
to the runtime casing:

- ChannelEditScreen: read channel.ffmpegProfileId; draft/set/select use
  ffmpegProfileId.
- ChannelBuilder: template reads/writes + ADVANCED_KEYS use ffmpegProfileId.
- App.tsx channel list: read channel.ffmpegProfile (was fFmpegProfile, a latent
  bug that always rendered "Unassigned" since the runtime never sent that key).
- PlaybackTroubleshootingScreen: drop the #198 escape hatch (rawChannel cast +
  dual-read) and read data.channel.ffmpegProfileId directly.
- Test mocks now use runtime casing; pinning comments updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 20:00:38 +02:00

93 lines
2.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getChannelById, updateChannel, type UpdateChannelRequest } from './channels';
const sampleChannel = {
id: 5,
number: '5',
name: 'Cartoons',
group: 'ChicoryTV',
categories: '',
ffmpegProfileId: 1,
slugSeconds: null,
logo: { path: '', contentType: '' },
streamSelectorMode: 'Default',
streamSelector: null,
preferredAudioLanguageCode: null,
preferredAudioTitle: null,
playoutSource: 'Generated',
playoutMode: 'Continuous',
mirrorSourceChannelId: null,
playoutOffset: null,
streamingMode: 'TransportStreamHybrid',
watermarkId: null,
fallbackFillerId: null,
playoutCount: 0,
preferredSubtitleLanguageCode: null,
subtitleMode: 'None',
musicVideoCreditsMode: 'None',
musicVideoCreditsTemplate: null,
songVideoMode: 'Default',
transcodeMode: 'OnDemand',
idleBehavior: 'StopOnDisconnect',
isEnabled: true,
showInEpg: true
};
describe('getChannelById', () => {
beforeEach(() => {
window.localStorage.clear();
vi.restoreAllMocks();
});
it('GETs the channel by id and returns the view model', async () => {
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify(sampleChannel), {
headers: { 'Content-Type': 'application/json' },
status: 200
})
);
await expect(getChannelById(5)).resolves.toMatchObject({ id: 5, name: 'Cartoons' });
const [url, init] = fetchSpy.mock.calls[0];
expect(url).toBe('/api/channels/5');
expect((init?.method ?? 'GET').toUpperCase()).toBe('GET');
});
it('rejects with the ApiError status on 404', async () => {
vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ status: 404, title: 'Not Found' }), {
headers: { 'Content-Type': 'application/json' },
status: 404
})
);
await expect(getChannelById(999)).rejects.toMatchObject({ status: 404 });
});
});
describe('updateChannel', () => {
beforeEach(() => {
window.localStorage.clear();
vi.restoreAllMocks();
});
it('PUTs the request body to the channel id and returns the updated view model', async () => {
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ ...sampleChannel, name: 'Renamed' }), {
headers: { 'Content-Type': 'application/json' },
status: 200
})
);
const body = { ...sampleChannel, name: 'Renamed' } as unknown as UpdateChannelRequest;
await expect(updateChannel(5, body)).resolves.toMatchObject({ name: 'Renamed' });
const [url, init] = fetchSpy.mock.calls[0];
expect(url).toBe('/api/channels/5');
expect((init?.method ?? '').toUpperCase()).toBe('PUT');
expect(JSON.parse(init?.body as string)).toMatchObject({ name: 'Renamed', number: '5' });
});
});