|
...
|
...
|
@@ -2,6 +2,7 @@ import 'dart:async'; |
|
|
|
|
|
|
|
import 'package:doublefeel_flutter/core/result/app_result.dart';
|
|
|
|
import 'package:doublefeel_flutter/core/services/health_raw_data_core_service.dart';
|
|
|
|
import 'package:doublefeel_flutter/core/services/health_raw_local_notification.dart';
|
|
|
|
import 'package:doublefeel_flutter/data/datasource/health/health_local_data_convert.dart';
|
|
|
|
import 'package:doublefeel_flutter/data/datasource/health/health_local_datasource.dart';
|
|
|
|
import 'package:doublefeel_flutter/data/models/enums/app_enums.dart';
|
|
...
|
...
|
@@ -527,6 +528,201 @@ void main() { |
|
|
|
});
|
|
|
|
|
|
|
|
test(
|
|
|
|
'startCoreCaculate sends hrv and sleep notifications for new non-first results',
|
|
|
|
() async {
|
|
|
|
final now = DateTime.now();
|
|
|
|
final day = DateTime(now.year, now.month, now.day);
|
|
|
|
final base = LocalHealthDataConvert.unixSeconds(day);
|
|
|
|
final api = _FakeHealthKitRawDataHostApi();
|
|
|
|
api.setPoints(HealthDataUploadType.hrv.type, [
|
|
|
|
_point(base + 120, 35),
|
|
|
|
_point(base + 420, 18),
|
|
|
|
_point(base + 720, 40),
|
|
|
|
]);
|
|
|
|
api.setPoints(HealthDataUploadType.heartRate.type, [
|
|
|
|
_point(base + 60, 68),
|
|
|
|
_point(base + 120, 70),
|
|
|
|
_point(base + 180, 72),
|
|
|
|
_point(base + 420, 90),
|
|
|
|
_point(base + 480, 88),
|
|
|
|
_point(base + 720, 76),
|
|
|
|
]);
|
|
|
|
api.setPoints(HealthDataUploadType.restingHeartRate.type, [
|
|
|
|
_point(base + 30, 60),
|
|
|
|
_point(base + 390, 62),
|
|
|
|
_point(base + 710, 61),
|
|
|
|
]);
|
|
|
|
final sleepStart = day.subtract(const Duration(hours: 1));
|
|
|
|
final sleepEnd = day.add(const Duration(hours: 7));
|
|
|
|
api.setSleepPoints([
|
|
|
|
HealthKitRawDataPoint(
|
|
|
|
dataType: 3,
|
|
|
|
startTime: LocalHealthDataConvert.unixSeconds(sleepStart),
|
|
|
|
endTime: LocalHealthDataConvert.unixSeconds(sleepEnd),
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
final store = _MemoryHealthRawStressLocalStore();
|
|
|
|
await store.upsertResult(
|
|
|
|
HealthRawStressCalculationResult(
|
|
|
|
userId: 42,
|
|
|
|
hrvStressPoints: [_hrvStressPoint(base + 10)],
|
|
|
|
realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
|
|
|
|
dailyStressPoints: const <HealthRawDailyStressPoint>[],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
await store.upsertSleepResults(
|
|
|
|
userId: 42,
|
|
|
|
results: [
|
|
|
|
HealthRawSleepResult(
|
|
|
|
userId: 42,
|
|
|
|
date: base - Duration.secondsPerDay,
|
|
|
|
startDate: base - Duration.secondsPerDay - 3600,
|
|
|
|
sleepScore: 80,
|
|
|
|
sleepState: 2,
|
|
|
|
inBedMinutes: 480,
|
|
|
|
awakMinutes: 10,
|
|
|
|
sleepMinutes: 470,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
final notificationDispatcher = _FakeHealthRawLocalNotificationDispatcher();
|
|
|
|
final service = HealthRawDataCoreService(
|
|
|
|
healthApi: _FakeHealthKitHostApi(),
|
|
|
|
rawDataApi: api,
|
|
|
|
localStore: store,
|
|
|
|
userIdProvider: () => 42,
|
|
|
|
uploadResultsAfterCalculation: false,
|
|
|
|
localNotificationDispatcher: notificationDispatcher,
|
|
|
|
);
|
|
|
|
|
|
|
|
await service.startCoreCaculate(
|
|
|
|
endTime: base + 800,
|
|
|
|
readChunkDays: 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(
|
|
|
|
notificationDispatcher.sentNotifications.where(
|
|
|
|
(e) => e.recordType == HealthRawLocalNotificationRecordType.hrv),
|
|
|
|
hasLength(1),
|
|
|
|
);
|
|
|
|
expect(
|
|
|
|
notificationDispatcher.sentNotifications.where(
|
|
|
|
(e) => e.recordType == HealthRawLocalNotificationRecordType.sleep,
|
|
|
|
),
|
|
|
|
hasLength(1),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(
|
|
|
|
'startCoreCaculate skips hrv and sleep notifications without new results',
|
|
|
|
() async {
|
|
|
|
const base = 1800000000;
|
|
|
|
final api = _FakeHealthKitRawDataHostApi();
|
|
|
|
final store = _MemoryHealthRawStressLocalStore();
|
|
|
|
await store.upsertResult(
|
|
|
|
HealthRawStressCalculationResult(
|
|
|
|
userId: 42,
|
|
|
|
hrvStressPoints: [_hrvStressPoint(base + 10)],
|
|
|
|
realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
|
|
|
|
dailyStressPoints: const <HealthRawDailyStressPoint>[],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
await store.upsertSleepResults(
|
|
|
|
userId: 42,
|
|
|
|
results: const [
|
|
|
|
HealthRawSleepResult(
|
|
|
|
userId: 42,
|
|
|
|
date: base + 20,
|
|
|
|
startDate: base,
|
|
|
|
sleepScore: 80,
|
|
|
|
sleepState: 2,
|
|
|
|
inBedMinutes: 480,
|
|
|
|
awakMinutes: 10,
|
|
|
|
sleepMinutes: 470,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
final notificationDispatcher = _FakeHealthRawLocalNotificationDispatcher();
|
|
|
|
final service = HealthRawDataCoreService(
|
|
|
|
healthApi: _FakeHealthKitHostApi(),
|
|
|
|
rawDataApi: api,
|
|
|
|
localStore: store,
|
|
|
|
userIdProvider: () => 42,
|
|
|
|
uploadResultsAfterCalculation: false,
|
|
|
|
localNotificationDispatcher: notificationDispatcher,
|
|
|
|
);
|
|
|
|
|
|
|
|
await service.startCoreCaculate(
|
|
|
|
endTime: base + 40,
|
|
|
|
readChunkDays: 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(
|
|
|
|
notificationDispatcher.sentNotifications.where(
|
|
|
|
(e) =>
|
|
|
|
e.recordType == HealthRawLocalNotificationRecordType.hrv ||
|
|
|
|
e.recordType == HealthRawLocalNotificationRecordType.sleep,
|
|
|
|
),
|
|
|
|
isEmpty,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(
|
|
|
|
'startCoreCaculate skips hrv and sleep notifications on first calculation',
|
|
|
|
() async {
|
|
|
|
final now = DateTime.now();
|
|
|
|
final day = DateTime(now.year, now.month, now.day);
|
|
|
|
final base = LocalHealthDataConvert.unixSeconds(day);
|
|
|
|
final api = _FakeHealthKitRawDataHostApi();
|
|
|
|
api.setPoints(HealthDataUploadType.hrv.type, [
|
|
|
|
_point(base + 120, 35),
|
|
|
|
_point(base + 420, 18),
|
|
|
|
]);
|
|
|
|
api.setPoints(HealthDataUploadType.heartRate.type, [
|
|
|
|
_point(base + 60, 68),
|
|
|
|
_point(base + 120, 70),
|
|
|
|
_point(base + 180, 72),
|
|
|
|
_point(base + 420, 90),
|
|
|
|
]);
|
|
|
|
api.setPoints(HealthDataUploadType.restingHeartRate.type, [
|
|
|
|
_point(base + 30, 60),
|
|
|
|
_point(base + 390, 62),
|
|
|
|
]);
|
|
|
|
final sleepStart = day.subtract(const Duration(hours: 1));
|
|
|
|
final sleepEnd = day.add(const Duration(hours: 7));
|
|
|
|
api.setSleepPoints([
|
|
|
|
HealthKitRawDataPoint(
|
|
|
|
dataType: 3,
|
|
|
|
startTime: LocalHealthDataConvert.unixSeconds(sleepStart),
|
|
|
|
endTime: LocalHealthDataConvert.unixSeconds(sleepEnd),
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
final notificationDispatcher = _FakeHealthRawLocalNotificationDispatcher();
|
|
|
|
final service = HealthRawDataCoreService(
|
|
|
|
healthApi: _FakeHealthKitHostApi(),
|
|
|
|
rawDataApi: api,
|
|
|
|
localStore: _MemoryHealthRawStressLocalStore(),
|
|
|
|
userIdProvider: () => 42,
|
|
|
|
uploadResultsAfterCalculation: false,
|
|
|
|
localNotificationDispatcher: notificationDispatcher,
|
|
|
|
);
|
|
|
|
|
|
|
|
await service.startCoreCaculate(
|
|
|
|
endTime: base + 800,
|
|
|
|
readChunkDays: 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(
|
|
|
|
notificationDispatcher.sentNotifications.where(
|
|
|
|
(e) =>
|
|
|
|
e.recordType == HealthRawLocalNotificationRecordType.hrv ||
|
|
|
|
e.recordType == HealthRawLocalNotificationRecordType.sleep,
|
|
|
|
),
|
|
|
|
isEmpty,
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test(
|
|
|
|
'startCoreCaculate uploads pending rows even when no new data calculates',
|
|
|
|
() async {
|
|
|
|
const base = 1800000000;
|
|
...
|
...
|
@@ -854,6 +1050,35 @@ class _FakeHealthKitHostApi extends HealthKitHostApi { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _FakeHealthRawLocalNotificationDispatcher
|
|
|
|
extends HealthRawLocalNotificationDispatcher {
|
|
|
|
final sentNotifications = <HealthRawLocalNotification>[];
|
|
|
|
var record = const HealthRawLocalNotificationRecord();
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<HealthRawLocalNotificationRecord> readRecord(int userId) async {
|
|
|
|
return record;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<bool> sendOne({
|
|
|
|
required int userId,
|
|
|
|
required HealthRawLocalNotification notification,
|
|
|
|
}) async {
|
|
|
|
sentNotifications.add(notification);
|
|
|
|
record = record.withNotification(notification);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> recordRealtimeStressTime({
|
|
|
|
required int userId,
|
|
|
|
required int recordTime,
|
|
|
|
}) async {
|
|
|
|
record = record.copyWith(lastRealtimeStressTime: recordTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ReadCall {
|
|
|
|
const _ReadCall(this.dataType, this.startTime, this.endTime);
|
|
|
|
|
...
|
...
|
|