Merge branch 'train-days' into integration
# Conflicts: # apps/train/halkyon_train_flutter/lib/src/app/train_paths.dart # docs/adr/0030-halkyon-train.md
This commit is contained in:
commit
81adeb91cc
1 changed files with 25705 additions and 410 deletions
|
|
@ -0,0 +1,177 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
import 'package:halkyon_train_flutter/src/core/privacy/train_keys.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/health/data/health_connect.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/health/data/health_platform.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/health/data/health_store.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/weight/data/weight_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/weight/model/weight.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_repository.dart';
|
||||
|
||||
import '../../support/fake_health_platform.dart';
|
||||
import '../../support/memory_content_key_gateway.dart';
|
||||
import '../../support/memory_weight_gateway.dart';
|
||||
import '../../support/memory_workout_gateway.dart';
|
||||
|
||||
/// Other apps' weights brought in from Health Connect (ADR 0030, D64): off
|
||||
/// until the person turns it on, then each weight once, as a weight of
|
||||
/// Train's own store; one the person entered in Train already is a double
|
||||
/// and stays out, as does one deleted in Train.
|
||||
void main() {
|
||||
final morning = DateTime.utc(2026, 11, 3, 6, 30);
|
||||
const scale = 'com.example.scale';
|
||||
|
||||
late Directory folder;
|
||||
late TrainKeys keys;
|
||||
late WorkoutRepository workouts;
|
||||
late WeightRepository weights;
|
||||
late FakeHealthPlatform platform;
|
||||
final opened = <HealthConnect>[];
|
||||
|
||||
HealthWeight measured(String id, double kilograms, {int days = 0}) =>
|
||||
HealthWeight(
|
||||
id: id,
|
||||
at: morning.add(Duration(days: days)),
|
||||
kilograms: kilograms,
|
||||
app: scale,
|
||||
);
|
||||
|
||||
HealthConnect open() {
|
||||
final health = HealthConnect(
|
||||
platform,
|
||||
HealthStore(File('${folder.path}/health_connect.json')),
|
||||
workouts,
|
||||
weights: weights,
|
||||
)..start();
|
||||
opened.add(health);
|
||||
return health;
|
||||
}
|
||||
|
||||
Future<void> settled(HealthConnect health) async {
|
||||
await pumpEventQueue();
|
||||
await health.idle;
|
||||
}
|
||||
|
||||
Future<List<WeightContent>> kept() async => [
|
||||
for (final weight in await weights.all()) weight.content!,
|
||||
];
|
||||
|
||||
setUp(() {
|
||||
folder = Directory.systemTemp.createTempSync('health');
|
||||
keys = TrainKeys(
|
||||
MemoryContentKeyGateway(),
|
||||
FakeContentCipher(),
|
||||
() async => FakeAccountCrypto().keysNumbered(1),
|
||||
);
|
||||
workouts = WorkoutRepository(MemoryWorkoutGateway(), keys);
|
||||
weights = WeightRepository(MemoryWeightGateway(), keys);
|
||||
platform = FakeHealthPlatform();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
for (final health in opened) {
|
||||
await health.dispose();
|
||||
}
|
||||
opened.clear();
|
||||
await workouts.dispose();
|
||||
await weights.dispose();
|
||||
keys.dispose();
|
||||
folder.deleteSync(recursive: true);
|
||||
});
|
||||
|
||||
test('nothing is read until the person turns it on, which asks Health '
|
||||
'Connect for the weights alone', () async {
|
||||
platform.otherWeights.add(measured('a', 74.3));
|
||||
final health = open();
|
||||
await settled(health);
|
||||
expect(await weights.all(), isEmpty);
|
||||
expect(platform.asked, isEmpty);
|
||||
|
||||
expect(await health.setReadingWeights(on: true), isTrue);
|
||||
await settled(health);
|
||||
expect(platform.asked, [
|
||||
{HealthAccess.weight},
|
||||
]);
|
||||
expect(await kept(), [
|
||||
WeightContent(
|
||||
at: morning,
|
||||
kilograms: 74.3,
|
||||
source: WeightSource.healthConnect,
|
||||
healthId: 'a',
|
||||
app: scale,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('each weight comes in once, read again or not', () async {
|
||||
platform.otherWeights.add(measured('a', 74.3));
|
||||
final health = open();
|
||||
await health.setReadingWeights(on: true);
|
||||
await settled(health);
|
||||
platform.otherWeights.add(measured('b', 74.1, days: 1));
|
||||
await health.read();
|
||||
await settled(health);
|
||||
await health.read();
|
||||
await settled(health);
|
||||
|
||||
expect([for (final weight in await kept()) weight.healthId], ['a', 'b']);
|
||||
});
|
||||
|
||||
test('one the person entered in Train already, within ten minutes and a '
|
||||
'tenth of a kilogram, stays out', () async {
|
||||
await weights.save(
|
||||
WeightContent(
|
||||
at: morning.add(const Duration(minutes: 4)),
|
||||
kilograms: 74.35,
|
||||
),
|
||||
);
|
||||
platform.otherWeights
|
||||
..add(measured('double', 74.3))
|
||||
..add(measured('other', 74.9, days: 1));
|
||||
final health = open();
|
||||
await health.setReadingWeights(on: true);
|
||||
await settled(health);
|
||||
|
||||
expect(
|
||||
[for (final weight in await kept()) weight.healthId],
|
||||
[null, 'other'],
|
||||
);
|
||||
});
|
||||
|
||||
test('one deleted in Train stays out', () async {
|
||||
platform.otherWeights.add(measured('a', 74.3));
|
||||
final health = open();
|
||||
await health.setReadingWeights(on: true);
|
||||
await settled(health);
|
||||
await weights.delete((await weights.all()).single.id);
|
||||
|
||||
await health.read();
|
||||
await settled(health);
|
||||
expect(await weights.all(), isEmpty);
|
||||
});
|
||||
|
||||
test('refused, it stays off and says so; turned off, nothing more '
|
||||
'comes', () async {
|
||||
platform = FakeHealthPlatform(grants: {});
|
||||
platform.otherWeights.add(measured('a', 74.3));
|
||||
final health = open();
|
||||
|
||||
expect(await health.setReadingWeights(on: true), isFalse);
|
||||
expect(health.settings.readsWeights, isFalse);
|
||||
expect(health.settings.weightsRefused, isTrue);
|
||||
expect(await weights.all(), isEmpty);
|
||||
});
|
||||
|
||||
test("the switch and what came in are kept with the phone's settings", () {
|
||||
final store = HealthStore(File('${folder.path}/health_connect.json'));
|
||||
const settings = HealthSettings(
|
||||
readsWeights: true,
|
||||
weightsRefused: true,
|
||||
takenWeights: {'a', 'b'},
|
||||
);
|
||||
store.save(settings);
|
||||
expect(store.read(), settings);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue