import { afterEach, describe, expect, it, vi } from 'vitest'; import { upsertTrack } from '../src/services/tracks'; import { json, routeFetch, useTestApp } from './helpers'; const app = useTestApp(); afterEach(() => vi.unstubAllGlobals()); const record = (over: object = {}) => ({ id: 70627, trackName: 'Midnight City', artistName: 'M83', duration: 241, instrumental: false, plainLyrics: 'Waiting in a car\nWaiting for a ride in the dark', syncedLyrics: '[00:01.00] Waiting in a car\n[00:04.50] Waiting for a ride in the dark', ...over, }); const lyrics = (id: string) => app().inject({ url: `/tracks/${id}/lyrics` }); describe('lyrics', () => { it('looks up synced lyrics once and serves them from the cache after', async () => { const { id } = await upsertTrack({ title: 'Midnight City', artist: 'M83', durationMs: 241_000 }); const calls: string[] = []; vi.stubGlobal('fetch', routeFetch([[/lrclib\.net\/api\/get\?/, (url) => (calls.push(url), json(record()))]])); const first = (await lyrics(id)).json(); expect(first.synced).toEqual([ { timeMs: 1000, text: 'Waiting in a car' }, { timeMs: 4500, text: 'Waiting for a ride in the dark' }, ]); expect(first.source.name).toBe('LRCLIB'); expect(new URL(calls[0]!).searchParams.get('duration')).toBe('241'); expect((await lyrics(id)).json()).toEqual(first); expect(calls).toHaveLength(1); }); it('falls back to a search, skipping recordings of a different length', async () => { const { id } = await upsertTrack({ title: 'Song', artist: 'Band', durationMs: 200_000 }); vi.stubGlobal( 'fetch', routeFetch([ [/\/api\/get\?/, () => json({ code: 404 }, 404)], [ /\/api\/search\?/, () => json([ record({ id: 1, trackName: 'Song', artistName: 'Band', duration: 320, plainLyrics: 'extended mix' }), record({ id: 2, trackName: 'Song', artistName: 'Band', duration: 201, syncedLyrics: null, plainLyrics: 'the one' }), ]), ], ]), ); const body = (await lyrics(id)).json(); expect(body.synced).toBeNull(); expect(body.plain).toBe('the one'); }); it('returns null when nothing matches, and 503 when LRCLIB is down', async () => { const { id } = await upsertTrack({ title: 'Unknown', artist: 'Nobody', durationMs: 100_000 }); vi.stubGlobal('fetch', routeFetch([[/lrclib/, () => json({ message: 'down' }, 500)]])); expect((await lyrics(id)).statusCode).toBe(503); vi.stubGlobal('fetch', routeFetch([[/\/api\/get\?/, () => json({}, 404)], [/\/api\/search\?/, () => json([])]])); const res = await lyrics(id); expect(res.statusCode).toBe(200); expect(res.json()).toBeNull(); expect((await lyrics('not-a-uuid')).statusCode).toBe(404); }); });