2026-08-29 16:13:43 +02:00
|
|
|
import { describe, it, expect } from 'vitest'
|
|
|
|
|
import {
|
|
|
|
|
DiscogsClient,
|
|
|
|
|
DiscogsAuthError,
|
|
|
|
|
DiscogsRateLimitError,
|
|
|
|
|
mapSearchResult,
|
|
|
|
|
mapRelease,
|
|
|
|
|
} from '../src/discogs.js'
|
|
|
|
|
import { discogsSearchFixture, discogsReleaseFixture } from './fixtures.js'
|
|
|
|
|
|
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
|
|
|
return new Response(JSON.stringify(body), {
|
|
|
|
|
status,
|
|
|
|
|
headers: { 'content-type': 'application/json' },
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-29 16:24:40 +02:00
|
|
|
function stubFetch(routes: (url: string, init?: any) => Response): typeof fetch {
|
|
|
|
|
return (async (input: any, init?: any) => routes(String(input), init)) as typeof fetch
|
2026-08-29 16:13:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('DiscogsClient', () => {
|
|
|
|
|
it('searches by barcode with token and maps results', async () => {
|
|
|
|
|
const seen: string[] = []
|
2026-08-29 16:24:40 +02:00
|
|
|
const seenAuth: string[] = []
|
2026-08-29 16:13:43 +02:00
|
|
|
const c = new DiscogsClient(
|
|
|
|
|
'testtoken',
|
2026-08-29 16:24:40 +02:00
|
|
|
stubFetch((_url, init) => {
|
|
|
|
|
seen.push(_url)
|
|
|
|
|
seenAuth.push(String(new Headers(init?.headers).get('Authorization')))
|
2026-08-29 16:13:43 +02:00
|
|
|
return jsonResponse(discogsSearchFixture)
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
const results = await c.searchByBarcode('5021592210629')
|
|
|
|
|
expect(seen[0]).toContain('/database/search')
|
|
|
|
|
expect(seen[0]).toContain('barcode=5021592210629')
|
|
|
|
|
expect(seen[0]).toContain('type=release')
|
|
|
|
|
expect(seen[0]).toContain('token=testtoken')
|
2026-08-29 16:24:40 +02:00
|
|
|
expect(seenAuth[0]).toBe('Discogs token=testtoken')
|
2026-08-29 16:13:43 +02:00
|
|
|
expect(results).toHaveLength(2)
|
|
|
|
|
expect(results[0]).toEqual({
|
|
|
|
|
id: 1001,
|
|
|
|
|
artist: 'The Cinematic Orchestra',
|
|
|
|
|
title: 'Motion',
|
|
|
|
|
year: 1999,
|
|
|
|
|
formats: ['CD', 'Album'],
|
|
|
|
|
labels: ['Ninja Tune'],
|
|
|
|
|
country: 'UK',
|
|
|
|
|
catno: 'ZENCD012',
|
|
|
|
|
thumbUrl: 'https://img.discogs.com/small1.jpg',
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('searches by text with optional format filter', async () => {
|
|
|
|
|
const seen: string[] = []
|
|
|
|
|
const c = new DiscogsClient(
|
|
|
|
|
't',
|
|
|
|
|
stubFetch((url) => {
|
|
|
|
|
seen.push(url)
|
|
|
|
|
return jsonResponse(discogsSearchFixture)
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
await c.searchByText('motion', 'Vinyl')
|
|
|
|
|
expect(seen[0]).toContain('q=motion')
|
|
|
|
|
expect(seen[0]).toContain('format=Vinyl')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('throws DiscogsAuthError on 401', async () => {
|
|
|
|
|
const c = new DiscogsClient('bad', stubFetch(() => jsonResponse({ message: 'bad' }, 401)))
|
|
|
|
|
await expect(c.searchByBarcode('123')).rejects.toBeInstanceOf(DiscogsAuthError)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('throws DiscogsRateLimitError on 429', async () => {
|
|
|
|
|
const c = new DiscogsClient('t', stubFetch(() => jsonResponse({}, 429)))
|
|
|
|
|
await expect(c.searchByBarcode('123')).rejects.toBeInstanceOf(DiscogsRateLimitError)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('fetches full release with barcode identifiers', async () => {
|
|
|
|
|
const c = new DiscogsClient(
|
|
|
|
|
't',
|
|
|
|
|
stubFetch((url) => {
|
|
|
|
|
expect(url).toContain('/releases/1001')
|
|
|
|
|
return jsonResponse(discogsReleaseFixture)
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
const release = await c.getRelease(1001)
|
|
|
|
|
expect(release.title).toBe('Motion')
|
|
|
|
|
expect(release.barcodes).toEqual(['5021592210629'])
|
|
|
|
|
expect(release.coverUrl).toBe('https://img.discogs.com/full1.jpg')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('mappers', () => {
|
|
|
|
|
it('splits search title into artist/title and parses year', () => {
|
|
|
|
|
const first = discogsSearchFixture.results[0]!
|
|
|
|
|
const mapped = mapSearchResult(first)
|
|
|
|
|
expect(mapped.artist).toBe('The Cinematic Orchestra')
|
|
|
|
|
expect(mapped.title).toBe('Motion')
|
|
|
|
|
expect(mapped.year).toBe(1999)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('maps full release', () => {
|
|
|
|
|
const mapped = mapRelease(discogsReleaseFixture)
|
|
|
|
|
expect(mapped.formats).toEqual(['CD'])
|
|
|
|
|
expect(mapped.labels).toEqual(['Ninja Tune'])
|
|
|
|
|
expect(mapped.tracklist).toEqual([
|
|
|
|
|
{ position: '1', title: 'Overture' },
|
|
|
|
|
{ position: '2', title: 'Theme de Yoyo' },
|
|
|
|
|
])
|
|
|
|
|
})
|
2026-08-29 16:24:40 +02:00
|
|
|
|
|
|
|
|
it('does not throw on malformed payloads', () => {
|
|
|
|
|
expect(() => mapSearchResult(null)).not.toThrow()
|
|
|
|
|
expect(() => mapRelease(undefined)).not.toThrow()
|
|
|
|
|
expect(mapRelease({ tracklist: [null, { title: 'X' }] }).tracklist).toEqual([{ position: '', title: 'X' }])
|
|
|
|
|
expect(mapSearchResult({ format: [null, 'CD'] }).formats).toEqual(['CD'])
|
|
|
|
|
expect(new DiscogsAuthError().name).toBe('DiscogsAuthError')
|
|
|
|
|
expect(new DiscogsRateLimitError().name).toBe('DiscogsRateLimitError')
|
|
|
|
|
})
|
2026-08-29 16:13:43 +02:00
|
|
|
})
|