feat(flutter): 接入分类记录收藏与下载授权
This commit is contained in:
1 parent
00280ca0da
commit
cc4d7c5a9d
52 files changed
+4440
-171
No files matched your search
@@ -0,0 +1,178 @@
|
||||
// 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/remote/api_exception.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/download_repository.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_controller.dart';
|
||||
|
||||
DownloadPolicy _policy({bool wifiOnly = true, int remaining = 19}) =>
|
||||
DownloadPolicy(
|
||||
maxPerDevice: 20,
|
||||
licenseDays: 30,
|
||||
wifiOnly: wifiOnly,
|
||||
maxQuality: 'AUTO',
|
||||
maxStorageBytes: 0,
|
||||
used: 20 - remaining,
|
||||
remaining: remaining,
|
||||
expirePolicyText: '30 days',
|
||||
);
|
||||
|
||||
DownloadLicense _license(String episodeId) => DownloadLicense(
|
||||
licenseId: 'license-$episodeId',
|
||||
dramaId: 'drama-1',
|
||||
dramaTitle: 'The Signal',
|
||||
episodeId: episodeId,
|
||||
episodeNo: 1,
|
||||
deviceId: 'device-9',
|
||||
quality: '720P',
|
||||
playUrl: '',
|
||||
licenseKey: '',
|
||||
status: 'ACTIVE',
|
||||
grantedAt: DateTime(2026, 10, 17),
|
||||
expireAt: DateTime(2026, 11, 16),
|
||||
taskStatus: 'QUEUED',
|
||||
progressPercent: 0,
|
||||
downloadedBytes: 0,
|
||||
totalBytes: 100,
|
||||
errorMsg: '',
|
||||
);
|
||||
|
||||
void main() {
|
||||
test('should_load_policy_and_licenses_when_page_opens', () async {
|
||||
final repository = ScriptedDownloadRepository(
|
||||
policy: _policy(),
|
||||
licenses: [_license('1')],
|
||||
);
|
||||
final controller = DownloadController(repository);
|
||||
|
||||
await controller.load();
|
||||
|
||||
expect(controller.loading, isFalse);
|
||||
expect(controller.error, isNull);
|
||||
expect(controller.policy?.remaining, 19);
|
||||
expect(controller.licenses.single.taskStatus, 'QUEUED');
|
||||
});
|
||||
|
||||
test('should_show_backend_message_when_grant_is_rejected', () async {
|
||||
final repository = ScriptedDownloadRepository(
|
||||
policy: _policy(),
|
||||
grantError: const ApiException(200003, 'download quota is full'),
|
||||
);
|
||||
final controller = DownloadController(repository);
|
||||
|
||||
final result = await controller.grant('episode-1');
|
||||
|
||||
expect(result.ok, isFalse);
|
||||
expect(result.message, 'download quota is full');
|
||||
});
|
||||
|
||||
test('should_rollback_wifi_setting_when_update_fails', () async {
|
||||
final repository = ScriptedDownloadRepository(
|
||||
policy: _policy(wifiOnly: true),
|
||||
failSettings: true,
|
||||
);
|
||||
final controller = DownloadController(repository);
|
||||
await controller.load();
|
||||
|
||||
final ok = await controller.updateSettings(wifiOnly: false);
|
||||
|
||||
expect(ok, isFalse);
|
||||
expect(controller.policy?.wifiOnly, isTrue);
|
||||
});
|
||||
|
||||
test('should_restore_license_when_delete_fails', () async {
|
||||
final repository = ScriptedDownloadRepository(
|
||||
policy: _policy(),
|
||||
licenses: [_license('1'), _license('2')],
|
||||
failRemove: true,
|
||||
);
|
||||
final controller = DownloadController(repository);
|
||||
await controller.load();
|
||||
|
||||
final ok = await controller.remove('1');
|
||||
|
||||
expect(ok, isFalse);
|
||||
expect(controller.licenses.map((item) => item.episodeId), ['1', '2']);
|
||||
});
|
||||
|
||||
test('should_restore_licenses_when_clear_fails', () async {
|
||||
final repository = ScriptedDownloadRepository(
|
||||
policy: _policy(),
|
||||
licenses: [_license('1'), _license('2')],
|
||||
failClear: true,
|
||||
);
|
||||
final controller = DownloadController(repository);
|
||||
await controller.load();
|
||||
|
||||
final ok = await controller.clear();
|
||||
|
||||
expect(ok, isFalse);
|
||||
expect(controller.licenses, hasLength(2));
|
||||
});
|
||||
}
|
||||
|
||||
class ScriptedDownloadRepository implements DownloadRepository {
|
||||
ScriptedDownloadRepository({
|
||||
required this.policy,
|
||||
this.licenses = const [],
|
||||
this.grantError,
|
||||
this.failSettings = false,
|
||||
this.failRemove = false,
|
||||
this.failClear = false,
|
||||
});
|
||||
|
||||
DownloadPolicy policy;
|
||||
List<DownloadLicense> licenses;
|
||||
ApiException? grantError;
|
||||
bool failSettings;
|
||||
bool failRemove;
|
||||
bool failClear;
|
||||
|
||||
@override
|
||||
Future<DownloadPolicy> fetchPolicy() async => policy;
|
||||
|
||||
@override
|
||||
Future<List<DownloadLicense>> list() async => licenses;
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> grant(String episodeId, {String? quality}) async {
|
||||
if (grantError != null) throw grantError!;
|
||||
return _license(episodeId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateSettings({bool? wifiOnly, String? maxQuality}) async {
|
||||
if (failSettings) throw StateError('settings unavailable');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> refreshUrl(String episodeId) async =>
|
||||
_license(episodeId);
|
||||
|
||||
@override
|
||||
Future<DownloadLicense> reportProgress(
|
||||
String episodeId, {
|
||||
String? deviceId,
|
||||
String? status,
|
||||
int? progressPercent,
|
||||
int? downloadedBytes,
|
||||
int? totalBytes,
|
||||
String? errorMsg,
|
||||
}) async => _license(episodeId);
|
||||
|
||||
@override
|
||||
Future<void> remove(String episodeId) async {
|
||||
if (failRemove) throw StateError('remove unavailable');
|
||||
licenses = licenses.where((item) => item.episodeId != episodeId).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> clear() async {
|
||||
if (failClear) throw StateError('clear unavailable');
|
||||
final count = licenses.length;
|
||||
licenses = const [];
|
||||
return count;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user