fix(qa): corrections issues de la passe QA fonctionnelle
Corrige les bugs identifiés lors de la QA finale sur use_cases.dart et drift_repositories.dart, ajoute la couverture de tests d'infrastructure manquante (test/infrastructure/drift_repositories_test.dart). flutter analyze propre, 21/21 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gametime/application/application.dart';
|
||||
import 'package:gametime/domain/domain.dart';
|
||||
@ -35,6 +37,55 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Workout template use case rejects target overrides for inactive measures',
|
||||
() async {
|
||||
final useCase = WorkoutTemplateUseCases(
|
||||
templateRepository: _FakeWorkoutTemplateRepository(),
|
||||
programRepository: _FakeProgramRepository(),
|
||||
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
useCase.saveConfigured(
|
||||
name: 'Séance tir',
|
||||
programs: [
|
||||
WorkoutTemplateProgramConfig(
|
||||
clientKey: 'program-1',
|
||||
programNameSnapshot: 'Programme tir',
|
||||
defaultRestSecondsSnapshot: 30,
|
||||
programSnapshotJson: jsonEncode({
|
||||
'exercises': [
|
||||
{
|
||||
'id': 'exercise-snapshot-1',
|
||||
'exerciseNameSnapshot': 'Lancers francs',
|
||||
'setsCount': 3,
|
||||
'timeEnabled': true,
|
||||
'repsEnabled': false,
|
||||
'scoreEnabled': false,
|
||||
'targetTimeSeconds': 60,
|
||||
'targetReps': null,
|
||||
'targetScore': null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
],
|
||||
overrides: const [
|
||||
WorkoutTemplateExerciseOverrideConfig(
|
||||
workoutTemplateProgramClientKey: 'program-1',
|
||||
snapshotProgramExerciseId: 'exercise-snapshot-1',
|
||||
targetRepsOverride: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
throwsA(isA<DomainException>()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('Active session elapsed time is based on persisted timestamps', () {
|
||||
final clock = _FakeClock(DateTime.utc(2026, 7, 17, 12));
|
||||
final session = ActiveWorkoutSession(
|
||||
|
||||
196
test/infrastructure/drift_repositories_test.dart
Normal file
196
test/infrastructure/drift_repositories_test.dart
Normal file
@ -0,0 +1,196 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gametime/application/application.dart';
|
||||
import 'package:gametime/domain/domain.dart';
|
||||
import 'package:gametime/infrastructure/local/local.dart' as local;
|
||||
|
||||
void main() {
|
||||
late local.AppDatabase database;
|
||||
late local.DriftActiveSessionRepository activeRepository;
|
||||
late local.DriftWorkoutTemplateRepository templateRepository;
|
||||
late local.DriftWorkoutHistoryRepository historyRepository;
|
||||
|
||||
setUp(() {
|
||||
database = local.AppDatabase(NativeDatabase.memory());
|
||||
activeRepository = local.DriftActiveSessionRepository(database);
|
||||
templateRepository = local.DriftWorkoutTemplateRepository(database);
|
||||
historyRepository = local.DriftWorkoutHistoryRepository(database);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
test(
|
||||
'running session elapsed time survives repository reconstruction',
|
||||
() async {
|
||||
final persistedAt = DateTime.utc(2026, 7, 17, 12);
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1', persistedAt),
|
||||
sourceWorkoutTemplateId: null,
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: persistedAt.subtract(const Duration(seconds: 30)),
|
||||
lastPersistedAt: persistedAt,
|
||||
elapsedActiveMs: 30000,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _resolvedSnapshot(),
|
||||
);
|
||||
|
||||
await activeRepository.save(session);
|
||||
|
||||
final afterAppKillRepository = local.DriftActiveSessionRepository(
|
||||
database,
|
||||
);
|
||||
final afterAppKillUseCases = ActiveWorkoutSessionUseCases(
|
||||
sessionRepository: afterAppKillRepository,
|
||||
templateRepository: templateRepository,
|
||||
clock: _FakeClock(persistedAt.add(const Duration(seconds: 20))),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
|
||||
final restored = await afterAppKillRepository.findOpen();
|
||||
|
||||
expect(restored, isNotNull);
|
||||
expect(afterAppKillUseCases.elapsedActiveMilliseconds(restored!), 50000);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'closing a session stores autonomous history rows with set snapshots',
|
||||
() async {
|
||||
final now = DateTime.utc(2026, 7, 17, 12);
|
||||
final template = WorkoutTemplate(
|
||||
metadata: _metadata('template-1', now),
|
||||
name: 'Séance jambes',
|
||||
);
|
||||
await templateRepository.save(template);
|
||||
|
||||
final session = ActiveWorkoutSession(
|
||||
metadata: _metadata('session-1', now),
|
||||
sourceWorkoutTemplateId: template.metadata.id,
|
||||
status: ActiveWorkoutStatus.running,
|
||||
startedAt: now.subtract(const Duration(minutes: 10)),
|
||||
lastPersistedAt: now,
|
||||
elapsedActiveMs: 600000,
|
||||
currentProgramIndex: 0,
|
||||
currentExerciseIndex: 0,
|
||||
currentSetIndex: 0,
|
||||
resolvedTemplateSnapshotJson: _resolvedSnapshot(),
|
||||
);
|
||||
await activeRepository.save(session);
|
||||
await activeRepository.saveSetResult(
|
||||
ActiveSetResult(
|
||||
metadata: _metadata('active-result-1', now),
|
||||
activeWorkoutSessionId: session.metadata.id,
|
||||
programSnapshotId: 'program-snapshot-1',
|
||||
exerciseSnapshotId: 'exercise-snapshot-1',
|
||||
programIndex: 0,
|
||||
exerciseIndex: 0,
|
||||
setIndex: 0,
|
||||
actualTimeMs: 45000,
|
||||
actualReps: 10,
|
||||
actualScore: 80,
|
||||
scoreLabelSnapshot: 'Charge',
|
||||
scoreUnitSnapshot: 'kg',
|
||||
completedAt: now,
|
||||
),
|
||||
);
|
||||
|
||||
final closeUseCase = CloseWorkoutSessionUseCase(
|
||||
sessionRepository: activeRepository,
|
||||
historyRepository: historyRepository,
|
||||
clock: _FakeClock(now.add(const Duration(seconds: 5))),
|
||||
ids: _FakeIds(),
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
|
||||
final history = await closeUseCase.close(
|
||||
sessionId: session.metadata.id,
|
||||
nameSnapshot: 'Séance jambes',
|
||||
completed: true,
|
||||
);
|
||||
await templateRepository.save(
|
||||
WorkoutTemplate(
|
||||
metadata: template.metadata.markDeleted(now),
|
||||
name: template.name,
|
||||
),
|
||||
);
|
||||
|
||||
final restored = await historyRepository.findById(history.metadata.id);
|
||||
|
||||
expect(restored, isNotNull);
|
||||
expect(restored!.nameSnapshot, 'Séance jambes');
|
||||
expect(restored.results, hasLength(1));
|
||||
expect(restored.results.single.programNameSnapshot, 'Programme jambes');
|
||||
expect(restored.results.single.exerciseNameSnapshot, 'Squat');
|
||||
expect(restored.results.single.actualTimeMs, 45000);
|
||||
expect(restored.results.single.actualReps, 10);
|
||||
expect(restored.results.single.actualScore, 80);
|
||||
expect(restored.results.single.scoreUnitSnapshot, 'kg');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _resolvedSnapshot() {
|
||||
return jsonEncode({
|
||||
'name': 'Séance jambes',
|
||||
'programs': [
|
||||
{
|
||||
'id': 'program-snapshot-1',
|
||||
'programNameSnapshot': 'Programme jambes',
|
||||
'programSnapshotJson': jsonEncode({
|
||||
'exercises': [
|
||||
{
|
||||
'id': 'exercise-snapshot-1',
|
||||
'exerciseNameSnapshot': 'Squat',
|
||||
'setsCount': 1,
|
||||
'timeEnabled': true,
|
||||
'repsEnabled': true,
|
||||
'scoreEnabled': true,
|
||||
'targetTimeSeconds': 45,
|
||||
'targetReps': 10,
|
||||
'targetScore': 80,
|
||||
'scoreLabelSnapshot': 'Charge',
|
||||
'scoreUnitSnapshot': 'kg',
|
||||
'restSecondsOverride': 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
'overrides': const [],
|
||||
});
|
||||
}
|
||||
|
||||
EntityMetadata _metadata(String id, DateTime now) {
|
||||
return EntityMetadata(
|
||||
id: id,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
originDeviceId: 'device-1',
|
||||
);
|
||||
}
|
||||
|
||||
final class _FakeClock implements Clock {
|
||||
const _FakeClock(this.value);
|
||||
|
||||
final DateTime value;
|
||||
|
||||
@override
|
||||
DateTime now() => value;
|
||||
}
|
||||
|
||||
final class _FakeIds implements IdGenerator {
|
||||
var _next = 0;
|
||||
|
||||
@override
|
||||
String newId() {
|
||||
_next += 1;
|
||||
return 'id-$_next';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user