Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 9s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 9s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m52s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 12s
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 8s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 9m0s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 8m37s
New `/app/auto-tune` screen driving the PR1 endpoints: pick metadata axes (TV Shows / TV Genres / Movie Genres) + defaults, preview proposed channels grouped by axis with per-row/per-group selection and already-exists dedup, then bulk-create with a Created/Skipped/Failed summary. Additive/non-destructive. The per-channel DetailPanel from the design iteration is deferred to #383 (new endpoints backlogged) — this ships the 3-step wizard only. - web/src/api/autoTune.ts (+test), web/src/screens/AutoTuneScreen.tsx (+test) - route/nav (routes.tsx, ScreenContent.tsx), api barrel, App nav-smoke test - docs: domain-model (route), blazor-route-parity (net-new SPA screen) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createAutoTunedChannels, previewAutoTune } from './autoTune';
|
|
|
|
const sampleProposals = [
|
|
{ axis: 'TvShow', value: 'The Office', name: 'The Office', number: '500', itemCount: 201, alreadyExists: false },
|
|
{ axis: 'TvGenre', value: 'Comedy', name: 'Comedy', number: '501', itemCount: 640, alreadyExists: true }
|
|
];
|
|
|
|
const sampleResult = {
|
|
results: [{ name: 'The Office', status: 'Created', channelId: 80, reason: null }],
|
|
createdCount: 1,
|
|
skippedCount: 0,
|
|
failedCount: 0
|
|
};
|
|
|
|
describe('previewAutoTune', () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('POSTs /api/v1/channels/auto-tune/preview with the axes/minItems/startingNumber body', async () => {
|
|
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
|
|
new Response(JSON.stringify(sampleProposals), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status: 200
|
|
})
|
|
);
|
|
|
|
await expect(
|
|
previewAutoTune({ axes: ['TvShow', 'TvGenre'], minItems: 5, startingNumber: 500 })
|
|
).resolves.toHaveLength(2);
|
|
|
|
const [url, init] = fetchSpy.mock.calls[0];
|
|
expect(url).toBe('/api/v1/channels/auto-tune/preview');
|
|
expect((init?.method ?? 'GET').toUpperCase()).toBe('POST');
|
|
expect(JSON.parse(String(init?.body))).toEqual({
|
|
axes: ['TvShow', 'TvGenre'],
|
|
minItems: 5,
|
|
startingNumber: 500
|
|
});
|
|
});
|
|
|
|
it('rejects with the ApiError status on failure', async () => {
|
|
vi.spyOn(window, 'fetch').mockResolvedValue(
|
|
new Response(JSON.stringify({ status: 500, title: 'Server Error' }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status: 500
|
|
})
|
|
);
|
|
|
|
await expect(
|
|
previewAutoTune({ axes: ['TvShow'], minItems: 1, startingNumber: 1 })
|
|
).rejects.toMatchObject({ status: 500 });
|
|
});
|
|
});
|
|
|
|
describe('createAutoTunedChannels', () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('POSTs /api/v1/channels/auto-tune with templateId, group and echoed channels', async () => {
|
|
const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(
|
|
new Response(JSON.stringify(sampleResult), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
status: 200
|
|
})
|
|
);
|
|
|
|
await expect(
|
|
createAutoTunedChannels({
|
|
templateId: 3,
|
|
group: 'Auto-Tuned',
|
|
channels: [{ axis: 'TvShow', value: 'The Office', name: 'The Office', number: '500' }]
|
|
})
|
|
).resolves.toMatchObject({ createdCount: 1 });
|
|
|
|
const [url, init] = fetchSpy.mock.calls[0];
|
|
expect(url).toBe('/api/v1/channels/auto-tune');
|
|
expect((init?.method ?? 'GET').toUpperCase()).toBe('POST');
|
|
expect(JSON.parse(String(init?.body))).toEqual({
|
|
templateId: 3,
|
|
group: 'Auto-Tuned',
|
|
channels: [{ axis: 'TvShow', value: 'The Office', name: 'The Office', number: '500' }]
|
|
});
|
|
});
|
|
});
|