- 30 秒定时冲刷:AnalyticsService.startPeriodicFlush/stopPeriodicFlush, 前台期间 Timer.periodic 周期冲刷;SessionTracker 新增 onEnterForeground 回调,退后台停(并保留既有 flushNow 触发)、回前台恢复,App dispose 收尾。 - 失败退避:网络错误/5xx 后 30s→60s→120s 指数退避封顶 5 分钟,退避窗口 只挡定时冲刷(flushNow/满 20/冷启动显式触发不受限),上传成功即重置; 429 改按网络错误同路径保段重试(Retry-After 分支待后端限流落地)。 - anonymousId 持久化:restore 时从 shared_preferences 采用/落盘 pb.analytics.anonymousId,首次生成后跨冷启动稳定;读取失败降级 进程内临时 id 不崩溃。 - 测试 272 → 286(+14):fakeAsync 定时/退避 8 个、anonymousId 4 个、 429 保段 1 个、前后台回调成对 1 个;analyze 0 问题、format 无 diff。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -137,5 +137,72 @@ void main() {
|
||||
expect(batchSizes, [40, 20]);
|
||||
expect(service.pendingEvents, isEmpty);
|
||||
});
|
||||
|
||||
test('429 不按毒丸丢弃:保段待退避重试(限流分支随后端落地)', () async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final (server, batchSizes) = await startServer(429);
|
||||
addTearDown(() => server.close(force: true));
|
||||
final store = AnalyticsEventStore();
|
||||
await store.restore();
|
||||
final service = buildService('http://127.0.0.1:${server.port}', store);
|
||||
|
||||
for (var i = 0; i < 20; i++) {
|
||||
await service.trackEvent('auth_login_succeeded', {'attemptSeq': i});
|
||||
}
|
||||
|
||||
expect(batchSizes, [20]);
|
||||
expect(service.pendingEvents.length, 20);
|
||||
expect(store.droppedCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('AnalyticsService anonymousId 持久化', () {
|
||||
test('首次 restore 落盘生成的 anonymousId', () async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final service = buildService('http://unused', AnalyticsEventStore());
|
||||
|
||||
await service.restore();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(
|
||||
prefs.getString(AnalyticsService.anonymousIdKey),
|
||||
service.anonymousId,
|
||||
);
|
||||
});
|
||||
|
||||
test('冷启动新实例沿用持久化 anonymousId,事件跨启动可归并', () async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final serviceA = buildService('http://unused', AnalyticsEventStore());
|
||||
await serviceA.restore();
|
||||
final firstLaunchId = serviceA.anonymousId;
|
||||
|
||||
// 模拟冷启动:新实例构造时是新的随机 v4,restore 后采用存储值。
|
||||
final serviceB = buildService('http://unused', AnalyticsEventStore());
|
||||
expect(serviceB.anonymousId, isNot(firstLaunchId));
|
||||
await serviceB.restore();
|
||||
|
||||
expect(serviceB.anonymousId, firstLaunchId);
|
||||
await serviceB.trackEvent('auth_login_succeeded');
|
||||
expect(serviceB.pendingEvents.single['anonymousId'], firstLaunchId);
|
||||
});
|
||||
|
||||
test('构造注入 anonymousId 的测试通道不被持久化覆盖', () async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
AnalyticsService.anonymousIdKey: 'anon-stored',
|
||||
});
|
||||
final service = AnalyticsService(
|
||||
apiBaseUrl: 'http://unused',
|
||||
getAccessToken: null,
|
||||
getSessionId: () => 'session-x',
|
||||
anonymousId: 'anon-injected',
|
||||
store: AnalyticsEventStore(),
|
||||
);
|
||||
|
||||
await service.restore();
|
||||
|
||||
expect(service.anonymousId, 'anon-injected');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getString(AnalyticsService.anonymousIdKey), 'anon-stored');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user