test: add blacklist provider test

This commit is contained in:
Kingkor Roy Tirtho 2025-04-21 21:27:59 +06:00
parent 252edfb73f
commit 95ee8e694e
12 changed files with 267 additions and 143 deletions

View File

@ -25,7 +25,7 @@ import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';

View File

@ -23,7 +23,7 @@ import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/audio_player/querying_track_info.dart';
import 'package:spotube/provider/audio_player/state.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import 'package:spotube/utils/platform.dart';
class TrackTile extends HookConsumerWidget {

View File

@ -65,12 +65,12 @@ Future<void> main(List<String> rawArgs) async {
AppLogger.runZoned(() async {
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
getIt.registerSingleton(await SharedPreferences.getInstance());
getIt.registerSingletonWithDependencies(
() => KVStoreService.init(),
dependsOn: [SharedPreferences],
getIt.registerSingleton<SharedPreferences>(
await SharedPreferences.getInstance(),
);
getIt.registerSingleton(KVStoreService.init());
getIt.registerLazySingleton<AppDatabase>(() => AppDatabase());
getIt.registerSingleton(SpotubeAudioPlayer());
getIt.registerSingleton<WindowManager>(windowManager);
@ -81,8 +81,6 @@ Future<void> main(List<String> rawArgs) async {
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
MediaKit.ensureInitialized();
await migrateMacOsFromSandboxToNoSandbox();
// force High Refresh Rate on some Android devices (like One Plus)

View File

@ -10,7 +10,7 @@ import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
class ArtistCard extends HookConsumerWidget {
final Artist artist;

View File

@ -11,7 +11,7 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/authentication/authentication.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/primitive_utils.dart';

View File

@ -10,7 +10,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/titlebar/titlebar.dart';
import 'package:spotube/components/ui/button_tile.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import 'package:auto_route/auto_route.dart';
@RoutePage()

View File

@ -10,7 +10,7 @@ import 'package:spotube/extensions/track.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/audio_player/state.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import 'package:spotube/provider/discord_provider.dart';
import 'package:spotube/provider/server/sourced_track.dart';
import 'package:spotube/services/audio_player/audio_player.dart';

View File

@ -1,128 +0,0 @@
// ignore_for_file: unused_local_variable, unused_import
import 'package:drift/drift.dart';
import 'package:drift_dev/api/migrations.dart';
import 'package:spotube/models/database/database.dart';
import 'package:test/test.dart';
import 'generated/schema.dart';
import 'generated/schema_v1.dart' as v1;
import 'generated/schema_v2.dart' as v2;
void main() {
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
late SchemaVerifier verifier;
setUpAll(() {
verifier = SchemaVerifier(GeneratedHelper());
});
group('simple database migrations', () {
// These simple tests verify all possible schema updates with a simple (no
// data) migration. This is a quick way to ensure that written database
// migrations properly alter the schema.
const versions = GeneratedHelper.versions;
for (final (i, fromVersion) in versions.indexed) {
group('from $fromVersion', () {
for (final toVersion in versions.skip(i + 1)) {
test('to $toVersion', () async {
final schema = await verifier.schemaAt(fromVersion);
final db = AppDatabase(schema.newConnection());
await verifier.migrateAndValidate(db, toVersion);
await db.close();
});
}
});
}
});
// Simple tests ensure the schema is transformed correctly, but some
// migrations benefit from a test verifying that data is transformed correctly
// too. This is particularly true for migrations that change existing columns
// (e.g. altering their type or constraints). Migrations that only add tables
// or columns typically don't need these advanced tests.
// TODO: Check whether you have migrations that could benefit from these tests
// and adapt this example to your database if necessary:
test("migration from v1 to v2 does not corrupt data", () async {
// Add data to insert into the old database, and the expected rows after the
// migration.
final oldAuthenticationTableData = <v1.AuthenticationTableData>[];
final expectedNewAuthenticationTableData = <v2.AuthenticationTableData>[];
final oldBlacklistTableData = <v1.BlacklistTableData>[];
final expectedNewBlacklistTableData = <v2.BlacklistTableData>[];
final oldPreferencesTableData = <v1.PreferencesTableData>[];
final expectedNewPreferencesTableData = <v2.PreferencesTableData>[];
final oldScrobblerTableData = <v1.ScrobblerTableData>[];
final expectedNewScrobblerTableData = <v2.ScrobblerTableData>[];
final oldSkipSegmentTableData = <v1.SkipSegmentTableData>[];
final expectedNewSkipSegmentTableData = <v2.SkipSegmentTableData>[];
final oldSourceMatchTableData = <v1.SourceMatchTableData>[];
final expectedNewSourceMatchTableData = <v2.SourceMatchTableData>[];
final oldAudioPlayerStateTableData = <v1.AudioPlayerStateTableData>[];
final expectedNewAudioPlayerStateTableData =
<v2.AudioPlayerStateTableData>[];
final oldPlaylistTableData = <v1.PlaylistTableData>[];
final expectedNewPlaylistTableData = <v2.PlaylistTableData>[];
final oldPlaylistMediaTableData = <v1.PlaylistMediaTableData>[];
final expectedNewPlaylistMediaTableData = <v2.PlaylistMediaTableData>[];
final oldHistoryTableData = <v1.HistoryTableData>[];
final expectedNewHistoryTableData = <v2.HistoryTableData>[];
final oldLyricsTableData = <v1.LyricsTableData>[];
final expectedNewLyricsTableData = <v2.LyricsTableData>[];
await verifier.testWithDataIntegrity(
oldVersion: 1,
newVersion: 2,
createOld: v1.DatabaseAtV1.new,
createNew: v2.DatabaseAtV2.new,
openTestedDatabase: (x) => AppDatabase(),
createItems: (batch, oldDb) {
batch.insertAll(oldDb.authenticationTable, oldAuthenticationTableData);
batch.insertAll(oldDb.blacklistTable, oldBlacklistTableData);
batch.insertAll(oldDb.preferencesTable, oldPreferencesTableData);
batch.insertAll(oldDb.scrobblerTable, oldScrobblerTableData);
batch.insertAll(oldDb.skipSegmentTable, oldSkipSegmentTableData);
batch.insertAll(oldDb.sourceMatchTable, oldSourceMatchTableData);
batch.insertAll(
oldDb.audioPlayerStateTable, oldAudioPlayerStateTableData);
batch.insertAll(oldDb.playlistTable, oldPlaylistTableData);
batch.insertAll(oldDb.playlistMediaTable, oldPlaylistMediaTableData);
batch.insertAll(oldDb.historyTable, oldHistoryTableData);
batch.insertAll(oldDb.lyricsTable, oldLyricsTableData);
},
validateItems: (newDb) async {
expect(expectedNewAuthenticationTableData,
await newDb.select(newDb.authenticationTable).get());
expect(expectedNewBlacklistTableData,
await newDb.select(newDb.blacklistTable).get());
expect(expectedNewPreferencesTableData,
await newDb.select(newDb.preferencesTable).get());
expect(expectedNewScrobblerTableData,
await newDb.select(newDb.scrobblerTable).get());
expect(expectedNewSkipSegmentTableData,
await newDb.select(newDb.skipSegmentTable).get());
expect(expectedNewSourceMatchTableData,
await newDb.select(newDb.sourceMatchTable).get());
expect(expectedNewAudioPlayerStateTableData,
await newDb.select(newDb.audioPlayerStateTable).get());
expect(expectedNewPlaylistTableData,
await newDb.select(newDb.playlistTable).get());
expect(expectedNewPlaylistMediaTableData,
await newDb.select(newDb.playlistMediaTable).get());
expect(expectedNewHistoryTableData,
await newDb.select(newDb.historyTable).get());
expect(expectedNewLyricsTableData,
await newDb.select(newDb.lyricsTable).get());
},
);
});
}

View File

@ -0,0 +1,250 @@
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/vars.dart';
import 'package:spotube/models/current_playlist.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/blacklist/blacklist_provider.dart';
import '../create_container.dart';
void main() {
group('BlacklistProvider', () {
late ProviderContainer container;
setUp(() {
getIt.registerLazySingleton<AppDatabase>(() {
final database = AppDatabase(NativeDatabase.memory());
addTearDown(() {
database.close();
});
return database;
});
container = createContainer();
});
tearDown(() {
getIt.reset();
});
test('initially should return empty list', () async {
final blackList = container.read(blacklistProvider.future);
await expectLater(blackList, completion(isEmpty));
});
test('add should add item to blacklist', () async {
final blacklistRef =
container.listen(blacklistProvider.future, (_, __) {});
await expectLater(blacklistRef.read(), completion(isEmpty));
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: 'test',
elementType: BlacklistedType.track,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
await expectLater(blacklistRef.read(), completion(isNotEmpty));
await expectLater(
blacklistRef.read(),
completion(
contains(
predicate<BlacklistTableData>(
(e) =>
e.name == 'Test' &&
e.elementId == 'test' &&
e.elementType == BlacklistedType.track,
),
),
),
);
});
test('remove should remove item from blacklist', () async {
final blacklistRef =
container.listen(blacklistProvider.future, (_, __) {});
await expectLater(blacklistRef.read(), completion(isEmpty));
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: 'test',
elementType: BlacklistedType.track,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
await expectLater(blacklistRef.read(), completion(isNotEmpty));
final removeRes = container
.read(blacklistProvider.notifier)
.remove(item.elementId.value);
await expectLater(removeRes, completes);
await Future.delayed(const Duration(milliseconds: 100));
await expectLater(blacklistRef.read(), completion(isEmpty));
});
group('contains', () {
test('should be true if track exists', () async {
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: FakeData.track.id!,
elementType: BlacklistedType.track,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
final track = FakeData.track as TrackSimple;
final contains =
container.read(blacklistProvider.notifier).contains(track);
expect(contains, isTrue);
});
test('should be true if track does not exist but artist of track exists',
() async {
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: FakeData.track.artists!.first.id!,
elementType: BlacklistedType.artist,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
final contains =
container.read(blacklistProvider.notifier).contains(FakeData.track);
expect(contains, isTrue);
});
});
group('containsArtist', () {
test('should be true for artist that exists', () async {
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: FakeData.artist.id!,
elementType: BlacklistedType.artist,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
expect(
container
.read(blacklistProvider.notifier)
.containsArtist(FakeData.artist),
isTrue);
});
test('should be false for artist that is not blacklisted', () async {
await expectLater(container.read(blacklistProvider.future), completes);
expect(
container
.read(blacklistProvider.notifier)
.containsArtist(FakeData.artist),
isFalse,
);
});
});
group('filter', () {
test('should return non-blacklisted tracks only', () async {
final tracks = List.generate(
10,
(e) => Track.fromJson({
...FakeData.track.toJson(),
'id': 'test-$e',
}),
);
final blacklist = container.read(blacklistProvider.future);
await expectLater(blacklist, completion(isEmpty));
final item = BlacklistTableCompanion.insert(
id: const Value(20),
name: 'Test',
elementId: tracks.first.id!,
elementType: BlacklistedType.track,
);
final res = container.read(blacklistProvider.notifier).add(item);
await expectLater(res, completes);
await Future.delayed(const Duration(milliseconds: 100));
final filteredTracks =
container.read(blacklistProvider.notifier).filter(tracks);
expect(filteredTracks, isNotEmpty);
expect(filteredTracks.length, 9);
expect(
filteredTracks,
isNot(contains(
predicate<Track>(
(e) => e.id == tracks.first.id,
),
)),
);
});
});
test('filterPlaylist should not modify anything but tracks', () async {
final playlist = CurrentPlaylist(
id: "lol",
name: "name",
thumbnail: "thumbnail",
tracks: [],
);
final blacklist = container.read(blacklistProvider.future);
await Future.delayed(const Duration(milliseconds: 100));
await expectLater(blacklist, completion(isEmpty));
final res =
container.read(blacklistProvider.notifier).filterPlaylist(playlist);
expect(res.id, playlist.id);
expect(res.name, playlist.name);
expect(res.thumbnail, playlist.thumbnail);
});
});
}

View File

@ -32,7 +32,7 @@ void main() {
getIt.registerSingleton<SpotubeAudioPlayer>(MockSpotubeAudioPlayer());
getIt.registerSingleton<KVStoreService>(MockKVStoreService());
getIt.registerSingleton<WindowManager>(mockWindowManager);
getIt.registerLazySingleton(
getIt.registerLazySingleton<AppDatabase>(
() {
final database = AppDatabase(NativeDatabase.memory());
@ -69,8 +69,12 @@ void main() {
getIt.reset();
});
test('Initial value should be equal the default values', () {
test('Initial value should be equal the default values', () async {
when(() => audioPlayer.setAudioNormalization(any()))
.thenAnswer((_) async {});
final preferences = container.read(userPreferencesProvider);
await Future.delayed(const Duration(milliseconds: 500));
final defaultPreferences = PreferencesTable.defaults();
expect(preferences, defaultPreferences);

View File

@ -26,5 +26,5 @@ void main() {
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}, skip: true);
}