@Tags(['drift']) library; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:drift/drift.dart' show Value; import 'package:drift/native.dart' show NativeDatabase; import 'package:flutter_test/flutter_test.dart'; import 'package:minstrel/cache/audio_cache_manager.dart'; import 'package:minstrel/cache/db.dart'; // See sync_controller_test.dart for the same skip rationale. const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers'; AppDb _testDb() => AppDb(NativeDatabase.memory()); void main() { test('isCached returns false when no row exists', skip: _skipDrift, () async { final db = _testDb(); addTearDown(db.close); final mgr = AudioCacheManager( db: db, dioFactory: () async => Dio(), cacheDirFactory: () async => Directory.systemTemp.createTempSync(), ); expect(await mgr.isCached('nonexistent'), false); expect(await mgr.pathFor('nonexistent'), null); }); test('usageBytes sums sizeBytes across rows', skip: _skipDrift, () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); final mgr = AudioCacheManager( db: db, dioFactory: () async => Dio(), cacheDirFactory: () async => tmp, ); await db.batch((b) { b.insertAll(db.audioCacheIndex, [ AudioCacheIndexCompanion.insert( trackId: 'a', path: 'p', sizeBytes: 100, source: CacheSource.manual), AudioCacheIndexCompanion.insert( trackId: 'b', path: 'p', sizeBytes: 250, source: CacheSource.incidental), ]); }); expect(await mgr.usageBytes(), 350); }); test('rolling cap evicts non-liked LRU; liked protected', skip: _skipDrift, () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); final mgr = AudioCacheManager( db: db, dioFactory: () async => Dio(), cacheDirFactory: () async => tmp, ); Future mk(String id) async { final f = File('${tmp.path}/audio_cache/$id.mp3'); await f.create(recursive: true); await f.writeAsBytes(List.filled(100, 0)); } await mk('old'); await mk('new'); await mk('lik'); await db.batch((b) { b.insertAll(db.audioCacheIndex, [ // 'old' has the older lastPlayedAt → evicted first. AudioCacheIndexCompanion.insert( trackId: 'old', path: '${tmp.path}/audio_cache/old.mp3', sizeBytes: 100, source: CacheSource.incidental, lastPlayedAt: Value(DateTime(2020))), AudioCacheIndexCompanion.insert( trackId: 'new', path: '${tmp.path}/audio_cache/new.mp3', sizeBytes: 100, source: CacheSource.incidental, lastPlayedAt: Value(DateTime(2024))), AudioCacheIndexCompanion.insert( trackId: 'lik', path: '${tmp.path}/audio_cache/lik.mp3', sizeBytes: 100, source: CacheSource.incidental, lastPlayedAt: Value(DateTime(2019))), ]); }); final liked = {'lik'}; final u = await mgr.bucketUsage(liked); expect(u.liked, 100); expect(u.rolling, 200); // Rolling cap fits one 100-byte file; Liked cap huge. await mgr.evictBuckets( likedCap: 1 << 30, rollingCap: 100, liked: liked); expect(await mgr.isCached('old'), false); // oldest rolling, evicted expect(await mgr.isCached('new'), true); // newer rolling, kept expect(await mgr.isCached('lik'), true); // liked, protected }); test('clearAll removes everything including manual', skip: _skipDrift, () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); final mgr = AudioCacheManager( db: db, dioFactory: () async => Dio(), cacheDirFactory: () async => tmp, ); final f = File('${tmp.path}/audio_cache/man.mp3'); await f.create(recursive: true); await f.writeAsBytes(List.filled(50, 0)); await db.into(db.audioCacheIndex).insertOnConflictUpdate( AudioCacheIndexCompanion.insert( trackId: 'man', path: f.path, sizeBytes: 50, source: CacheSource.manual), ); expect(await mgr.usageBytes(), 50); await mgr.clearAll(); expect(await mgr.usageBytes(), 0); expect(await mgr.isCached('man'), false); }); test('unpin removes index row + deletes file', skip: _skipDrift, () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); final mgr = AudioCacheManager( db: db, dioFactory: () async => Dio(), cacheDirFactory: () async => tmp, ); final f = File('${tmp.path}/audio_cache/x.mp3'); await f.create(recursive: true); await f.writeAsBytes(List.filled(10, 0)); await db.into(db.audioCacheIndex).insertOnConflictUpdate( AudioCacheIndexCompanion.insert( trackId: 'x', path: f.path, sizeBytes: 10, source: CacheSource.autoPrefetch), ); expect(await mgr.isCached('x'), true); await mgr.unpin('x'); expect(await mgr.isCached('x'), false); expect(f.existsSync(), false); }); }