feat(monetization-261): prepare entitlement infrastructure and quota enforcement for release

- Add EntitlementSnapshot, EntitlementRevalidationUseCase, and BillingUseCases
- Add DriftEntitlementSnapshotRepository for offline entitlement caching
- Add HealthConnect gateway and integration
- Add quota enforcement in share acceptance (server and UI)
- Add entitlement API endpoints in server
- Add privacy policy v1 and release compliance checklist
- Add QA gates and device runbooks for release validation
- Add demo content screen and settings screen

Ticket #261
This commit is contained in:
2026-08-28 23:12:25 +02:00
parent d4df3822b7
commit b12a04ba30
10 changed files with 1096 additions and 26 deletions

View File

@ -1216,10 +1216,19 @@ void main() {
totalCaloriesKcal: 56,
),
);
final activeWorkoutSensorUseCases =
ActiveWorkoutSensorUseCases(clock: clock)..recordTelemetrySample(
WatchSensorSample(
sessionId: session.metadata.id,
capturedAtEpochMs: clock.now().millisecondsSinceEpoch,
stepCount: 1320,
),
);
final useCase = CloseWorkoutSessionUseCase(
sessionRepository: sessionRepository,
historyRepository: historyRepository,
telemetryRepository: telemetryRepository,
activeWorkoutSensorUseCases: activeWorkoutSensorUseCases,
clock: clock,
ids: _FakeIds(),
originDeviceId: 'device-1',
@ -1236,7 +1245,13 @@ void main() {
expect(history.maxHeartRateBpm, 151);
expect(history.totalDistanceMeters, 720);
expect(history.totalCaloriesKcal, 56);
expect(history.totalSteps, 1320);
expect(
jsonDecode(history.historySnapshotJson),
containsPair('totalSteps', 1320),
);
expect(historyRepository.histories.single, history);
await activeWorkoutSensorUseCases.dispose();
});
test('adjustRestSeconds persists adjusted rest duration', () async {
@ -3240,6 +3255,7 @@ void main() {
heartRateBpm: 120,
distanceMeters: 500,
caloriesKcal: 42,
stepCount: 900,
),
);
final duplicate = useCase.recordTelemetrySample(
@ -3271,18 +3287,35 @@ void main() {
).millisecondsSinceEpoch,
distanceMeters: 620,
caloriesKcal: 48,
stepCount: 1320,
),
);
final stepsOnly = useCase.recordTelemetrySample(
WatchTelemetrySample(
sampleId: 'sample-3',
sessionId: 'session-1',
capturedAtEpochMs: DateTime.utc(
2026,
7,
25,
12,
3,
).millisecondsSinceEpoch,
stepCount: 1480,
),
);
expect(first?.latestHeartRateBpm, 120);
expect(duplicate, isNull);
expect(distanceOnly?.sampleCount, 2);
expect(stepsOnly?.sampleCount, 3);
expect(distanceOnly?.latestHeartRateBpm, 120);
expect(distanceOnly?.minHeartRateBpm, 120);
expect(distanceOnly?.averageHeartRateBpm, 120);
expect(distanceOnly?.maxHeartRateBpm, 120);
expect(distanceOnly?.latestDistanceMeters, 620);
expect(distanceOnly?.latestCaloriesKcal, 48);
expect(stepsOnly?.latestStepCount, 1480);
await useCase.dispose();
},
);