import { vi } from 'vitest' import config from '../config' import subsonic from './index' describe('getCoverArtUrl', () => { beforeEach(() => { // Mock window.location delete window.location window.location = { href: 'http://localhost:3000/app' } // Mock localStorage values required by subsonic const localStorageMock = { getItem: vi.fn((key) => { const values = { username: 'testuser', 'subsonic-token': 'testtoken', 'subsonic-salt': 'testsalt', } return values[key] || null }), setItem: vi.fn(), clear: vi.fn(), } Object.defineProperty(window, 'localStorage', { value: localStorageMock }) }) it('should return playlist cover art URL for records with sync property', () => { const playlistRecord = { id: 'playlist-123', sync: true, updatedAt: '2023-01-01T00:00:00Z', } const url = subsonic.getCoverArtUrl( playlistRecord, config.uiCoverArtSize, true, ) expect(url).toContain('pl-playlist-123') expect(url).toContain('size=600') expect(url).toContain('square=true') expect(url).toContain('_=2023-01-01T00%3A00%3A00Z') }) it('should add timestamp for playlists without updatedAt', () => { const playlistRecord = { id: 'playlist-123', sync: true, } const url = subsonic.getCoverArtUrl( playlistRecord, config.uiCoverArtSize, true, ) expect(url).toContain('pl-playlist-123') expect(url).toContain('size=600') expect(url).toContain('square=true') expect(url).not.toContain('_=') }) it('should return album cover art URL for records with albumArtist', () => { const albumRecord = { id: 'album-123', albumArtist: 'Test Artist', updatedAt: '2023-01-01T00:00:00Z', } const url = subsonic.getCoverArtUrl( albumRecord, config.uiCoverArtSize, true, ) expect(url).toContain('al-album-123') expect(url).toContain('size=600') expect(url).toContain('square=true') }) it('should return media file cover art URL for records with album', () => { const songRecord = { id: 'song-123', album: 'Test Album', updatedAt: '2023-01-01T00:00:00Z', } const url = subsonic.getCoverArtUrl(songRecord, config.uiCoverArtSize, true) expect(url).toContain('mf-song-123') expect(url).toContain('size=600') expect(url).toContain('square=true') }) it('should return artist cover art URL for other records', () => { const artistRecord = { id: 'artist-123', updatedAt: '2023-01-01T00:00:00Z', } const url = subsonic.getCoverArtUrl( artistRecord, config.uiCoverArtSize, true, ) expect(url).toContain('ar-artist-123') expect(url).toContain('size=600') expect(url).toContain('square=true') }) it('should handle records without updatedAt', () => { const record = { id: 'test-123', } const url = subsonic.getCoverArtUrl(record) expect(url).toContain('ar-test-123') expect(url).not.toContain('_=') }) it('appends the content hash to the artwork id', () => { const url = subsonic.getCoverArtUrl({ id: 'album-123', albumArtist: 'AA', imageHash: '0123456789abcdef', }) expect(url).toContain('al-album-123_0123456789abcdef') }) it('drops the updatedAt buster once the url is hash-versioned', () => { const url = subsonic.getCoverArtUrl({ id: 'album-123', albumArtist: 'AA', updatedAt: '2023-01-01T00:00:00Z', imageHash: '0123456789abcdef', }) expect(url).toContain('al-album-123_0123456789abcdef') expect(url).not.toContain('_=') }) it('keeps the updatedAt buster while artwork is unresolved', () => { const url = subsonic.getCoverArtUrl({ id: 'album-123', albumArtist: 'AA', updatedAt: '2023-01-01T00:00:00Z', }) expect(url).toContain('al-album-123') expect(url).toContain('_=') }) it('still builds a url for known-absent artwork so the server placeholder renders', () => { // Returning '' here made render as a broken icon; the server serves a // proper placeholder for absent art, so the client must still request it. const url = subsonic.getCoverArtUrl({ id: 'album-123', albumArtist: 'AA', imageAbsent: true, }) expect(url).toContain('al-album-123') }) }) describe('getDiscCoverArtUrl', () => { beforeEach(() => { const localStorageMock = { getItem: vi.fn((key) => { const values = { username: 'testuser', 'subsonic-token': 'testtoken', 'subsonic-salt': 'testsalt', } return values[key] || null }), } Object.defineProperty(window, 'localStorage', { value: localStorageMock }) }) it('should construct URL with dc-albumId:discNumber format, size, and cache param', () => { const url = subsonic.getDiscCoverArtUrl( 'album-123', 2, '2023-01-01T00:00:00Z', 48, ) expect(url).toContain('getCoverArt') expect(url).toContain('id=dc-album-123%3A2') expect(url).toContain('size=48') expect(url).toContain('_=2023-01-01T00%3A00%3A00Z') }) it('should handle missing updatedAt', () => { const url = subsonic.getDiscCoverArtUrl('album-123', 1, undefined, 48) expect(url).toContain('id=dc-album-123%3A1') expect(url).toContain('size=48') expect(url).not.toContain('_=') }) it('should handle missing size', () => { const url = subsonic.getDiscCoverArtUrl( 'album-123', 1, '2023-01-01T00:00:00Z', ) expect(url).toContain('id=dc-album-123%3A1') expect(url).toContain('_=2023-01-01T00%3A00%3A00Z') expect(url).not.toContain('size=') }) }) describe('getAvatarUrl', () => { beforeEach(() => { // Mock localStorage values required by subsonic const localStorageMock = { getItem: vi.fn((key) => { const values = { username: 'testuser', 'subsonic-token': 'testtoken', 'subsonic-salt': 'testsalt', } return values[key] || null }), } Object.defineProperty(window, 'localStorage', { value: localStorageMock }) }) it('should include username parameter', () => { const url = subsonic.getAvatarUrl('john') expect(url).toContain('getAvatar') expect(url).toContain('username=john') }) }) describe('reportPlayback', () => { beforeEach(() => { const localStorageMock = { getItem: vi.fn((key) => { const values = { username: 'testuser', 'subsonic-token': 'testtoken', 'subsonic-salt': 'testsalt', } return values[key] || null }), } Object.defineProperty(window, 'localStorage', { value: localStorageMock }) }) it('should construct reportPlayback URL with correct parameters', () => { const url = subsonic.url('reportPlayback', null, { mediaId: 'song-123', mediaType: 'song', positionMs: 5000, state: 'playing', }) expect(url).toContain('reportPlayback') expect(url).toContain('mediaId=song-123') expect(url).toContain('mediaType=song') expect(url).toContain('positionMs=5000') expect(url).toContain('state=playing') }) })