119 lines
4.1 KiB
Dart
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);
|
|
},
|
|
);
|
|
}
|