mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-05 02:28:36 +02:00
205 lines
6.0 KiB
Dart
205 lines
6.0 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/providers/download_queue_provider.dart';
|
|
|
|
DownloadHistoryItem _historyItem({
|
|
required String id,
|
|
required String filePath,
|
|
required DateTime downloadedAt,
|
|
String albumName = 'Album',
|
|
}) {
|
|
return DownloadHistoryItem(
|
|
id: id,
|
|
trackName: 'Same Song',
|
|
artistName: 'Same Artist',
|
|
albumName: albumName,
|
|
filePath: filePath,
|
|
service: 'test',
|
|
downloadedAt: downloadedAt,
|
|
spotifyId: 'spotify:track:same',
|
|
isrc: 'SAMEISRC',
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('download history identity', () {
|
|
test('same track metadata does not merge files from different albums', () {
|
|
final first = _historyItem(
|
|
id: 'first',
|
|
filePath: '/music/First Album/Same Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 22),
|
|
albumName: 'First Album',
|
|
);
|
|
final second = _historyItem(
|
|
id: 'second',
|
|
filePath: '/music/Second Album/Same Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 23),
|
|
albumName: 'Second Album',
|
|
);
|
|
|
|
expect(historyItemsReferToSameStoredFile(first, second), isFalse);
|
|
});
|
|
|
|
test('equivalent paths still update one stored file record', () {
|
|
final original = _historyItem(
|
|
id: 'original',
|
|
filePath: '/music/Album/Same Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 22),
|
|
);
|
|
final converted = _historyItem(
|
|
id: 'converted',
|
|
filePath: '/music/Album/Same Song.opus',
|
|
downloadedAt: DateTime.utc(2026, 7, 23),
|
|
);
|
|
|
|
expect(historyItemsReferToSameStoredFile(original, converted), isTrue);
|
|
});
|
|
|
|
test('duplicate track identities keep the newest lookup item', () {
|
|
final newest = _historyItem(
|
|
id: 'newest',
|
|
filePath: '/music/New Album/Same Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 23),
|
|
albumName: 'New Album',
|
|
);
|
|
final older = _historyItem(
|
|
id: 'older',
|
|
filePath: '/music/Old Album/Same Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 22),
|
|
albumName: 'Old Album',
|
|
);
|
|
|
|
final state = DownloadHistoryState(
|
|
items: [newest, older],
|
|
lookupItems: [newest, older],
|
|
totalCount: 2,
|
|
);
|
|
|
|
expect(state.getBySpotifyId('spotify:track:same')?.id, 'newest');
|
|
expect(state.getByIsrc('SAMEISRC')?.id, 'newest');
|
|
expect(
|
|
state.findByTrackAndArtist('Same Song', 'Same Artist')?.id,
|
|
'newest',
|
|
);
|
|
expect(state.items, hasLength(2));
|
|
});
|
|
|
|
test('actual audio quality survives history serialization', () {
|
|
final item =
|
|
_historyItem(
|
|
id: 'quality',
|
|
filePath: '/music/Album/Hi-Res Song.flac',
|
|
downloadedAt: DateTime.utc(2026, 7, 23),
|
|
).copyWith(
|
|
quality: '24-bit/96kHz',
|
|
bitDepth: 24,
|
|
sampleRate: 96000,
|
|
format: 'flac',
|
|
);
|
|
|
|
final restored = DownloadHistoryItem.fromJson(item.toJson());
|
|
|
|
expect(restored.quality, '24-bit/96kHz');
|
|
expect(restored.bitDepth, 24);
|
|
expect(restored.sampleRate, 96000);
|
|
expect(restored.format, 'flac');
|
|
});
|
|
|
|
test('placeholder refresh cannot erase an existing measured quality', () {
|
|
expect(
|
|
resolvePersistedHistoryQuality(
|
|
incoming: 'HI_RES_LOSSLESS',
|
|
existing: '24-bit/96kHz',
|
|
),
|
|
'24-bit/96kHz',
|
|
);
|
|
expect(
|
|
resolvePersistedHistoryQuality(
|
|
incoming: '24-bit/192kHz',
|
|
existing: '24-bit/96kHz',
|
|
),
|
|
'24-bit/192kHz',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('startup orphan reconciliation', () {
|
|
test('requires consecutive missing checks before confirmation', () {
|
|
final firstMissing = reconcileStartupOrphanSuspects(
|
|
checkedIds: {'track'},
|
|
missingIds: {'track'},
|
|
previousSuspectIds: {},
|
|
);
|
|
expect(firstMissing.confirmedIds, isEmpty);
|
|
expect(firstMissing.pendingIds, {'track'});
|
|
|
|
final recovered = reconcileStartupOrphanSuspects(
|
|
checkedIds: {'track'},
|
|
missingIds: {},
|
|
previousSuspectIds: firstMissing.pendingIds,
|
|
);
|
|
expect(recovered.confirmedIds, isEmpty);
|
|
expect(recovered.pendingIds, isEmpty);
|
|
|
|
final missingAgain = reconcileStartupOrphanSuspects(
|
|
checkedIds: {'track'},
|
|
missingIds: {'track'},
|
|
previousSuspectIds: recovered.pendingIds,
|
|
);
|
|
expect(missingAgain.confirmedIds, isEmpty);
|
|
expect(missingAgain.pendingIds, {'track'});
|
|
|
|
final consecutiveMissing = reconcileStartupOrphanSuspects(
|
|
checkedIds: {'track'},
|
|
missingIds: {'track'},
|
|
previousSuspectIds: missingAgain.pendingIds,
|
|
);
|
|
expect(consecutiveMissing.confirmedIds, {'track'});
|
|
expect(consecutiveMissing.pendingIds, isEmpty);
|
|
});
|
|
|
|
test('ignores unchecked stale suspects', () {
|
|
final decision = reconcileStartupOrphanSuspects(
|
|
checkedIds: {'checked'},
|
|
missingIds: {'unchecked'},
|
|
previousSuspectIds: {'unchecked'},
|
|
);
|
|
|
|
expect(decision.confirmedIds, isEmpty);
|
|
expect(decision.pendingIds, isEmpty);
|
|
});
|
|
});
|
|
|
|
group('download completion persistence', () {
|
|
test('publishes completion only after history persistence', () async {
|
|
final events = <String>[];
|
|
|
|
await persistBeforePublishingDownloadCompletion(
|
|
persist: () async {
|
|
events.add('persist');
|
|
},
|
|
publish: () => events.add('publish'),
|
|
);
|
|
|
|
expect(events, ['persist', 'publish']);
|
|
});
|
|
|
|
test(
|
|
'does not publish completion when history persistence fails',
|
|
() async {
|
|
var published = false;
|
|
|
|
await expectLater(
|
|
persistBeforePublishingDownloadCompletion(
|
|
persist: () =>
|
|
Future<void>.error(StateError('database unavailable')),
|
|
publish: () => published = true,
|
|
),
|
|
throwsStateError,
|
|
);
|
|
|
|
expect(published, isFalse);
|
|
},
|
|
);
|
|
});
|
|
}
|