249 lines
6.7 KiB
Dart
249 lines
6.7 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:yx_tracking_flutter/src/config/config_manager.dart';
|
|
import 'package:yx_tracking_flutter/src/core/analytics_core.dart';
|
|
import 'package:yx_tracking_flutter/src/core/scheduler.dart';
|
|
import 'package:yx_tracking_flutter/src/model/system_dim_info.dart';
|
|
import 'package:yx_tracking_flutter/src/network/api_client.dart';
|
|
import 'package:yx_tracking_flutter/src/storage/config_storage.dart';
|
|
import 'package:yx_tracking_flutter/src/storage/event_storage.dart';
|
|
import 'package:yx_tracking_flutter/yx_tracking_flutter.dart';
|
|
|
|
class _NoopInterceptor implements AnalyticsInterceptor {
|
|
@override
|
|
FutureOr<Event?> beforeSend(Event event) async => event;
|
|
|
|
@override
|
|
FutureOr<void> afterSend(Event event, SendResult result) async {}
|
|
}
|
|
|
|
class _MemoryEventStorage implements EventStorage {
|
|
final _items = <StoredEvent>[];
|
|
var _nextId = 1;
|
|
|
|
@override
|
|
Future<void> init() async {}
|
|
|
|
@override
|
|
Future<int> insert(Event event) async {
|
|
final stored = StoredEvent(
|
|
id: _nextId,
|
|
event: event,
|
|
createTime: event.createTime,
|
|
retryCount: event.retryCount,
|
|
);
|
|
_items.add(stored);
|
|
_nextId += 1;
|
|
return stored.id;
|
|
}
|
|
|
|
@override
|
|
Future<List<StoredEvent>> fetchBatch(int limit) async {
|
|
if (limit <= 0) return const <StoredEvent>[];
|
|
final copy = List<StoredEvent>.from(_items)
|
|
..sort((a, b) => a.createTime.compareTo(b.createTime));
|
|
return copy.take(limit).toList(growable: false);
|
|
}
|
|
|
|
@override
|
|
Future<List<StoredEvent>> fetchRecent(int limit) async {
|
|
if (limit <= 0) return const <StoredEvent>[];
|
|
final copy = List<StoredEvent>.from(_items)
|
|
..sort((a, b) => b.createTime.compareTo(a.createTime));
|
|
return copy.take(limit).toList(growable: false);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteByIds(List<int> ids) async {
|
|
if (ids.isEmpty) return;
|
|
_items.removeWhere((e) => ids.contains(e.id));
|
|
}
|
|
|
|
@override
|
|
Future<int> count() async => _items.length;
|
|
|
|
@override
|
|
Future<int> trimToMaxSize(int maxSize) async {
|
|
if (maxSize <= 0) {
|
|
final removed = _items.length;
|
|
_items.clear();
|
|
return removed;
|
|
}
|
|
final overflow = _items.length - maxSize;
|
|
if (overflow <= 0) return 0;
|
|
_items
|
|
..sort((a, b) => a.createTime.compareTo(b.createTime))
|
|
..removeRange(0, overflow);
|
|
return overflow;
|
|
}
|
|
|
|
@override
|
|
Future<int> deleteExpired(DateTime cutoff) async {
|
|
final before = _items.length;
|
|
_items.removeWhere((e) => !e.createTime.isAfter(cutoff));
|
|
return before - _items.length;
|
|
}
|
|
|
|
@override
|
|
Future<void> updateRetryCount(int id, int retryCount) async {
|
|
final index = _items.indexWhere((e) => e.id == id);
|
|
if (index < 0) return;
|
|
final current = _items[index];
|
|
_items[index] = current.copyWith(
|
|
retryCount: retryCount,
|
|
event: current.event.copyWith(retryCount: retryCount),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {}
|
|
}
|
|
|
|
class _FakeApiClient extends ApiClient {
|
|
_FakeApiClient(super.config);
|
|
|
|
int sent = 0;
|
|
|
|
@override
|
|
Future<void> sendBatch(List<Event> events) async {
|
|
sent += events.length;
|
|
}
|
|
}
|
|
|
|
class _MemoryConfigStorage implements ConfigStorage {
|
|
SystemDimInfo? _value;
|
|
|
|
@override
|
|
Future<void> init() async {}
|
|
|
|
@override
|
|
Future<void> saveSystemDimInfo(SystemDimInfo info) async {
|
|
_value = info;
|
|
}
|
|
|
|
@override
|
|
Future<SystemDimInfo?> loadSystemDimInfo() async => _value;
|
|
|
|
@override
|
|
Future<void> clear() async {
|
|
_value = null;
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {}
|
|
}
|
|
|
|
class _TestConfigManager extends ConfigManager {
|
|
_TestConfigManager({required super.config})
|
|
: super(
|
|
storage: _MemoryConfigStorage(),
|
|
refreshInterval: Duration.zero,
|
|
httpClient: null,
|
|
);
|
|
|
|
SystemDimInfo? _current;
|
|
|
|
@override
|
|
SystemDimInfo? get currentConfig => _current;
|
|
|
|
@override
|
|
Future<void> init() async {
|
|
_current ??= SystemDimInfo(
|
|
systemInfo: const SystemInfo(raw: <String, Object?>{}),
|
|
eventDefinitions: const <EventDefinition>[
|
|
EventDefinition(eventCode: 'FACADE_EVENT'),
|
|
EventDefinition(eventCode: 'SDK_METRICS_SEND'),
|
|
EventDefinition(eventCode: 'SDK_METRICS_QUEUE'),
|
|
],
|
|
tagDefinitions: const <TagDefinition>[],
|
|
sdkStrategy: const SdkStrategy(
|
|
enabled: true,
|
|
defaultSampleRate: 1,
|
|
eventSettings: <String, EventStrategy>{},
|
|
),
|
|
lastFetchedAt: DateTime.fromMillisecondsSinceEpoch(1),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> fetchAndCacheConfig({bool force = false}) async {}
|
|
|
|
@override
|
|
Future<void> forceRefresh() async {}
|
|
|
|
@override
|
|
Future<void> dispose() async {}
|
|
}
|
|
|
|
class _NoopScheduler extends Scheduler {
|
|
_NoopScheduler({required super.interval, required super.onTick});
|
|
|
|
@override
|
|
void start() {}
|
|
|
|
@override
|
|
void stop() {}
|
|
}
|
|
|
|
AnalyticsConfig _config() {
|
|
return AnalyticsConfig(
|
|
systemCode: 'TEST_APP',
|
|
endpointBaseUrl: 'https://example.com',
|
|
metricsReportInterval: Duration(days: 1),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('Analytics Facade', () {
|
|
test('可注入 core 并走通关键路径', () async {
|
|
final memoryStorage = _MemoryEventStorage();
|
|
final fakeApiClient = _FakeApiClient(_config());
|
|
|
|
final core = AnalyticsCore(
|
|
storageFactory: () => memoryStorage,
|
|
apiClientFactory: (_) => fakeApiClient,
|
|
configManagerFactory: (config) => _TestConfigManager(config: config),
|
|
deviceInfoCollector: () async => const DeviceInfo(
|
|
os: 'test-os',
|
|
model: 'test-model',
|
|
screenResolution: '100x200',
|
|
),
|
|
schedulerFactory: (interval, onTick) =>
|
|
_NoopScheduler(interval: interval, onTick: onTick),
|
|
randomDouble: () => 0,
|
|
now: () => DateTime.fromMillisecondsSinceEpoch(10),
|
|
);
|
|
|
|
Analytics.coreForTesting = core;
|
|
expect(Analytics.instance(), isA<Analytics>());
|
|
expect(Analytics.coreForTesting, same(core));
|
|
|
|
await Analytics.init(_config());
|
|
await Analytics.setUser(const UserInfo(userId: 1, userName: 'u'));
|
|
await Analytics.setDeviceInfo(
|
|
const DeviceInfo(os: 'o', model: 'm', screenResolution: '1x1'),
|
|
);
|
|
|
|
await Analytics.track(
|
|
'FACADE_EVENT',
|
|
eventParams: const <String, Object?>{'k': 1},
|
|
);
|
|
|
|
expect(await Analytics.cachedEventCount(), greaterThanOrEqualTo(1));
|
|
expect(await Analytics.cachedRecentEvents(limit: 5), isNotEmpty);
|
|
|
|
await Analytics.flush(force: true);
|
|
await Analytics.refreshConfig(force: false);
|
|
await Analytics.reportMetricsNow();
|
|
|
|
Analytics.addInterceptor(_NoopInterceptor());
|
|
Analytics.setDebug(enabled: true);
|
|
|
|
expect(fakeApiClient.sent, greaterThanOrEqualTo(1));
|
|
|
|
await Analytics.dispose();
|
|
});
|
|
});
|
|
}
|