GetById/Create/Update return ChannelResponseModel via new GetChannelByIdForApi
read-side query; POST /api/channels/{id:int}/playout/reset (new
GetPlayoutIdByChannelId; by-number kept for HlsSessionWorker broadcast).
Refs #288 #197
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
238 lines
7.7 KiB
TypeScript
238 lines
7.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
createPlayout,
|
|
deletePlayout,
|
|
erasePlayoutItems,
|
|
erasePlayoutItemsAndHistory,
|
|
getAlternateSchedules,
|
|
getPlayoutItemSchedulingContext,
|
|
getPlayoutTemplates,
|
|
replaceAlternateSchedules,
|
|
replacePlayoutTemplates,
|
|
resetChannelPlayout,
|
|
updatePlayoutDefaultDeco,
|
|
updatePlayoutDetails,
|
|
type CreatePlayoutRequest
|
|
} from './playouts';
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status
|
|
});
|
|
}
|
|
|
|
function noContentResponse(): Response {
|
|
return new Response(null, { status: 204 });
|
|
}
|
|
|
|
describe('playouts api client', () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('createPlayout POSTs a classic playout request', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 7 }, 201));
|
|
|
|
const body: CreatePlayoutRequest = {
|
|
channelId: 1,
|
|
programScheduleId: 2,
|
|
scheduleFile: null,
|
|
scheduleKind: 'Classic'
|
|
};
|
|
|
|
await expect(createPlayout(body)).resolves.toMatchObject({ id: 7 });
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts');
|
|
expect(init).toMatchObject({ method: 'POST' });
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
channelId: 1,
|
|
programScheduleId: 2,
|
|
scheduleKind: 'Classic'
|
|
});
|
|
});
|
|
|
|
it('createPlayout POSTs a sequential playout request with a schedule file', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 9 }, 201));
|
|
|
|
const body: CreatePlayoutRequest = {
|
|
channelId: 3,
|
|
programScheduleId: null,
|
|
scheduleFile: '/config/schedule.yml',
|
|
scheduleKind: 'Sequential'
|
|
};
|
|
|
|
await createPlayout(body);
|
|
|
|
const [, init] = fetchMock.mock.calls[0];
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({
|
|
channelId: 3,
|
|
scheduleFile: '/config/schedule.yml',
|
|
scheduleKind: 'Sequential'
|
|
});
|
|
});
|
|
|
|
it('updatePlayoutDetails PUTs to the id route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3 }));
|
|
|
|
await updatePlayoutDetails(3, { dailyRebuildTime: '04:00:00', scheduleFile: null });
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/3');
|
|
expect(init).toMatchObject({ method: 'PUT' });
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({ dailyRebuildTime: '04:00:00' });
|
|
});
|
|
|
|
it('updatePlayoutDetails sends null dailyRebuildTime to clear the daily reset', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3 }));
|
|
|
|
await updatePlayoutDetails(3, { dailyRebuildTime: null, scheduleFile: null });
|
|
|
|
const [, init] = fetchMock.mock.calls[0];
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({ dailyRebuildTime: null });
|
|
});
|
|
|
|
it('updatePlayoutDefaultDeco PUTs the deco id to the deco route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ id: 3 }));
|
|
|
|
await updatePlayoutDefaultDeco(3, 7);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/3/deco');
|
|
expect(init).toMatchObject({ method: 'PUT' });
|
|
expect(JSON.parse(String(init?.body))).toMatchObject({ decoId: 7 });
|
|
});
|
|
|
|
it('deletePlayout DELETEs the id route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContentResponse());
|
|
|
|
await expect(deletePlayout(4)).resolves.toBeUndefined();
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/4');
|
|
expect(init).toMatchObject({ method: 'DELETE' });
|
|
});
|
|
|
|
it('resetChannelPlayout POSTs to the id-keyed channel playout reset route without a mode', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({}));
|
|
|
|
await resetChannelPlayout(20);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/channels/20/playout/reset');
|
|
expect(init).toMatchObject({ method: 'POST' });
|
|
});
|
|
|
|
it('erasePlayoutItems POSTs to the erase-items route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContentResponse());
|
|
|
|
await erasePlayoutItems(6);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/6/erase-items');
|
|
expect(init).toMatchObject({ method: 'POST' });
|
|
});
|
|
|
|
it('erasePlayoutItemsAndHistory POSTs to the erase-items-and-history route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(noContentResponse());
|
|
|
|
await erasePlayoutItemsAndHistory(6);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/6/erase-items-and-history');
|
|
expect(init).toMatchObject({ method: 'POST' });
|
|
});
|
|
|
|
it('getPlayoutItemSchedulingContext GETs the item scheduling-context route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse({ context: '{ "x": 1 }' }));
|
|
|
|
await expect(getPlayoutItemSchedulingContext(42)).resolves.toMatchObject({ context: '{ "x": 1 }' });
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/items/42/scheduling-context');
|
|
expect(init?.method ?? 'GET').toBe('GET');
|
|
});
|
|
|
|
it('getAlternateSchedules GETs the alternate-schedules route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
|
|
|
|
await getAlternateSchedules(5);
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/5/alternate-schedules');
|
|
expect(init?.method ?? 'GET').toBe('GET');
|
|
});
|
|
|
|
it('replaceAlternateSchedules PUTs items with day-name strings', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
|
|
|
|
await replaceAlternateSchedules(5, {
|
|
items: [
|
|
{
|
|
id: 0,
|
|
programScheduleId: 2,
|
|
daysOfWeek: ['Monday', 'Tuesday'],
|
|
daysOfMonth: [1, 2],
|
|
monthsOfYear: [1],
|
|
limitToDateRange: false,
|
|
startMonth: 1,
|
|
startDay: 1,
|
|
startYear: null,
|
|
endMonth: 12,
|
|
endDay: 31,
|
|
endYear: null
|
|
}
|
|
]
|
|
});
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/5/alternate-schedules');
|
|
expect(init).toMatchObject({ method: 'PUT' });
|
|
const body = JSON.parse(String(init?.body));
|
|
expect(body.items[0].daysOfWeek).toEqual(['Monday', 'Tuesday']);
|
|
expect(body.items[0].programScheduleId).toBe(2);
|
|
});
|
|
|
|
it('getPlayoutTemplates GETs the templates route', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
|
|
|
|
await getPlayoutTemplates(5);
|
|
|
|
const [url] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/5/templates');
|
|
});
|
|
|
|
it('replacePlayoutTemplates PUTs template items', async () => {
|
|
const fetchMock = vi.spyOn(window, 'fetch').mockResolvedValue(jsonResponse([]));
|
|
|
|
await replacePlayoutTemplates(5, {
|
|
items: [
|
|
{
|
|
id: 0,
|
|
templateId: 3,
|
|
decoTemplateId: null,
|
|
daysOfWeek: ['Sunday'],
|
|
daysOfMonth: [1],
|
|
monthsOfYear: [1],
|
|
limitToDateRange: false,
|
|
startMonth: 1,
|
|
startDay: 1,
|
|
startYear: null,
|
|
endMonth: 12,
|
|
endDay: 31,
|
|
endYear: null
|
|
}
|
|
]
|
|
});
|
|
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe('/api/playouts/5/templates');
|
|
expect(init).toMatchObject({ method: 'PUT' });
|
|
const body = JSON.parse(String(init?.body));
|
|
expect(body.items[0].templateId).toBe(3);
|
|
expect(body.items[0].decoTemplateId).toBeNull();
|
|
});
|
|
});
|