feat(mobile): upload engine with preparation, batching states and retries
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/// 设备标识(DeviceId)测试(P3 计划 Task 6)。
|
||||
library;
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:photo_live/core/device_id.dart';
|
||||
|
||||
class MemoryDeviceIdStore implements DeviceIdStore {
|
||||
String? value;
|
||||
int writes = 0;
|
||||
|
||||
@override
|
||||
Future<String?> read() async => value;
|
||||
|
||||
@override
|
||||
Future<void> write(String v) async {
|
||||
writes++;
|
||||
value = v;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('首次生成:32 位小写十六进制并持久化', () async {
|
||||
final store = MemoryDeviceIdStore();
|
||||
final id = await DeviceId(store: store).value();
|
||||
|
||||
expect(DeviceId.kPattern.hasMatch(id), isTrue, reason: '形状:$id');
|
||||
expect(store.value, id);
|
||||
expect(store.writes, 1);
|
||||
});
|
||||
|
||||
test('懒缓存 + 跨实例稳定:同一存储只生成一次', () async {
|
||||
final store = MemoryDeviceIdStore();
|
||||
final a = DeviceId(store: store);
|
||||
final b = DeviceId(store: store);
|
||||
|
||||
final first = await a.value();
|
||||
// 同实例二次调用走缓存(不再写存储)。
|
||||
expect(await a.value(), first);
|
||||
expect(store.writes, 1);
|
||||
// 新实例从存储读取同一值(per-install 语义)。
|
||||
expect(await b.value(), first);
|
||||
expect(store.writes, 1);
|
||||
});
|
||||
|
||||
test('并发首次调用共享同一次生成(不重复落盘)', () async {
|
||||
final store = MemoryDeviceIdStore();
|
||||
final device = DeviceId(store: store);
|
||||
final results = await Future.wait(
|
||||
List.generate(5, (_) => device.value()),
|
||||
);
|
||||
expect(results.toSet().length, 1);
|
||||
expect(store.writes, 1);
|
||||
});
|
||||
|
||||
test('不同安装(不同存储)生成不同标识', () async {
|
||||
final a = await DeviceId(store: MemoryDeviceIdStore()).value();
|
||||
final b = await DeviceId(store: MemoryDeviceIdStore()).value();
|
||||
expect(a, isNot(b));
|
||||
});
|
||||
|
||||
test('存储中的非法值(长度/字符不符)重新生成', () async {
|
||||
final store = MemoryDeviceIdStore()..value = 'short';
|
||||
final id = await DeviceId(store: store).value();
|
||||
expect(DeviceId.kPattern.hasMatch(id), isTrue);
|
||||
expect(store.value, id); // 修正值写回
|
||||
});
|
||||
|
||||
test('存储读取失败(异常)按未生成处理', () async {
|
||||
final throwing = _ThrowingStore();
|
||||
final id = await DeviceId(store: throwing).value();
|
||||
expect(DeviceId.kPattern.hasMatch(id), isTrue);
|
||||
});
|
||||
|
||||
test('hexEncode:字节 → 小写两位十六进制', () {
|
||||
expect(
|
||||
DeviceId.hexEncode([0x00, 0x0f, 0x10, 0xff, 0xa5]),
|
||||
'000f10ffa5',
|
||||
);
|
||||
expect(DeviceId.hexEncode(List.filled(16, 0xab)).length, 32);
|
||||
});
|
||||
|
||||
test('可注入 Random(确定性生成便于复现)', () async {
|
||||
final fixed = _SequenceRandom();
|
||||
final id = await DeviceId(store: MemoryDeviceIdStore(), random: fixed)
|
||||
.value();
|
||||
expect(id, '0102030405060708090a0b0c0d0e0f10');
|
||||
});
|
||||
}
|
||||
|
||||
class _ThrowingStore implements DeviceIdStore {
|
||||
@override
|
||||
Future<String?> read() async => throw _Boom();
|
||||
|
||||
@override
|
||||
Future<void> write(String value) async {}
|
||||
}
|
||||
|
||||
class _Boom implements Exception {}
|
||||
|
||||
/// 固定序列 Random(测试确定性注入)。
|
||||
class _SequenceRandom implements Random {
|
||||
final List<int> _values = List<int>.generate(16, (i) => i + 1);
|
||||
int _cursor = 0;
|
||||
|
||||
@override
|
||||
int nextInt(int max) {
|
||||
final v = _values[_cursor % _values.length] % max;
|
||||
_cursor++;
|
||||
return v;
|
||||
}
|
||||
|
||||
@override
|
||||
bool nextBool() => true;
|
||||
|
||||
@override
|
||||
double nextDouble() => .5;
|
||||
}
|
||||
Reference in New Issue
Block a user