Commit 37deec526d7c61d6813a1bae61d1f0ca6c284d74

Authored by 权海
1 parent db377e19

feat(ui):ohos同一数据类型批量请求时修改为并发

... ... @@ -22,6 +22,7 @@ import '../../core/services/push_service.dart';
import '../../core/services/thinking_data_service.dart';
import '../../core/services/user_state_service.dart';
import '../../core/services/web_socket_service.dart';
import '../../core/util/app_toast.dart';
import '../../data/local/local_storage.dart';
import '../../data/local/user_account_storage.dart';
import '../../data/local/user_preferences_storage.dart';
... ... @@ -74,6 +75,7 @@ void registerUserSessionDeps() {
environmentConfig: environmentConfig,
userIdProvider: () => userPrefs.preferences.value.meUserInfo?.id ?? 0,
serverHealthApi: HealthApi(dioClient),
ohosSyncFailureToastSink: AppToast.show,
),
permanent: true,
);
... ...
... ... @@ -24,6 +24,7 @@ class HealthRawDataCoreService {
int Function()? userIdProvider,
bool uploadResultsAfterCalculation = true,
HealthApi? serverHealthApi,
void Function(String message)? ohosSyncFailureToastSink,
}) : _appleService = appleService ??
AppleHealthRawDataCoreService(
environmentConfig: environmentConfig,
... ... @@ -37,6 +38,7 @@ class HealthRawDataCoreService {
userIdProvider: userIdProvider,
uploadResultsAfterCalculation: uploadResultsAfterCalculation,
serverHealthApi: serverHealthApi,
toastSink: ohosSyncFailureToastSink,
),
_targetPlatform = targetPlatform;
... ...
... ... @@ -1484,9 +1484,8 @@ class AppleHealthRawDataCoreService {
}) async {
if (sleepIntervals.isEmpty) return const <HealthRawSleepResult>[];
final days = {
for (final interval in sleepIntervals)
DateTime.fromMillisecondsSinceEpoch(interval.endTime * 1000)
}.map((date) => DateTime(date.year, date.month, date.day)).toList()
for (final interval in sleepIntervals) _localDay(interval.endTime),
}.toList()
..sort((a, b) => a.compareTo(b));
final results = <HealthRawSleepResult>[];
for (final day in days) {
... ... @@ -1608,6 +1607,11 @@ class AppleHealthRawDataCoreService {
return date.year * 10000 + date.month * 100 + date.day;
}
static DateTime _localDay(int seconds) {
final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
return DateTime(date.year, date.month, date.day);
}
static (int startTime, int endTime) _dayRangeFromDateKey(int dateKey) {
final year = dateKey ~/ 10000;
final month = (dateKey ~/ 100) % 100;
... ...
... ... @@ -43,6 +43,8 @@ class OHOSHealthRawDataCoreService {
Future<bool> Function()? healthReadAuthorizationChecker,
OhosHealthRawResultUploadService? resultUploadService,
HealthRawLocalNotificationDispatcher? localNotificationDispatcher,
bool Function()? debugModeProvider,
void Function(String message)? toastSink,
}) : _rawDataSource = rawDataSource ??
OhosHealthRawDataSource(
userIdProvider: userIdProvider,
... ... @@ -58,6 +60,8 @@ class OHOSHealthRawDataCoreService {
(Get.isRegistered<HarmonyApi>() ? Get.find<HarmonyApi>() : null),
_healthReadAuthorizationChecker = healthReadAuthorizationChecker,
_resultUploadService = resultUploadService,
_debugModeProvider = debugModeProvider,
_toastSink = toastSink,
_localNotificationDispatcher = localNotificationDispatcher ??
HealthRawLocalNotificationDispatcher();
... ... @@ -70,13 +74,16 @@ class OHOSHealthRawDataCoreService {
final HarmonyApi? _harmonyApi;
final Future<bool> Function()? _healthReadAuthorizationChecker;
OhosHealthRawResultUploadService? _resultUploadService;
final bool Function()? _debugModeProvider;
final void Function(String message)? _toastSink;
final HealthRawLocalNotificationDispatcher _localNotificationDispatcher;
final StreamController<HealthRawDataUpdatedEvent>
_healthDataUpdatedController =
StreamController<HealthRawDataUpdatedEvent>.broadcast();
Future<HealthRawStressCalculationResult>? _coreCalculation;
bool get _isDebug => _environmentConfig?.isDebug ?? false;
bool get _isDebug =>
_debugModeProvider?.call() ?? _environmentConfig?.isDebug ?? false;
int get _userId {
final userId = _userIdProvider?.call() ?? 0;
... ... @@ -267,13 +274,6 @@ class OHOSHealthRawDataCoreService {
}) async {
var hasExistingHrv = false;
var hasExistingSleep = false;
final emptyResult = HealthRawStressCalculationResult(
userId: userId,
hrvStressPoints: const <HealthRawHrvStressPoint>[],
realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
dailyStressPoints: const <HealthRawDailyStressPoint>[],
sleepResults: const <HealthRawSleepResult>[],
);
try {
final hrvContextStart =
await _localStore.latestHrvSourceStartTime(userId);
... ... @@ -433,11 +433,7 @@ class OHOSHealthRawDataCoreService {
error,
stackTrace,
);
return _OhosStoredCalculationResult(
result: emptyResult,
hasExistingHrv: hasExistingHrv,
hasExistingSleep: hasExistingSleep,
);
rethrow;
}
}
... ... @@ -895,7 +891,6 @@ class OHOSHealthRawDataCoreService {
if (rawDataSource is! OhosHealthRawDataSource) {
return const <OhosHealthRawDataSyncResult>[];
}
await rawDataSource.getActivityGoal(refresh: true);
return rawDataSource.syncCalculationRawData(
startTime: startTime,
endTime: endTime,
... ... @@ -921,12 +916,15 @@ class OHOSHealthRawDataCoreService {
);
} catch (error, stackTrace) {
_logError(
'$_calculateLogMarker sync_failed_continue '
'$_calculateLogMarker sync_failed_abort '
'startTime=$startTime endTime=$endTime',
error,
stackTrace,
);
return const <OhosHealthRawDataSyncResult>[];
if (_isDebug) {
_toastSink?.call('数据同步失败,请刷新首页重试');
}
rethrow;
}
}
... ... @@ -1070,9 +1068,8 @@ class OHOSHealthRawDataCoreService {
return const <HealthRawSleepResult>[];
}
final days = {
for (final interval in sleepIntervals)
DateTime.fromMillisecondsSinceEpoch(interval.endTime * 1000)
}.map((date) => DateTime(date.year, date.month, date.day)).toList()
for (final interval in sleepIntervals) _localDay(interval.endTime),
}.toList()
..sort((a, b) => a.compareTo(b));
_logInfo(
'$_sleepCalcLogMarker days userId=$userId '
... ... @@ -1224,6 +1221,11 @@ class OHOSHealthRawDataCoreService {
return _dateKeyFromDateTime(date);
}
static DateTime _localDay(int seconds) {
final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
return DateTime(date.year, date.month, date.day);
}
static int _dateKeyFromDateTime(DateTime date) {
return date.year * 10000 + date.month * 100 + date.day;
}
... ...
... ... @@ -109,9 +109,10 @@ class OhosHealthRawDataSyncService {
);
final List<OhosHealthRawDataSyncResult> results;
try {
results = await Future.wait(
final activityGoalFuture = _remoteDataSource.fetchActivityGoal();
final rawFetchesFuture = Future.wait(
resolvedDataTypes.map(
(dataType) => syncRawData(
(dataType) => _fetchRawDataForSync(
dataType: dataType,
startTime: startTime,
endTime: resolvedEndTime,
... ... @@ -119,6 +120,27 @@ class OhosHealthRawDataSyncService {
),
eagerError: true,
);
final fetched = await Future.wait<Object?>(
<Future<Object?>>[
activityGoalFuture,
rawFetchesFuture,
],
eagerError: true,
);
final activityGoal = fetched[0] as V2ActivityTarget?;
final rawFetches = fetched[1] as List<_FetchedRawDataSync>;
if (activityGoal != null) {
await _localStore.upsertActivityGoal(activityGoal);
_log(
'calculation_sync_activity_goal_stored '
'move=${activityGoal.move} step=${activityGoal.step} '
'exercise=${activityGoal.exercise} stand=${activityGoal.stand}',
);
}
results = <OhosHealthRawDataSyncResult>[];
for (final fetch in rawFetches) {
results.add(await _storeFetchedRawData(fetch));
}
} catch (error, stackTrace) {
_log(
'calculation_sync_failed dataTypes=${resolvedDataTypes.join(',')} '
... ... @@ -142,6 +164,26 @@ class OhosHealthRawDataSyncService {
return results;
}
Future<_FetchedRawDataSync> _fetchRawDataForSync({
required int dataType,
required int? startTime,
required int endTime,
}) async {
final resolvedStartTime = await _resolveStartTime(
dataType: dataType,
requestedStartTime: startTime,
endTime: endTime,
);
if (endTime < resolvedStartTime) {
throw ArgumentError.value(endTime, 'endTime');
}
return _fetchResolvedRawData(
dataType: dataType,
startTime: resolvedStartTime,
endTime: endTime,
);
}
Future<List<OhosHealthRawDataItem>> queryRawData({
required int dataType,
required int startTime,
... ... @@ -187,6 +229,19 @@ class OhosHealthRawDataSyncService {
required int startTime,
required int endTime,
}) async {
final fetched = await _fetchResolvedRawData(
dataType: dataType,
startTime: startTime,
endTime: endTime,
);
return _storeFetchedRawData(fetched);
}
Future<_FetchedRawDataSync> _fetchResolvedRawData({
required int dataType,
required int startTime,
required int endTime,
}) async {
final fetchRanges = _splitIntoFetchRanges(
dataType: dataType,
startTime: startTime,
... ... @@ -199,72 +254,105 @@ class OhosHealthRawDataSyncService {
'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length}',
);
final segments = await Future.wait(
<Future<_FetchedRawDataSegment>>[
for (var index = 0; index < fetchRanges.length; index++)
_fetchRawDataSegment(
dataType: dataType,
range: fetchRanges[index],
segmentIndex: index,
segmentCount: fetchRanges.length,
fetchChunkDays: fetchChunkDays,
),
],
eagerError: true,
);
segments.sort((a, b) => a.segmentIndex.compareTo(b.segmentIndex));
return _FetchedRawDataSync(
dataType: dataType,
startTime: startTime,
endTime: endTime,
fetchChunkDays: fetchChunkDays,
segments: segments,
);
}
Future<_FetchedRawDataSegment> _fetchRawDataSegment({
required int dataType,
required OhosHealthRawDataFetchRange range,
required int segmentIndex,
required int segmentCount,
required int fetchChunkDays,
}) async {
_log(
'segment_start dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'chunkDays=$fetchChunkDays '
'startTime=${range.startTime} endTime=${range.endTime} '
'startDate=${_dateKeyFromUnixSeconds(range.startTime)} '
'endDate=${_dateKeyFromUnixSeconds(range.endTime)}',
);
final page = await _remoteDataSource.fetchRawDataPage(
dataType: dataType,
startTime: range.startTime,
endTime: range.endTime,
);
_log(
'page_fetched dataType=$dataType '
'segment=${segmentIndex + 1}/$segmentCount '
'items=${page.items.length}',
);
return _FetchedRawDataSegment(
segmentIndex: segmentIndex,
range: range,
page: page,
);
}
Future<OhosHealthRawDataSyncResult> _storeFetchedRawData(
_FetchedRawDataSync fetched,
) async {
var pageCount = 0;
var storedCount = 0;
for (var rangeIndex = 0; rangeIndex < fetchRanges.length; rangeIndex++) {
final range = fetchRanges[rangeIndex];
_log(
'segment_start dataType=$dataType '
'segment=${rangeIndex + 1}/${fetchRanges.length} '
'chunkDays=$fetchChunkDays '
'startTime=${range.startTime} endTime=${range.endTime} '
'startDate=${_dateKeyFromUnixSeconds(range.startTime)} '
'endDate=${_dateKeyFromUnixSeconds(range.endTime)}',
);
String? pageToken;
do {
final page = await _remoteDataSource.fetchRawDataPage(
dataType: dataType,
startTime: range.startTime,
endTime: range.endTime,
pageToken: pageToken,
for (final segment in fetched.segments) {
final page = segment.page;
pageCount += 1;
if (page.items.isNotEmpty) {
final pageStoredCount = await _localStore.upsertRawDataBatch(
dataType: fetched.dataType,
items: page.items,
);
pageCount += 1;
storedCount += pageStoredCount;
_log(
'page_fetched dataType=$dataType '
'segment=${rangeIndex + 1}/${fetchRanges.length} '
'page=$pageCount items=${page.items.length} '
'nextPageToken=${page.nextPageToken ?? ''}',
'page_stored dataType=${fetched.dataType} '
'segment=${segment.segmentIndex + 1}/${fetched.segmentCount} '
'fetchedItems=${page.items.length} '
'storedItems=$pageStoredCount totalStored=$storedCount',
);
if (page.items.isNotEmpty) {
final pageStoredCount = await _localStore.upsertRawDataBatch(
dataType: dataType,
items: page.items,
);
storedCount += pageStoredCount;
_log(
'page_stored dataType=$dataType '
'segment=${rangeIndex + 1}/${fetchRanges.length} '
'fetchedItems=${page.items.length} '
'storedItems=$pageStoredCount totalStored=$storedCount',
);
}
pageToken = page.nextPageToken;
} while (pageToken != null && pageToken.isNotEmpty);
}
_log(
'segment_finish dataType=$dataType '
'segment=${rangeIndex + 1}/${fetchRanges.length}',
'segment_finish dataType=${fetched.dataType} '
'segment=${segment.segmentIndex + 1}/${fetched.segmentCount}',
);
}
_log(
'sync_finish dataType=$dataType '
'startTime=$startTime endTime=$endTime '
'chunkDays=$fetchChunkDays rangeCount=${fetchRanges.length} '
'sync_finish dataType=${fetched.dataType} '
'startTime=${fetched.startTime} endTime=${fetched.endTime} '
'chunkDays=${fetched.fetchChunkDays} rangeCount=${fetched.segmentCount} '
'pageCount=$pageCount '
'storedCount=$storedCount',
);
return OhosHealthRawDataSyncResult(
dataType: dataType,
startTime: startTime,
endTime: endTime,
segmentCount: fetchRanges.length,
dataType: fetched.dataType,
startTime: fetched.startTime,
endTime: fetched.endTime,
segmentCount: fetched.segmentCount,
pageCount: pageCount,
storedCount: storedCount,
);
... ... @@ -350,7 +438,6 @@ abstract class OhosHealthRawDataRemoteDataSource {
required int dataType,
required int startTime,
required int endTime,
String? pageToken,
});
Future<V2ActivityTarget?> fetchActivityGoal();
... ... @@ -410,7 +497,6 @@ class OhosHarmonyHealthRawDataRemoteDataSource
required int dataType,
required int startTime,
required int endTime,
String? pageToken,
}) async {
final startDate = _dateKeyFromUnixSeconds(startTime);
final endDate = _endDateKeyFromUnixSeconds(endTime);
... ... @@ -552,7 +638,6 @@ class TodoOhosHealthRawDataRemoteDataSource
required int dataType,
required int startTime,
required int endTime,
String? pageToken,
}) async {
// TODO: Call the OHOS raw-data API after the endpoint and response schema
// are available.
... ... @@ -607,11 +692,9 @@ class TodoOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore {
class OhosHealthRawDataPage {
const OhosHealthRawDataPage({
required this.items,
this.nextPageToken,
});
final List<OhosHealthRawDataItem> items;
final String? nextPageToken;
}
class OhosHealthRawDataItem {
... ... @@ -636,6 +719,36 @@ class OhosHealthRawDataFetchRange {
final int endTime;
}
class _FetchedRawDataSync {
const _FetchedRawDataSync({
required this.dataType,
required this.startTime,
required this.endTime,
required this.fetchChunkDays,
required this.segments,
});
final int dataType;
final int startTime;
final int endTime;
final int fetchChunkDays;
final List<_FetchedRawDataSegment> segments;
int get segmentCount => segments.length;
}
class _FetchedRawDataSegment {
const _FetchedRawDataSegment({
required this.segmentIndex,
required this.range,
required this.page,
});
final int segmentIndex;
final OhosHealthRawDataFetchRange range;
final OhosHealthRawDataPage page;
}
class OhosHealthRawDataSyncResult {
const OhosHealthRawDataSyncResult({
required this.dataType,
... ...
... ... @@ -109,10 +109,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8"
sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.12.7"
version: "8.13.0"
cached_network_image:
dependency: "direct main"
description:
... ...
... ... @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_dat
import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_models.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ios/apple_health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/ohos_health_raw_data_sync_service.dart';
import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
import 'package:doublefeel_flutter/pigeon/health_kit_raw_data_api.g.dart';
import 'package:flutter_test/flutter_test.dart';
... ... @@ -54,6 +55,93 @@ void main() {
expect(store.dailyStressPoints, hasLength(1));
expect(store.sleepResults, hasLength(1));
});
test('OHOS core aborts calculation and shows debug toast when sync fails',
() async {
final store = _FakeHealthRawStressLocalStore();
final toasts = <String>[];
final service = OHOSHealthRawDataCoreService(
rawDataSource: OhosHealthRawDataSource(
syncService: OhosHealthRawDataSyncService(
remoteDataSource: _FailingOhosHealthRawDataRemoteDataSource(),
localStore: _FakeOhosHealthRawDataLocalStore(),
),
),
localStore: store,
userIdProvider: () => 42,
uploadResultsAfterCalculation: false,
healthReadAuthorizationChecker: () async => true,
debugModeProvider: () => true,
toastSink: toasts.add,
);
await expectLater(
service.syncAndStore(
startTime: DateTime.now()
.subtract(const Duration(days: 1))
.millisecondsSinceEpoch ~/
1000,
endTime: DateTime.now().millisecondsSinceEpoch ~/ 1000,
readChunkDays: 1,
),
throwsA(isA<StateError>()),
);
expect(toasts, ['数据同步失败,请刷新首页重试']);
expect(store.hrvStressPoints, isEmpty);
expect(store.realtimeStressPoints, isEmpty);
expect(store.dailyStressPoints, isEmpty);
expect(store.sleepResults, isEmpty);
});
}
class _FailingOhosHealthRawDataRemoteDataSource
implements OhosHealthRawDataRemoteDataSource {
@override
Future<OhosHealthRawDataPage> fetchRawDataPage({
required int dataType,
required int startTime,
required int endTime,
}) async {
throw StateError('failed dataType=$dataType');
}
@override
Future<V2ActivityTarget?> fetchActivityGoal() async {
return null;
}
}
class _FakeOhosHealthRawDataLocalStore implements OhosHealthRawDataLocalStore {
@override
Future<int?> latestDataTime({required int dataType}) async {
return null;
}
@override
Future<List<OhosHealthRawDataItem>> queryRawData({
required int dataType,
required int startTime,
required int endTime,
}) async {
return const <OhosHealthRawDataItem>[];
}
@override
Future<V2ActivityTarget?> queryActivityGoal() async {
return null;
}
@override
Future<void> upsertActivityGoal(V2ActivityTarget goal) async {}
@override
Future<int> upsertRawDataBatch({
required int dataType,
required List<OhosHealthRawDataItem> items,
}) async {
return items.length;
}
}
class _FakeHealthRawDataSource implements HealthRawDataSource {
... ...
... ... @@ -10,7 +10,7 @@ import 'package:doublefeel_flutter/data/models/health/health_v2_models.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('syncRawData starts from latest local data time and stores each page',
test('syncRawData starts from latest local data time and stores fetched data',
() async {
final remote = _FakeOhosHealthRawDataRemoteDataSource([
const OhosHealthRawDataPage(
... ... @@ -20,11 +20,6 @@ void main() {
dataTime: 1001,
payload: {'value': 70},
),
],
nextPageToken: 'next',
),
const OhosHealthRawDataPage(
items: [
OhosHealthRawDataItem(
dataType: 2,
dataTime: 1002,
... ... @@ -46,11 +41,10 @@ void main() {
expect(result.startTime, expectedStart);
expect(result.endTime, 2000);
expect(result.segmentCount, 1);
expect(result.pageCount, 2);
expect(result.pageCount, 1);
expect(result.storedCount, 2);
expect(remote.calls.map((e) => e.pageToken), [null, 'next']);
expect(remote.calls.every((e) => e.startTime == expectedStart), isTrue);
expect(local.storedBatches.map((e) => e.length), [1, 1]);
expect(local.storedBatches.map((e) => e.length), [2]);
});
test('syncRawData falls back to half-year lookback when local data is empty',
... ... @@ -212,6 +206,38 @@ void main() {
);
});
test('syncRawData fetches split ranges concurrently before storing',
() async {
final start = _unixSeconds(DateTime(2026, 1, 30, 10));
final end = _unixSeconds(DateTime(2026, 2, 22, 9));
final gate = Completer<void>();
final remote = _FakeOhosHealthRawDataRemoteDataSource(
const <OhosHealthRawDataPage>[],
gate: gate,
);
final local = _FakeOhosHealthRawDataLocalStore();
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: local,
);
final sync = service.syncRawData(
dataType: HuaweiHealthDataType.heartRate.dataType,
startTime: start,
endTime: end,
);
await Future<void>.delayed(Duration.zero);
expect(remote.calls, hasLength(3));
expect(local.storedBatches, isEmpty);
gate.complete();
final result = await sync;
expect(result.segmentCount, 3);
expect(result.pageCount, 3);
});
test('syncRawData joins duplicate in-flight syncs', () async {
final gate = Completer<void>();
final remote = _FakeOhosHealthRawDataRemoteDataSource(
... ... @@ -318,6 +344,81 @@ void main() {
expect(logs, contains(contains('calculation_sync_finish')));
});
test('syncCalculationRawData fetches data types concurrently', () async {
final gate = Completer<void>();
final remote = _FakeOhosHealthRawDataRemoteDataSource(
const <OhosHealthRawDataPage>[],
gate: gate,
);
final local = _FakeOhosHealthRawDataLocalStore();
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: local,
);
final sync = service.syncCalculationRawData(
startTime: 1000,
endTime: 2000,
dataTypes: const <int>[1, 2, OhosHealthRawDataType.sleepAnalysis],
);
await Future<void>.delayed(Duration.zero);
expect(remote.calls.map((call) => call.dataType).toSet(), {
1,
2,
OhosHealthRawDataType.sleepAnalysis,
});
expect(local.storedBatches, isEmpty);
gate.complete();
final results = await sync;
expect(results.map((result) => result.dataType), [
1,
2,
OhosHealthRawDataType.sleepAnalysis,
]);
});
test(
'syncCalculationRawData does not store raw data when activity goal fails',
() async {
final remote = _FakeOhosHealthRawDataRemoteDataSource(
const <OhosHealthRawDataPage>[
OhosHealthRawDataPage(
items: [
OhosHealthRawDataItem(
dataType: 1,
dataTime: 1001,
payload: {'value': 45},
),
],
),
],
failingActivityGoal: true,
);
final local = _FakeOhosHealthRawDataLocalStore();
final logs = <String>[];
final service = OhosHealthRawDataSyncService(
remoteDataSource: remote,
localStore: local,
logSink: logs.add,
);
await expectLater(
service.syncCalculationRawData(
startTime: 1000,
endTime: 2000,
dataTypes: const <int>[1],
),
throwsA(isA<StateError>()),
);
expect(local.storedBatches, isEmpty);
expect(local.activityGoal, isNull);
expect(logs, contains(contains('calculation_sync_failed')));
});
test('Harmony remote data source calls getHealthData and converts items',
() async {
final client = _FakeOhosHarmonyRawDataClient(
... ... @@ -350,7 +451,6 @@ void main() {
expect(client.healthCalls.single.dataType, HuaweiHealthDataType.hrv);
expect(client.healthCalls.single.startDate, 20260801);
expect(client.healthCalls.single.endDate, 20260831);
expect(page.nextPageToken, isNull);
expect(page.items, hasLength(1));
expect(page.items.single.dataType, 1);
expect(page.items.single.dataTime, 1787900000);
... ... @@ -470,14 +570,17 @@ class _FakeOhosHealthRawDataRemoteDataSource
Completer<void>? gate,
Set<int> failingDataTypes = const <int>{},
V2ActivityTarget? activityGoal,
bool failingActivityGoal = false,
}) : _gate = gate,
_failingDataTypes = failingDataTypes,
_activityGoal = activityGoal;
_activityGoal = activityGoal,
_failingActivityGoal = failingActivityGoal;
final List<OhosHealthRawDataPage> _pages;
final Completer<void>? _gate;
final Set<int> _failingDataTypes;
final V2ActivityTarget? _activityGoal;
final bool _failingActivityGoal;
final List<_FetchCall> calls = <_FetchCall>[];
@override
... ... @@ -485,14 +588,12 @@ class _FakeOhosHealthRawDataRemoteDataSource
required int dataType,
required int startTime,
required int endTime,
String? pageToken,
}) async {
calls.add(
_FetchCall(
dataType: dataType,
startTime: startTime,
endTime: endTime,
pageToken: pageToken,
),
);
if (_gate != null) {
... ... @@ -510,6 +611,9 @@ class _FakeOhosHealthRawDataRemoteDataSource
@override
Future<V2ActivityTarget?> fetchActivityGoal() async {
if (_failingActivityGoal) {
throw StateError('failed activity goal');
}
return _activityGoal;
}
}
... ... @@ -570,13 +674,11 @@ class _FetchCall {
required this.dataType,
required this.startTime,
required this.endTime,
required this.pageToken,
});
final int dataType;
final int startTime;
final int endTime;
final String? pageToken;
}
class _FakeOhosHarmonyRawDataClient implements OhosHarmonyRawDataClient {
... ...