d7eaa189e2
T13 of the M5a Lidarr plan. Three new client modules wrap api.get/post/put/del
helpers and the existing TanStack Query qk namespace:
lidarr.ts - searchLidarr() + createLidarrSearchQuery()
requests.ts - createRequest, listMyRequests, getRequest, cancelRequest;
createMyRequestsQuery(); cancel goes through apiFetch
directly because the backend returns the cancelled row body
(api.del's return type is fixed to null).
admin.ts - getLidarrConfig, putLidarrConfig, testLidarrConnection,
listQualityProfiles, listRootFolders, listAdminRequests,
approveRequest, rejectRequest; query factories for each
read; quality profiles + root folders take an enabled prop
so the call site decides when Lidarr is configured.
Shared LidarrRequestStatus / LidarrRequestKind enums and request/config/
search-result shapes added to types.ts. Per-module helpers (CreateRequestParams)
stay in their module files. testLidarrConnection returns a discriminated union
({ok:true,version} | {ok:false,error}) and never throws on ok:false so the
SPA can render either branch.
qk extended with lidarrSearch, myRequests, lidarrConfig,
lidarrQualityProfiles, lidarrRootFolders, adminRequests.
Tests mirror likes.test.ts (vi.mock('./client')) and cover URL construction,
query-param encoding, body shapes, the not-ok testLidarrConnection branch,
and qk additions. 32 new tests, 217 total passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
|
|
|
vi.mock('./client', () => ({
|
|
api: {
|
|
get: vi.fn(),
|
|
post: vi.fn(),
|
|
put: vi.fn(),
|
|
del: vi.fn()
|
|
}
|
|
}));
|
|
|
|
import { searchLidarr } from './lidarr';
|
|
import { api } from './client';
|
|
import { qk } from './queries';
|
|
import type { LidarrSearchResult } from './types';
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('searchLidarr', () => {
|
|
test('builds URL with q and kind query params', async () => {
|
|
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
|
|
await searchLidarr('aphex twin', 'artist');
|
|
expect(api.get).toHaveBeenCalledWith('/api/lidarr/search?q=aphex+twin&kind=artist');
|
|
});
|
|
|
|
test('encodes special characters in q (quotes, ampersands, plus)', async () => {
|
|
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce([]);
|
|
await searchLidarr('boards of canada & friends', 'album');
|
|
const calledWith = (api.get as ReturnType<typeof vi.fn>).mock.calls[0][0] as string;
|
|
// URLSearchParams encodes space as '+' and '&' as '%26'.
|
|
expect(calledWith).toBe(
|
|
'/api/lidarr/search?q=boards+of+canada+%26+friends&kind=album'
|
|
);
|
|
});
|
|
|
|
test('passes through results unchanged', async () => {
|
|
const fixture: LidarrSearchResult[] = [
|
|
{
|
|
mbid: 'mbid-1',
|
|
name: 'Aphex Twin',
|
|
secondary_text: 'Electronic',
|
|
image_url: 'https://example.test/img.jpg',
|
|
artist_mbid: '',
|
|
album_mbid: '',
|
|
in_library: false,
|
|
requested: false
|
|
}
|
|
];
|
|
(api.get as ReturnType<typeof vi.fn>).mockResolvedValueOnce(fixture);
|
|
const out = await searchLidarr('aphex', 'artist');
|
|
expect(out).toBe(fixture);
|
|
});
|
|
|
|
test('propagates errors from api.get', async () => {
|
|
const err = { code: 'lidarr_unavailable', message: 'down', status: 503 };
|
|
(api.get as ReturnType<typeof vi.fn>).mockRejectedValueOnce(err);
|
|
await expect(searchLidarr('x', 'track')).rejects.toMatchObject({
|
|
code: 'lidarr_unavailable',
|
|
status: 503
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('qk.lidarrSearch', () => {
|
|
test('keys include q + kind', () => {
|
|
expect(qk.lidarrSearch('foo', 'artist')).toEqual([
|
|
'lidarrSearch',
|
|
{ q: 'foo', kind: 'artist' }
|
|
]);
|
|
});
|
|
});
|