Files
minstrel/web/src/lib/api/admin.cover-sources.test.ts
T
bvandeusen 39a07a280c feat(web/m7-cover-sources): cover-providers API client + query
Adds CoverProvider type + capability union, getCoverProviders /
updateCoverProvider / testCoverProvider against the three new admin
endpoints, plus createCoverProvidersQuery (60s staleTime; refetches
on focus + after mutations rather than polling — settings rarely
change in operator time).

New api.patch helper added to client.ts (the existing helpers were
get/post/put/del; PATCH was missing).

New qk.coverProviders() key follows the existing tuple pattern.
api_key_set: bool replaces echoing the credential. UpdateCoverProviderPatch
uses optional fields so the operator can change just enabled, just
key, or both atomically.

Vitest covers GET path, PATCH path with body, version_bumped
response, POST /test path with empty body, ok=false error response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:11:08 -04:00

104 lines
3.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
getCoverProviders,
updateCoverProvider,
testCoverProvider,
type CoverProvider,
type CoverProvidersResponse
} from './admin';
vi.mock('./client', () => ({
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn() }
}));
import { api } from './client';
describe('admin cover-sources API', () => {
beforeEach(() => vi.clearAllMocks());
it('getCoverProviders GETs the correct path', async () => {
const sample: CoverProvidersResponse = {
providers: [
{
id: 'mbcaa',
display_name: 'Cover Art Archive',
requires_api_key: false,
supports: ['album_cover'],
enabled: true,
api_key_set: false,
display_order: 0,
testable: true
}
],
sources_version: 1
};
(api.get as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(sample);
const got = await getCoverProviders();
expect(api.get).toHaveBeenCalledWith('/api/admin/cover-sources');
expect(got.providers[0].id).toBe('mbcaa');
expect(got.sources_version).toBe(1);
});
it('updateCoverProvider PATCHes the right path with patch body', async () => {
const patched: CoverProvider & { version_bumped: boolean } = {
id: 'theaudiodb',
display_name: 'TheAudioDB',
requires_api_key: true,
supports: ['album_cover', 'artist_thumb', 'artist_fanart'],
enabled: true,
api_key_set: true,
display_order: 1,
testable: true,
version_bumped: false
};
(api.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(patched);
const got = await updateCoverProvider('theaudiodb', { api_key: 'mykey' });
expect(api.patch).toHaveBeenCalledWith(
'/api/admin/cover-sources/theaudiodb',
{ api_key: 'mykey' }
);
expect(got.api_key_set).toBe(true);
expect(got.version_bumped).toBe(false);
});
it('updateCoverProvider with enabled toggle returns version_bumped: true', async () => {
const patched: CoverProvider & { version_bumped: boolean } = {
id: 'theaudiodb',
display_name: 'TheAudioDB',
requires_api_key: true,
supports: ['album_cover', 'artist_thumb', 'artist_fanart'],
enabled: false,
api_key_set: false,
display_order: 1,
testable: true,
version_bumped: true
};
(api.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(patched);
const got = await updateCoverProvider('theaudiodb', { enabled: false });
expect(got.version_bumped).toBe(true);
expect(got.enabled).toBe(false);
});
it('testCoverProvider POSTs to /test endpoint with empty body', async () => {
(api.post as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
ok: true,
duration_ms: 234
});
const got = await testCoverProvider('theaudiodb');
expect(api.post).toHaveBeenCalledWith('/api/admin/cover-sources/theaudiodb/test', {});
expect(got.ok).toBe(true);
expect(got.duration_ms).toBe(234);
});
it('testCoverProvider returns ok=false with error string on failure', async () => {
(api.post as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
ok: false,
duration_ms: 12,
error: 'connection refused'
});
const got = await testCoverProvider('theaudiodb');
expect(got.ok).toBe(false);
expect(got.error).toBe('connection refused');
});
});