Files
ersatztv/web/src/api/search.test.ts
T
timothyandClaude Opus 4.8 70f357f8f9 feat(api): #288 ChannelDetailResponseModel for edit form + SPA repoints + final regen
Mint ChannelDetailResponseModel (faithful detail DTO exposing the raw editable
field set the channel editor reads: raw FFmpegProfileId/WatermarkId/FallbackFillerId
ids, the mode enums, logo, playoutCount, id) and route GetById/Create/Update through
it, replacing the lean list ChannelResponseModel that resolved the profile to a name
and dropped the editable ids (a functional regression for draftFromChannel). The lean
ChannelResponseModel stays unchanged for GET /api/channels. webEncodedName dropped
(SPA never reads it). Logo is mirrored as a Core ChannelLogoResponseModel since the
Application ArtworkContentTypeModel can't be referenced from Core.

Repoint the hand-written SPA client aliases now that the VMs are gone from the schema:
Channel -> ChannelDetailResponseModel, MediaCollection/SmartCollection -> *ResponseModel,
ProgramSchedule -> ProgramScheduleResponseModel. Fix #288 honest-nullability test fallout
in search.test.ts (null -> [] for now-non-null id arrays). Include the already-on-disk
playouts.ts WithDayNames removal and regenerate v1.json + v1.d.ts + endpoint-index.md
(authoritative final regen; the reset endpoint's {channelNumber}->{id} re-key surfaces
in the generated docs and the OpenApi error-contract test).

Refs #288 #197

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:33:39 +02:00

116 lines
3.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getSearchAllItems, getSearchResults, toAddItemsRequestFromSearch } from './search';
const emptyGroup = { totalCount: 0, items: [] };
const sampleResults = {
movies: emptyGroup,
shows: emptyGroup,
seasons: emptyGroup,
artists: emptyGroup,
episodes: emptyGroup,
musicVideos: emptyGroup,
songs: emptyGroup,
otherVideos: emptyGroup,
images: emptyGroup,
remoteStreams: emptyGroup
};
describe('getSearchResults', () => {
beforeEach(() => {
window.localStorage.clear();
vi.restoreAllMocks();
});
it('GETs /api/search with the query', async () => {
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify(sampleResults), {
headers: { 'Content-Type': 'application/json' },
status: 200
})
);
await getSearchResults({ query: 'star' });
const [url, init] = fetchSpy.mock.calls[0];
expect(url).toBe('/api/search?query=star');
expect((init?.method ?? 'GET').toUpperCase()).toBe('GET');
});
it('includes pageSize when provided', async () => {
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify(sampleResults), {
headers: { 'Content-Type': 'application/json' },
status: 200
})
);
await getSearchResults({ query: 'star wars', pageSize: 50 });
const [url] = fetchSpy.mock.calls[0];
expect(url).toBe('/api/search?query=star+wars&pageSize=50');
});
it('rejects with the ApiError status on failure', async () => {
vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ status: 422, title: 'Validation failed' }), {
headers: { 'Content-Type': 'application/json' },
status: 422
})
);
await expect(getSearchResults({ query: '' })).rejects.toMatchObject({ status: 422 });
});
});
describe('getSearchAllItems', () => {
beforeEach(() => {
window.localStorage.clear();
vi.restoreAllMocks();
});
it('GETs /api/search/all-items with the query', async () => {
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
new Response(JSON.stringify({ movieIds: [1], showIds: [2] }), {
headers: { 'Content-Type': 'application/json' },
status: 200
})
);
await getSearchAllItems('star wars');
const [url, init] = fetchSpy.mock.calls[0];
expect(url).toBe('/api/search/all-items?query=star+wars');
expect((init?.method ?? 'GET').toUpperCase()).toBe('GET');
});
});
describe('toAddItemsRequestFromSearch', () => {
it('fills every bucket, defaulting null arrays to []', () => {
expect(
toAddItemsRequestFromSearch({
artistIds: [],
episodeIds: [],
imageIds: [],
movieIds: [1, 2],
musicVideoIds: [],
otherVideoIds: [],
remoteStreamIds: [],
seasonIds: [],
showIds: [],
songIds: []
})
).toEqual({
artistIds: [],
episodeIds: [],
imageIds: [],
movieIds: [1, 2],
musicVideoIds: [],
otherVideoIds: [],
remoteStreamIds: [],
seasonIds: [],
showIds: [],
songIds: []
});
});
});