Merge remote-tracking branch 'origin/main' into feat/144-s1-blocks
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 4m31s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m42s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

This commit is contained in:
2026-07-07 18:15:29 +02:00
11 changed files with 1037 additions and 31 deletions
+7 -1
View File
@@ -395,7 +395,9 @@ export interface components {
};
"CreatePlayoutRequest": {
"channelId": number;
"programScheduleId": number;
"scheduleKind": components["schemas"]["PlayoutScheduleKind"];
"programScheduleId": null | number;
"scheduleFile": null | string;
};
"CreateResolutionRequest": {
"width": number;
@@ -1054,6 +1056,10 @@ export interface components {
"searchingMinimumLogLevel": components["schemas"]["LogEventLevel"];
"streamingMinimumLogLevel": components["schemas"]["LogEventLevel"];
"httpMinimumLogLevel": components["schemas"]["LogEventLevel"];
};
"UpdatePlayoutDetailsRequest": {
"dailyRebuildTime": null | string;
"scheduleFile": null | string;
};
"UpdatePlayoutSettingsRequest": {
"daysToBuild": number;
+78
View File
@@ -0,0 +1,78 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPlayout, updatePlayoutDetails, type CreatePlayoutRequest } from './playouts';
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
headers: { 'Content-Type': 'application/json' },
status
});
}
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 });
});
});
+17
View File
@@ -8,6 +8,9 @@ export type PlayoutItem = components['schemas']['PlayoutItemResponseModel'];
export type PlayoutsPage = components['schemas']['PagedPlayoutsResponseModel'];
export type PlayoutItemsPage = components['schemas']['PagedPlayoutItemsResponseModel'];
export type PlayoutChannelState = components['schemas']['ChannelStateResponseModel'];
export type PlayoutScheduleKind = components['schemas']['PlayoutScheduleKind'];
export type CreatePlayoutRequest = components['schemas']['CreatePlayoutRequest'];
export type UpdatePlayoutDetailsRequest = components['schemas']['UpdatePlayoutDetailsRequest'];
export interface PlayoutsScreenData {
channelStates: PlayoutChannelState[];
@@ -64,6 +67,20 @@ export function resetAllPlayouts(): Promise<void> {
return request<void>('/api/playouts/reset-all', { method: 'POST' });
}
export function createPlayout(body: CreatePlayoutRequest): Promise<PlayoutDetail> {
return request<PlayoutDetail>('/api/playouts', {
body,
method: 'POST'
});
}
export function updatePlayoutDetails(playoutId: number, body: UpdatePlayoutDetailsRequest): Promise<PlayoutDetail> {
return request<PlayoutDetail>(`/api/playouts/${playoutId}`, {
body,
method: 'PUT'
});
}
export function usePlayoutsScreenQuery(pollMs = 30000): PlayoutsScreenQueryState {
const [state, setState] = useState<PlayoutsScreenState>({
data: null,