feat(app): 完成 P0 钱包解锁、下载落盘与手机号登录
This commit is contained in:
1 parent
de941c841c
commit
136ee74618
47 files changed
+3264
-70
No files matched your search
@@ -0,0 +1,149 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/models/download_license.dart';
|
||||
import 'package:fruitshorts/core/data/models/download_policy.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/download_repository.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_storage.dart';
|
||||
|
||||
void main() {
|
||||
test('should_mark_episode_downloaded_and_report_progress_when_download_finishes',
|
||||
() async {
|
||||
final repository = _RecordingRepository(_license('ep-1'));
|
||||
final storage = _FakeStorage();
|
||||
final controller = DownloadController(repository, storage: storage);
|
||||
await controller.load();
|
||||
|
||||
final result = await controller.startDownload('ep-1');
|
||||
|
||||
expect(result.ok, isTrue);
|
||||
expect(controller.isDownloaded('ep-1'), isTrue);
|
||||
expect(controller.localPathFor('ep-1'), 'file:///tmp/ep-1.mp4');
|
||||
expect(repository.statuses, containsAll(['DOWNLOADING', 'COMPLETED']));
|
||||
});
|
||||
|
||||
test('should_keep_local_file_when_backend_delete_fails', () async {
|
||||
final repository = _RecordingRepository(_license('ep-1'));
|
||||
final storage = _FakeStorage();
|
||||
final controller = DownloadController(repository, storage: storage);
|
||||
await controller.load();
|
||||
await controller.startDownload('ep-1');
|
||||
repository.failRemove = true;
|
||||
|
||||
final ok = await controller.remove('ep-1');
|
||||
|
||||
expect(ok, isFalse);
|
||||
expect(controller.isDownloaded('ep-1'), isTrue);
|
||||
});
|
||||
}
|
||||
|
||||
DownloadLicense _license(String episodeId) => DownloadLicense(
|
||||
licenseId: 'license-$episodeId',
|
||||
dramaId: 'drama-1',
|
||||
dramaTitle: 'The Signal',
|
||||
episodeId: episodeId,
|
||||
episodeNo: 1,
|
||||
deviceId: 'device-1',
|
||||
quality: 'AUTO',
|
||||
playUrl: '/files/episode/ep-1.mp4?e=1&s=abc',
|
||||
licenseKey: 'key',
|
||||
status: 'ACTIVE',
|
||||
grantedAt: DateTime(2026, 10, 19),
|
||||
expireAt: DateTime(2026, 11, 18),
|
||||
taskStatus: 'QUEUED',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: 0,
|
||||
errorMsg: '',
|
||||
);
|
||||
|
||||
class _FakeStorage implements DownloadStorage {
|
||||
final Set<String> files = {};
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> restore() async => {
|
||||
for (final id in files) id: 'file:///tmp/$id.mp4',
|
||||
};
|
||||
|
||||
@override
|
||||
Future<String?> localPathFor(String episodeId) async =>
|
||||
files.contains(episodeId) ? 'file:///tmp/$episodeId.mp4' : null;
|
||||
|
||||
@override
|
||||
Future<DownloadedMedia> download({
|
||||
required String episodeId,
|
||||
required String url,
|
||||
DownloadProgress? onProgress,
|
||||
}) async {
|
||||
onProgress?.call(50, 100);
|
||||
onProgress?.call(100, 100);
|
||||
files.add(episodeId);
|
||||
return DownloadedMedia(
|
||||
episodeId: episodeId,
|
||||
filePath: 'file:///tmp/$episodeId.mp4',
|
||||
bytes: 100,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String episodeId) async => files.remove(episodeId);
|
||||
|
||||
@override
|
||||
Future<void> clear() async => files.clear();
|
||||
}
|
||||
|
||||
class _RecordingRepository implements DownloadRepository {
|
||||
_RecordingRepository(this.license);
|
||||
|
||||
DownloadLicense license;
|
||||
bool failRemove = false;
|
||||
final List<String> statuses = [];
|
||||
|
||||
@override
|
||||
Future<DownloadPolicy> fetchPolicy() async => const DownloadPolicy(
|
||||
maxPerDevice: 20,
|
||||
licenseDays: 30,
|
||||
wifiOnly: false,
|
||||
maxQuality: 'AUTO',
|
||||
maxStorageBytes: 0,
|
||||
used: 1,
|
||||
remaining: 19,
|
||||
expirePolicyText: '30 days',
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<DownloadLicense>> list() async => [license];
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> grant(String episodeId, {String? quality}) async =>
|
||||
license;
|
||||
|
||||
@override
|
||||
Future<void> updateSettings({bool? wifiOnly, String? maxQuality}) async {}
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> refreshUrl(String episodeId) async => license;
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> reportProgress(
|
||||
String episodeId, {
|
||||
String? deviceId,
|
||||
String? status,
|
||||
int? progressPercent,
|
||||
int? downloadedBytes,
|
||||
int? totalBytes,
|
||||
String? errorMsg,
|
||||
}) async {
|
||||
if (status != null) statuses.add(status);
|
||||
return license;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String episodeId) async {
|
||||
if (failRemove) throw StateError('remove failed');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> clear() async => 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_storage.dart';
|
||||
|
||||
void main() {
|
||||
test('should_write_media_file_and_report_progress_when_download_completes',
|
||||
() async {
|
||||
final directory = await Directory.systemTemp.createTemp('fruit-download-');
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
final payload = Uint8List.fromList(List<int>.generate(4096, (i) => i % 251));
|
||||
server.listen((request) async {
|
||||
request.response.headers.contentLength = payload.length;
|
||||
request.response.add(payload);
|
||||
await request.response.close();
|
||||
});
|
||||
addTearDown(() async {
|
||||
await server.close(force: true);
|
||||
await directory.delete(recursive: true);
|
||||
});
|
||||
final storage = FileDownloadStorage(directoryProvider: () async => directory);
|
||||
final progress = <double>[];
|
||||
|
||||
final media = await storage.download(
|
||||
episodeId: 'ep-1',
|
||||
url: 'http://127.0.0.1:${server.port}/episode.mp4',
|
||||
onProgress: (received, total) => progress.add(total == 0 ? 0 : received / total),
|
||||
);
|
||||
|
||||
expect(File(media.filePath).existsSync(), isTrue);
|
||||
expect(media.bytes, payload.length);
|
||||
expect(progress.last, 1.0);
|
||||
final localPath = await storage.localPathFor('ep-1');
|
||||
expect(localPath, isNotNull);
|
||||
expect(File(localPath!).existsSync(), isTrue);
|
||||
await storage.delete('ep-1');
|
||||
expect(await storage.localPathFor('ep-1'), isNull);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user