import { test, expect } from 'bun:test'; import { Hono } from 'hono'; import { registerDownloadRetryRoute } from './download-retry.js'; function fixture(status = 'failed') { const app = new Hono(), calls = [], hints = []; let clock = 1000; registerDownloadRetryRoute(app, { media: { status: async () => ({ status }), ensureCached: async (id, options) => { calls.push([id, options]); } }, priority: 0, clearHints: id => hints.push(id), now: () => clock, log: { warn() {} }, }); return { app, calls, hints, advance: () => { clock += 16000; } }; } const request = app => app.request('/api/download/wZzRoXymOUU/retry', { method: 'POST' }); test('explicit retry starts a fresh guarded cache job and clears stale hints', async () => { const { app, calls, hints } = fixture(); const response = await request(app); expect(response.status).toBe(202); expect(await response.json()).toEqual({ ok: true, state: 'working' }); expect(calls).toEqual([['wZzRoXymOUU', { priority: 0, force: true }]]); expect(hints).toEqual(['wZzRoXymOUU']); expect(response.headers.get('cache-control')).toBe('no-store'); }); test('ready and in-progress songs are reused instead of redownloaded', async () => { for (const status of ['ready', 'queued', 'downloading', 'validating']) { const { app, calls } = fixture(status); expect((await request(app)).status).toBe(200); expect(calls).toHaveLength(0); } }); test('failed-job retries are bounded while later deliberate retries work', async () => { const f = fixture(); expect((await request(f.app)).status).toBe(202); const blocked = await request(f.app); expect(blocked.status).toBe(429); expect(blocked.headers.get('retry-after')).toBe('15'); expect(f.calls).toHaveLength(1); f.advance(); expect((await request(f.app)).status).toBe(202); expect(f.calls).toHaveLength(2); }); test('invalid ids never reach cache machinery and GET cannot force a retry', async () => { const { app, calls } = fixture(); expect((await app.request('/api/download/bad-id/retry', { method: 'POST' })).status).toBe(400); expect((await app.request('/api/download/wZzRoXymOUU/retry')).status).toBe(404); expect(calls).toHaveLength(0); }); test('asynchronous upstream failure is handled and remains available through status', async () => { const app = new Hono(), warnings = []; registerDownloadRetryRoute(app, { priority: 0, media: { status: async () => ({ status: 'failed' }), ensureCached: async () => { throw new Error('Video unavailable'); } }, log: { warn: detail => warnings.push(detail) } }); expect((await request(app)).status).toBe(202); expect(warnings[0]).toContain('Video unavailable'); });