From 85183c455a420ac6561143c9c3d3e7d10dd536e6 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 20 May 2026 18:02:17 -0400 Subject: [PATCH] test(flutter): re-enable drift tests after ci-flutter:1.26 ships libsqlite3-0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the libsqlite3-missing skip cohort now that the ci-flutter runner image installs libsqlite3-0 (CI-runner commit on its main). Per-file removals (no behavior change in tests themselves — they just stop being skipped): - `@Tags(['drift'])` + `library;` directive from 5 files. - `const _skipDrift = ...;` declaration + its rationale comment from 6 files (the 5 above + like_button_test.dart, which had its own _skipDrift for the rollback-via-drift case). - `skip: _skipDrift` annotations from 17 test invocations across those 6 files (16 single-line + 1 multi-line in like_button). - Stale `@Tags(['drift']) tier covers it` reference in home_screen_test.dart's drift-coverage comment. Net -79 +18 lines across 7 files; 17 previously-silent tests are now part of the CI signal. Fable #399. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../test/cache/audio_cache_manager_test.dart | 17 +++++----------- .../test/cache/sync_controller_test.dart | 20 ++++--------------- .../test/library/home_screen_test.dart | 3 +-- .../test/library/widgets_smoke_test.dart | 13 +----------- .../test/likes/like_button_test.dart | 8 +------- .../quarantine/quarantine_provider_test.dart | 19 ++++-------------- .../test/settings/storage_section_test.dart | 17 ++-------------- 7 files changed, 18 insertions(+), 79 deletions(-) diff --git a/flutter_client/test/cache/audio_cache_manager_test.dart b/flutter_client/test/cache/audio_cache_manager_test.dart index 433f5ae1..f0372894 100644 --- a/flutter_client/test/cache/audio_cache_manager_test.dart +++ b/flutter_client/test/cache/audio_cache_manager_test.dart @@ -1,6 +1,3 @@ -@Tags(['drift']) -library; - import 'dart:io'; import 'package:dio/dio.dart'; @@ -11,13 +8,10 @@ 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 { + test('isCached returns false when no row exists', () async { final db = _testDb(); addTearDown(db.close); final mgr = AudioCacheManager( @@ -29,7 +23,7 @@ void main() { expect(await mgr.pathFor('nonexistent'), null); }); - test('usageBytes sums sizeBytes across rows', skip: _skipDrift, () async { + test('usageBytes sums sizeBytes across rows', () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); @@ -55,8 +49,7 @@ void main() { expect(await mgr.usageBytes(), 350); }); - test('rolling cap evicts non-liked LRU; liked protected', - skip: _skipDrift, () async { + test('rolling cap evicts non-liked LRU; liked protected', () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); @@ -108,7 +101,7 @@ void main() { expect(await mgr.isCached('lik'), true); // liked, protected }); - test('clearAll removes everything including manual', skip: _skipDrift, () async { + test('clearAll removes everything including manual', () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); @@ -133,7 +126,7 @@ void main() { expect(await mgr.isCached('man'), false); }); - test('unpin removes index row + deletes file', skip: _skipDrift, () async { + test('unpin removes index row + deletes file', () async { final db = _testDb(); addTearDown(db.close); final tmp = Directory.systemTemp.createTempSync(); diff --git a/flutter_client/test/cache/sync_controller_test.dart b/flutter_client/test/cache/sync_controller_test.dart index f5288557..ac13f8c5 100644 --- a/flutter_client/test/cache/sync_controller_test.dart +++ b/flutter_client/test/cache/sync_controller_test.dart @@ -1,6 +1,3 @@ -@Tags(['drift']) -library; - import 'package:dio/dio.dart'; import 'package:drift/native.dart' show NativeDatabase; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -11,15 +8,6 @@ import 'package:minstrel/cache/db.dart'; import 'package:minstrel/cache/sync_controller.dart'; import 'package:minstrel/library/library_providers.dart' show dioProvider; -// SKIP NOTE (#357 plan B follow-up): drift's NativeDatabase needs the -// system libsqlite3.so. The flutter-ci runner image doesn't ship it, so -// every test in this file errors with "Failed to load dynamic library -// 'libsqlite3.so'". Unblock by either: -// (a) adding libsqlite3-dev to the CI-Runner/CI-flutter image, or -// (b) using sqlite3/wasm + the wasm executor for VM tests. -// Until then, real coverage lives in on-device verification. -const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers'; - /// Builds a Dio whose adapter resolves every request to the supplied /// status code + body. Avoids touching the network in tests. Dio _stubDio({required int status, dynamic body}) { @@ -42,7 +30,7 @@ ProviderContainer _container({required AppDb db, required Dio dio}) { } void main() { - test('204 advances lastSyncAt without changing cursor', skip: _skipDrift, () async { + test('204 advances lastSyncAt without changing cursor', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); @@ -56,7 +44,7 @@ void main() { expect(meta?.lastSyncAt, isNotNull); }); - test('200 with artist upsert writes drift row + advances cursor', skip: _skipDrift, () async { + test('200 with artist upsert writes drift row + advances cursor', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); @@ -85,7 +73,7 @@ void main() { expect(meta?.cursor, 7); }); - test('200 with track delete removes the row', skip: _skipDrift, () async { + test('200 with track delete removes the row', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); @@ -115,7 +103,7 @@ void main() { expect(track, isNull); }); - test('like_track upsert + delete round-trip', skip: _skipDrift, () async { + test('like_track upsert + delete round-trip', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); diff --git a/flutter_client/test/library/home_screen_test.dart b/flutter_client/test/library/home_screen_test.dart index 0212ad81..da1c1a58 100644 --- a/flutter_client/test/library/home_screen_test.dart +++ b/flutter_client/test/library/home_screen_test.dart @@ -12,8 +12,7 @@ import 'package:minstrel/playlists/playlists_provider.dart'; import 'package:minstrel/theme/theme_data.dart'; // All tests pump an empty HomeIndex unless they care about populated -// section IDs — per-tile hydration is intentionally not exercised here -// (that requires drift, and the @Tags(['drift']) tier covers it). +// section IDs — per-tile hydration is intentionally not exercised here. // What this suite verifies is the screen's section-level shape: // placeholders / empty-state copy / section presence given the index. const _emptyIndex = HomeIndex.empty; diff --git a/flutter_client/test/library/widgets_smoke_test.dart b/flutter_client/test/library/widgets_smoke_test.dart index 78d3dc30..599dba58 100644 --- a/flutter_client/test/library/widgets_smoke_test.dart +++ b/flutter_client/test/library/widgets_smoke_test.dart @@ -1,6 +1,3 @@ -@Tags(['drift']) -library; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,14 +8,6 @@ import 'package:minstrel/models/album.dart'; import 'package:minstrel/models/track.dart'; import 'package:minstrel/theme/theme_data.dart'; -// Same drift-cohort skip as the rest. TrackRow now contains -// CachedIndicator (ConsumerWidget), which reads -// audioCacheManagerProvider → constructs AppDb → drift_flutter calls -// driftDatabase() → libsqlite3.so isn't on the runner. Open a real -// async chain that leaves a pending timer at test teardown. -// See Fable #399. -const _skipDrift = true; - void main() { testWidgets('AlbumCard renders title and artist', (tester) async { await tester.pumpWidget(MaterialApp( @@ -39,7 +28,7 @@ void main() { expect(find.text('Boards of Canada'), findsOneWidget); }); - testWidgets('TrackRow shows mm:ss duration', skip: _skipDrift, (tester) async { + testWidgets('TrackRow shows mm:ss duration', (tester) async { // TrackRow now contains CachedIndicator (ConsumerWidget) so a // ProviderScope is required at the root. await tester.pumpWidget(ProviderScope( diff --git a/flutter_client/test/likes/like_button_test.dart b/flutter_client/test/likes/like_button_test.dart index 01505c4e..55e4a4ed 100644 --- a/flutter_client/test/likes/like_button_test.dart +++ b/flutter_client/test/likes/like_button_test.dart @@ -43,14 +43,8 @@ class _ThrowingLikesApi implements LikesApi { const Paged(items: [], total: 0, limit: 50, offset: 0); } -// libsqlite3 missing on flutter-ci runner; the rollback assertion now -// depends on drift writes via LikesController. Re-enable when the runner -// image carries libsqlite3-dev (Fable #399). -const _skipDrift = true; - void main() { - testWidgets('tap toggles icon optimistically; rollback on error', - skip: _skipDrift, (tester) async { + testWidgets('tap toggles icon optimistically; rollback on error', (tester) async { final api = _ThrowingLikesApi(); final container = ProviderContainer(overrides: [ likesApiProvider.overrideWith((ref) async => api), diff --git a/flutter_client/test/quarantine/quarantine_provider_test.dart b/flutter_client/test/quarantine/quarantine_provider_test.dart index 2da87322..cf6818d1 100644 --- a/flutter_client/test/quarantine/quarantine_provider_test.dart +++ b/flutter_client/test/quarantine/quarantine_provider_test.dart @@ -1,6 +1,3 @@ -@Tags(['drift']) -library; - import 'package:drift/native.dart' show NativeDatabase; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -11,13 +8,6 @@ import 'package:minstrel/cache/db.dart'; import 'package:minstrel/models/track.dart'; import 'package:minstrel/quarantine/quarantine_provider.dart'; -// SKIP NOTE: drift's NativeDatabase needs the system libsqlite3.so. -// The flutter-ci runner image doesn't ship it. Same skip as -// sync_controller_test.dart — unblock by adding libsqlite3-dev to the -// CI image, or switch to sqlite3/wasm. On-device runs cover real -// behaviour today. -const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers'; - class _StubQuarantineApi implements QuarantineApi { _StubQuarantineApi({this.shouldThrow = false}); bool shouldThrow; @@ -72,7 +62,7 @@ Future _seed(AppDb db) async { } void main() { - test('flag inserts a drift row and calls the server', skip: _skipDrift, + test('flag inserts a drift row and calls the server', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); @@ -92,8 +82,7 @@ void main() { expect(rows.first.reason, 'bad_rip'); }); - test('flag keeps drift optimistic + queues mutation on server failure', - skip: _skipDrift, () async { + test('flag keeps drift optimistic + queues mutation on server failure', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); final api = _StubQuarantineApi(shouldThrow: true); @@ -115,7 +104,7 @@ void main() { expect(mutations.first.kind, 'quarantine.flag'); }); - test('unflag deletes the drift row and calls the server', skip: _skipDrift, + test('unflag deletes the drift row and calls the server', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); @@ -132,7 +121,7 @@ void main() { expect(rows, isEmpty); }); - test('isHidden reflects drift state', skip: _skipDrift, () async { + test('isHidden reflects drift state', () async { final db = AppDb(NativeDatabase.memory()); addTearDown(db.close); await _seed(db); diff --git a/flutter_client/test/settings/storage_section_test.dart b/flutter_client/test/settings/storage_section_test.dart index f4711bb9..1722b722 100644 --- a/flutter_client/test/settings/storage_section_test.dart +++ b/flutter_client/test/settings/storage_section_test.dart @@ -1,6 +1,3 @@ -@Tags(['drift']) -library; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; @@ -13,22 +10,12 @@ import 'package:minstrel/theme/theme_data.dart'; class _MockStorage extends Mock implements FlutterSecureStorage {} -// StorageSection.initState calls audioCacheManagerProvider.usageBytes() -// which opens the AppDb via NativeDatabase. CI's flutter-ci runner -// lacks libsqlite3.so so this silently emits a warning today and would -// break under stricter analyze. Skipped with the drift cohort. -// -// testWidgets only accepts a bool for skip (unlike test() which takes -// a String reason). Reason: 'libsqlite3 missing on flutter-ci runner; -// on-device covers'. See Fable #399. -const _skipDrift = true; - void main() { setUpAll(() { registerFallbackValue(''); }); - testWidgets('renders Storage heading + all controls', skip: _skipDrift, (t) async { + testWidgets('renders Storage heading + all controls', (t) async { final storage = _MockStorage(); when(() => storage.read(key: any(named: 'key'))) .thenAnswer((_) async => null); @@ -57,7 +44,7 @@ void main() { expect(find.byKey(const Key('prefetch_selector')), findsOneWidget); }); - testWidgets('Clear cache button shows confirm dialog', skip: _skipDrift, (t) async { + testWidgets('Clear cache button shows confirm dialog', (t) async { final storage = _MockStorage(); when(() => storage.read(key: any(named: 'key'))) .thenAnswer((_) async => null);