39a07a280c
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>
104 lines
3.4 KiB
TypeScript
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');
|
|
});
|
|
});
|