import { describe, it, expect } from 'vitest' import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' import { cacheArtwork } from '../src/artwork.js' function tempDir(): string { return mkdtempSync(path.join(tmpdir(), 'rs-artwork-')) } function imageFetch(calls: { count: number }): typeof fetch { return (async () => { calls.count++ return new Response(new Uint8Array([0xff, 0xd8, 0xff, 0xe0]), { status: 200, headers: { 'content-type': 'image/jpeg' }, }) }) as typeof fetch } describe('cacheArtwork', () => { it('downloads and stores by url hash with content-type extension', async () => { const dir = tempDir() try { const calls = { count: 0 } const file = await cacheArtwork(dir, 'https://img.discogs.com/a.jpg', imageFetch(calls)) expect(file).toMatch(/^[0-9a-f]{64}\.jpg$/) expect(existsSync(path.join(dir, file as string))).toBe(true) } finally { rmSync(dir, { recursive: true, force: true }) } }) it('second call for same url does not refetch', async () => { const dir = tempDir() try { const calls = { count: 0 } const fetcher = imageFetch(calls) await cacheArtwork(dir, 'https://img.discogs.com/a.jpg', fetcher) const again = await cacheArtwork(dir, 'https://img.discogs.com/a.jpg', fetcher) expect(calls.count).toBe(1) expect(again).not.toBeNull() } finally { rmSync(dir, { recursive: true, force: true }) } }) it('returns null for empty url and fetch failures without throwing', async () => { const dir = tempDir() try { expect(await cacheArtwork(dir, '', imageFetch({ count: 0 }))).toBeNull() const failing = (async () => { throw new TypeError('fetch failed') }) as unknown as typeof fetch expect(await cacheArtwork(dir, 'https://x/y.jpg', failing)).toBeNull() const notFound = (async () => new Response('nope', { status: 404 })) as unknown as typeof fetch expect(await cacheArtwork(dir, 'https://x/y.jpg', notFound)).toBeNull() } finally { rmSync(dir, { recursive: true, force: true }) } }) it('stores non-jpeg types with correct extension', async () => { const dir = tempDir() try { const fetcher = (async () => new Response(new Uint8Array([0x89, 0x50]), { status: 200, headers: { 'content-type': 'image/png' }, })) as typeof fetch const file = await cacheArtwork(dir, 'https://img.discogs.com/a.png', fetcher) expect(file).toMatch(/\.png$/) expect(readFileSync(path.join(dir, file as string)).length).toBeGreaterThan(0) } finally { rmSync(dir, { recursive: true, force: true }) } }) it('returns null when the response body fails mid-download', async () => { const dir = tempDir() try { const fetcher = (async () => new Response(new ReadableStream({ start(controller) { controller.enqueue(new Uint8Array([0xff])) controller.error(new TypeError('network error mid-body')) }, }), { status: 200, headers: { 'content-type': 'image/jpeg' } })) as unknown as typeof fetch expect(await cacheArtwork(dir, 'https://x/abort.jpg', fetcher)).toBeNull() } finally { rmSync(dir, { recursive: true, force: true }) } }) })