import 'dart:io'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:melo/library/database.dart'; import 'package:melo/library/scan_service.dart'; import 'package:path/path.dart' as p; void main() { final fixtures = p.join(Directory.current.path, 'test', 'fixtures', 'audio'); late MeloDb db; late Directory coverDir; setUp(() async { db = MeloDb(NativeDatabase.memory()); coverDir = await Directory.systemTemp.createTemp('melo_covers'); }); tearDown(() async { await db.close(); await coverDir.delete(recursive: true); }); test('scanFolders liest Tags aus den Test-mp3s', () async { final count = await scanFolders(db, [fixtures], coverDir: coverDir); expect(count, 3); final songs = await db.watchSongs().first; expect( songs.map((s) => s.title).toSet(), {'Nachtpuls', 'Taglicht', 'Herzschlag'}, ); final nacht = songs.firstWhere((s) => s.title == 'Nachtpuls'); expect(nacht.artist, 'Rotklang'); expect(nacht.album, 'Schwarz'); expect(nacht.durationMs, greaterThan(0)); }); test('extrahiert eingebettetes Cover als Datei', () async { await scanFolders(db, [fixtures], coverDir: coverDir); final songs = await db.watchSongs().first; final withCover = songs.where((s) => s.coverPath != null).toList(); // Nur "Nachtpuls" hat ein eingebettetes Cover. expect(withCover.length, 1); expect(withCover.first.title, 'Nachtpuls'); expect(File(withCover.first.coverPath!).existsSync(), isTrue); }); test('Re-Scan behält die UUID (sync-stabil)', () async { await scanFolders(db, [fixtures], coverDir: coverDir); final firstIds = { for (final s in await db.watchSongs().first) s.path: s.id }; await scanFolders(db, [fixtures], coverDir: coverDir); final secondIds = { for (final s in await db.watchSongs().first) s.path: s.id }; expect(secondIds, firstIds); expect((await db.watchSongs().first).length, 3); // keine Duplikate }); test('CRITICAL-1: leerer Scan loescht nicht (nonexistent folder)', () async { // Initialer Scan await scanFolders(db, [fixtures], coverDir: coverDir); final initialCount = (await db.watchSongs().first).length; expect(initialCount, 3); // Scan mit nicht-existierendem Ordner (livePaths wird leer) await scanFolders(db, ['/this/does/not/exist'], coverDir: coverDir); // Alle Songs sollten noch da sein final afterEmpty = await db.watchSongs().first; expect(afterEmpty.length, initialCount); }); test('CRITICAL-2: Datei wird nach Tombstone wieder sichtbar', () async { // Initialer Scan: alle 3 Dateien await scanFolders(db, [fixtures], coverDir: coverDir); var songs = await db.watchSongs().first; expect(songs.length, 3); final nachtpulsId = songs.firstWhere((s) => s.title == 'Nachtpuls').id; // Manuell tombstonen await db.upsertSongs([ SongsCompanion.insert( id: nachtpulsId, path: p.join(fixtures, '01 Nachtpuls.mp3'), title: 'Nachtpuls', dateAddedMs: 1, updatedAtMs: 1, deleted: const Value(true), ), ]); songs = await db.watchSongs().first; expect(songs.where((s) => s.title == 'Nachtpuls').length, 0); // nicht sichtbar // Re-Scan sollte die Datei wieder finden und deleted=false setzen await scanFolders(db, [fixtures], coverDir: coverDir); songs = await db.watchSongs().first; expect(songs.where((s) => s.title == 'Nachtpuls').length, 1); // wieder da expect(songs.length, 3); // alle 3 Songs }); }