import { describe, it, expect } from 'vitest' import { selectPruneCandidates, selectUpgradeCandidates } from '../src/shared/retention' import { qualityRank, type MediaItem } from '@shared/ipc' const DAY = 86_400_000 const NOW = 1_700_000_000_000 function item(over: Partial): MediaItem { return { id: over.id ?? 'x', sourceId: 's', videoId: over.id ?? 'x', title: over.title ?? 'V', url: `https://y/${over.id ?? 'x'}`, playlistTitle: 'Uploads', playlistIndex: 1, downloaded: true, downloadedAt: NOW, filePath: `C:/out/${over.id ?? 'x'}.mp4`, ...over } } describe('selectPruneCandidates (retention)', () => { it('no policy → nothing pruned', () => { const items = [item({ id: 'a', downloadedAt: NOW - 100 * DAY })] expect(selectPruneCandidates(items, {}, NOW)).toEqual([]) }) it('maxAgeDays prunes items older than the cutoff', () => { const items = [ item({ id: 'fresh', downloadedAt: NOW - 5 * DAY }), item({ id: 'old', downloadedAt: NOW - 40 * DAY }) ] const pruned = selectPruneCandidates(items, { maxAgeDays: 30 }, NOW).map((i) => i.id) expect(pruned).toEqual(['old']) }) it('maxCount keeps the N most-recent, prunes the rest', () => { const items = [ item({ id: 'a', downloadedAt: NOW - 1 * DAY }), item({ id: 'b', downloadedAt: NOW - 2 * DAY }), item({ id: 'c', downloadedAt: NOW - 3 * DAY }), item({ id: 'd', downloadedAt: NOW - 4 * DAY }) ] const pruned = selectPruneCandidates(items, { maxCount: 2 }, NOW) .map((i) => i.id) .sort() expect(pruned).toEqual(['c', 'd']) }) it('age + count combine — an item over EITHER limit is pruned', () => { const items = [ item({ id: 'a', downloadedAt: NOW - 1 * DAY }), // within both item({ id: 'b', downloadedAt: NOW - 2 * DAY }), // within age, over count(1) item({ id: 'c', downloadedAt: NOW - 40 * DAY }) // over age ] const pruned = selectPruneCandidates(items, { maxAgeDays: 30, maxCount: 1 }, NOW) .map((i) => i.id) .sort() expect(pruned).toEqual(['b', 'c']) }) it('never prunes un-downloaded items or ones without a filePath', () => { const items = [ item({ id: 'pending', downloaded: false, filePath: undefined, downloadedAt: undefined }), item({ id: 'nofile', filePath: undefined }), item({ id: 'real', downloadedAt: NOW - 99 * DAY }) ] expect(selectPruneCandidates(items, { maxAgeDays: 30 }, NOW).map((i) => i.id)).toEqual(['real']) }) }) describe('selectUpgradeCandidates (retention)', () => { it('selects items downloaded below the target quality', () => { const items = [ item({ id: 'lo', downloadedQuality: '480p' }), item({ id: 'ok', downloadedQuality: '1080p' }), item({ id: 'hi', downloadedQuality: 'Best available' }) ] const up = selectUpgradeCandidates(items, '1080p', qualityRank).map((i) => i.id) expect(up).toEqual(['lo']) }) it('skips items with no recorded downloadedQuality (unknown, not re-churned)', () => { const items = [ item({ id: 'unknown', downloadedQuality: undefined }), item({ id: 'lo', downloadedQuality: '360p' }) ] expect(selectUpgradeCandidates(items, '720p', qualityRank).map((i) => i.id)).toEqual(['lo']) }) it('skips un-downloaded items', () => { const items = [item({ id: 'pending', downloaded: false, downloadedQuality: '360p' })] expect(selectUpgradeCandidates(items, 'Best available', qualityRank)).toEqual([]) }) it('a "Best" target upgrades anything not already Best', () => { const items = [ item({ id: 'a', downloadedQuality: '1080p' }), item({ id: 'b', downloadedQuality: 'Best available' }) ] expect(selectUpgradeCandidates(items, 'Best', qualityRank).map((i) => i.id)).toEqual(['a']) }) }) describe('qualityRank', () => { it('ranks Best above all numeric qualities', () => { expect(qualityRank('Best available')).toBeGreaterThan(qualityRank('1080p')) expect(qualityRank('Best')).toBeGreaterThan(qualityRank('320 kbps')) }) it('orders numeric qualities bigger-is-better', () => { expect(qualityRank('1080p')).toBeGreaterThan(qualityRank('720p')) expect(qualityRank('320 kbps')).toBeGreaterThan(qualityRank('128 kbps')) }) it('unknown labels rank lowest', () => { expect(qualityRank('mystery')).toBe(0) expect(qualityRank('mystery')).toBeLessThan(qualityRank('360p')) }) })