Files
reelbox/test/download_android_persistence_test.dart
T

119 lines
4.1 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
class _Storage implements PersistentStorage {
final failure = FileSystemException('fixture storage is read-only');
bool failWrites = true;
int writes = 0;
int? failWriteNumber;
final saved = <String, TaskRecord>{};
@override
Future<void> initialize() async {}
@override
Future<TaskRecord?> retrieveTaskRecord(String id) async => saved[id];
@override
Future<List<TaskRecord>> retrieveAllTaskRecords() async =>
saved.values.toList();
@override
Future<void> storeTaskRecord(TaskRecord record) async {
writes++;
if (failWrites || writes == failWriteNumber) throw failure;
saved[record.taskId] = record;
}
@override
Future<void> removePausedTask(String? id) async {}
@override
Future<void> removeResumeData(String? id) async {}
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test(
'failed native status persistence fails flush and later writes still run',
() async {
final previous = debugDefaultTargetPlatformOverride;
debugDefaultTargetPlatformOverride = TargetPlatform.android;
addTearDown(() => debugDefaultTargetPlatformOverride = previous);
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
const connectivity = MethodChannel(
'dev.fluttercommunity.plus/connectivity',
);
const connectivityEvents = MethodChannel(
'dev.fluttercommunity.plus/connectivity_status',
);
messenger.setMockMethodCallHandler(connectivity, (_) async => ['wifi']);
messenger.setMockMethodCallHandler(connectivityEvents, (_) async => null);
addTearDown(() {
messenger.setMockMethodCallHandler(connectivity, null);
messenger.setMockMethodCallHandler(connectivityEvents, null);
});
final storage = _Storage();
final unhandled = <Object>[];
final done = Completer<void>();
Object? observed;
Object? batchFailure;
var retried = false;
runZonedGuarded(() async {
final downloader = FileDownloader(persistentStorage: storage);
await downloader.ready;
final engine = downloader.downloaderForTesting;
engine.trackedGroups.add('fixture');
engine.groupStatusCallbacks['fixture'] = (_) {};
final task = DownloadTask(
taskId: 'read-only-status',
url: 'https://example.test/fixture.mp4',
group: 'fixture',
);
try {
engine.processStatusUpdate(
TaskStatusUpdate(task, TaskStatus.running),
);
try {
await engine.flushDatabaseUpdates();
} catch (error) {
observed = error;
}
storage.failWrites = false;
engine.processStatusUpdate(
TaskStatusUpdate(task, TaskStatus.running),
);
await engine.flushDatabaseUpdates();
retried = storage.saved[task.taskId]?.status == TaskStatus.running;
// One background message can queue status plus progress. A later
// successful write must not turn the whole flush into false success.
storage.failWriteNumber = storage.writes + 1;
engine.processStatusUpdate(
TaskStatusUpdate(task, TaskStatus.enqueued),
);
engine.processStatusUpdate(
TaskStatusUpdate(task, TaskStatus.running),
);
try {
await engine.flushDatabaseUpdates();
} catch (error) {
batchFailure = error;
}
await Future<void>.delayed(Duration.zero);
} finally {
downloader.destroy();
done.complete();
}
}, (error, stack) => unhandled.add(error));
await done.future;
expect(observed, same(storage.failure));
expect(batchFailure, same(storage.failure));
expect(unhandled, isEmpty);
expect(retried, isTrue);
},
);
}