mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-28 01:56:47 +00:00
7e9cb60763
Move the Subsonic + Navidrome protocol clients and the library IPC facade (subsonic*, navidrome*, library.ts + tests) from src/api/ into src/lib/api/, the feature-free infra layer (plan M4, decision §10.3: shared client core → lib/). Pure move: deep-path import specifiers @/api/* -> @/lib/api/* across ~150 consumers; no behaviour change. Domain REST (lyrics/events) and cover/analysis infra stay in src/api/ pending their own M4 placement. tsc 0, lint 0/0, full suite 319 files / 2353 tests green.
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import type { LibraryTrackDto } from '@/lib/api/library';
|
|
import { useAuthStore } from '@/store/authStore';
|
|
import { useLibraryIndexStore } from '@/store/libraryIndexStore';
|
|
import { useLocalPlaybackStore } from '@/store/localPlaybackStore';
|
|
import {
|
|
countLocalBrowsableTracks,
|
|
fetchOfflineLocalBrowsableSongPage,
|
|
offlineLocalBrowseEnabled,
|
|
} from '@/features/offline/utils/offlineLocalBrowse';
|
|
|
|
const { libraryGetTracksBatchChunkedMock } = vi.hoisted(() => ({
|
|
libraryGetTracksBatchChunkedMock: vi.fn(async (): Promise<LibraryTrackDto[]> => []),
|
|
}));
|
|
|
|
vi.mock('@/lib/api/library', () => ({
|
|
libraryGetTracksBatchChunked: libraryGetTracksBatchChunkedMock,
|
|
libraryGetTracksByAlbum: vi.fn(async () => []),
|
|
libraryAdvancedSearch: vi.fn(async () => ({ albums: [], artists: [], tracks: [] })),
|
|
}));
|
|
|
|
describe('offlineLocalBrowse', () => {
|
|
beforeEach(() => {
|
|
useAuthStore.setState({
|
|
activeServerId: 'srv-a',
|
|
servers: [{ id: 'srv-a', name: 'A', url: 'https://a.test', username: 'u', password: 'p' }],
|
|
});
|
|
useLibraryIndexStore.setState({ masterEnabled: true });
|
|
useLocalPlaybackStore.setState({ entries: {} });
|
|
libraryGetTracksBatchChunkedMock.mockReset();
|
|
libraryGetTracksBatchChunkedMock.mockResolvedValue([]);
|
|
});
|
|
|
|
it('offlineLocalBrowseEnabled requires index and local bytes', () => {
|
|
expect(offlineLocalBrowseEnabled('srv-a')).toBe(false);
|
|
useLocalPlaybackStore.setState({
|
|
entries: {
|
|
'a.test:t1': {
|
|
serverIndexKey: 'a.test',
|
|
trackId: 't1',
|
|
localPath: '/media/library/a.test/a/al/t1.mp3',
|
|
layoutFingerprint: 'fp',
|
|
sizeBytes: 1,
|
|
tier: 'library',
|
|
cachedAt: 1,
|
|
suffix: 'mp3',
|
|
},
|
|
},
|
|
});
|
|
expect(countLocalBrowsableTracks('srv-a')).toBe(1);
|
|
expect(offlineLocalBrowseEnabled('srv-a')).toBe(true);
|
|
});
|
|
|
|
it('fetchOfflineLocalBrowsableSongPage pages local bytes alphabetically', async () => {
|
|
useLocalPlaybackStore.setState({
|
|
entries: {
|
|
'a.test:t1': {
|
|
serverIndexKey: 'a.test',
|
|
trackId: 't1',
|
|
localPath: '/media/library/a.test/a/al/t1.mp3',
|
|
layoutFingerprint: 'fp',
|
|
sizeBytes: 1,
|
|
tier: 'library',
|
|
cachedAt: 1,
|
|
suffix: 'mp3',
|
|
},
|
|
'a.test:t2': {
|
|
serverIndexKey: 'a.test',
|
|
trackId: 't2',
|
|
localPath: '/media/library/a.test/a/al/t2.mp3',
|
|
layoutFingerprint: 'fp',
|
|
sizeBytes: 1,
|
|
tier: 'library',
|
|
cachedAt: 1,
|
|
suffix: 'mp3',
|
|
},
|
|
},
|
|
});
|
|
libraryGetTracksBatchChunkedMock.mockResolvedValue([
|
|
{
|
|
id: 't2', title: 'Beta', artist: 'A', album: 'Al', albumId: 'al-1',
|
|
durationSec: 1, serverId: 'srv-a', syncedAt: 1, rawJson: {},
|
|
},
|
|
{
|
|
id: 't1', title: 'Alpha', artist: 'A', album: 'Al', albumId: 'al-1',
|
|
durationSec: 1, serverId: 'srv-a', syncedAt: 1, rawJson: {},
|
|
},
|
|
]);
|
|
|
|
const page = await fetchOfflineLocalBrowsableSongPage('srv-a', 0, 1);
|
|
expect(page?.songs.map(s => s.id)).toEqual(['t1']);
|
|
expect(page?.hasMore).toBe(true);
|
|
});
|
|
|
|
});
|