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:
@ -458,11 +458,17 @@ final class WorkoutTemplateUseCases {
|
||||
}
|
||||
final templateOverrides = <WorkoutTemplateExerciseOverride>[];
|
||||
for (final input in overrides) {
|
||||
final programInput = programs.firstWhere(
|
||||
(program) => program.clientKey == input.workoutTemplateProgramClientKey,
|
||||
orElse: () =>
|
||||
throw const DomainException('Workout template program not found.'),
|
||||
);
|
||||
final workoutTemplateProgramId =
|
||||
programIdsByClientKey[input.workoutTemplateProgramClientKey];
|
||||
if (workoutTemplateProgramId == null) {
|
||||
throw const DomainException('Workout template program not found.');
|
||||
}
|
||||
_validateOverrideTargets(programInput.programSnapshotJson, input);
|
||||
templateOverrides.add(
|
||||
WorkoutTemplateExerciseOverride(
|
||||
metadata:
|
||||
@ -833,6 +839,14 @@ final class CloseWorkoutSessionUseCase {
|
||||
final now = clock.now();
|
||||
final results = await sessionRepository.listSetResults(sessionId);
|
||||
final historyId = ids.newId();
|
||||
final historyResults = _historyResultsFromActiveResults(
|
||||
historyId: historyId,
|
||||
results: results,
|
||||
resolvedTemplateSnapshotJson: session.resolvedTemplateSnapshotJson,
|
||||
now: now,
|
||||
ids: ids,
|
||||
originDeviceId: originDeviceId,
|
||||
);
|
||||
final history = WorkoutHistory(
|
||||
metadata: EntityMetadata(
|
||||
id: historyId,
|
||||
@ -867,6 +881,7 @@ final class CloseWorkoutSessionUseCase {
|
||||
)
|
||||
.toList(),
|
||||
}),
|
||||
results: historyResults,
|
||||
);
|
||||
await historyRepository.save(history);
|
||||
return history;
|
||||
@ -886,6 +901,142 @@ final class WorkoutHistoryUseCases {
|
||||
Future<void> delete(String id) => repository.delete(id, clock.now());
|
||||
}
|
||||
|
||||
void _validateOverrideTargets(
|
||||
String programSnapshotJson,
|
||||
WorkoutTemplateExerciseOverrideConfig input,
|
||||
) {
|
||||
final snapshot = jsonDecode(programSnapshotJson) as Map<String, dynamic>;
|
||||
final exercises = (snapshot['exercises'] as List<dynamic>? ?? const [])
|
||||
.cast<Map<String, dynamic>>();
|
||||
final exercise = exercises.cast<Map<String, dynamic>?>().firstWhere(
|
||||
(item) =>
|
||||
item?['id'] == input.snapshotProgramExerciseId ||
|
||||
item?['snapshotProgramExerciseId'] == input.snapshotProgramExerciseId,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (exercise == null) {
|
||||
throw const DomainException('Snapshot exercise not found.');
|
||||
}
|
||||
if (input.targetTimeSecondsOverride != null &&
|
||||
exercise['timeEnabled'] != true) {
|
||||
throw const DomainException('Cannot override inactive time target.');
|
||||
}
|
||||
if (input.targetRepsOverride != null && exercise['repsEnabled'] != true) {
|
||||
throw const DomainException('Cannot override inactive reps target.');
|
||||
}
|
||||
if (input.targetScoreOverride != null && exercise['scoreEnabled'] != true) {
|
||||
throw const DomainException('Cannot override inactive score target.');
|
||||
}
|
||||
}
|
||||
|
||||
List<WorkoutHistorySetResult> _historyResultsFromActiveResults({
|
||||
required String historyId,
|
||||
required List<ActiveSetResult> results,
|
||||
required String resolvedTemplateSnapshotJson,
|
||||
required DateTime now,
|
||||
required IdGenerator ids,
|
||||
required String originDeviceId,
|
||||
}) {
|
||||
final snapshots = _exerciseSnapshotsById(resolvedTemplateSnapshotJson);
|
||||
return results.map((result) {
|
||||
final snapshot = snapshots[result.exerciseSnapshotId];
|
||||
return WorkoutHistorySetResult(
|
||||
metadata: _newMetadata(ids, originDeviceId, now),
|
||||
workoutHistoryId: historyId,
|
||||
programSnapshotId: result.programSnapshotId,
|
||||
exerciseSnapshotId: result.exerciseSnapshotId,
|
||||
programIndex: result.programIndex,
|
||||
exerciseIndex: result.exerciseIndex,
|
||||
setIndex: result.setIndex,
|
||||
programNameSnapshot:
|
||||
snapshot?.programNameSnapshot ?? result.programSnapshotId,
|
||||
exerciseNameSnapshot:
|
||||
snapshot?.exerciseNameSnapshot ?? result.exerciseSnapshotId,
|
||||
timeEnabledSnapshot: snapshot?.timeEnabled ?? result.actualTimeMs != null,
|
||||
repsEnabledSnapshot: snapshot?.repsEnabled ?? result.actualReps != null,
|
||||
scoreEnabledSnapshot:
|
||||
snapshot?.scoreEnabled ?? result.actualScore != null,
|
||||
targetTimeSecondsSnapshot: snapshot?.targetTimeSeconds,
|
||||
targetRepsSnapshot: snapshot?.targetReps,
|
||||
targetScoreSnapshot: snapshot?.targetScore,
|
||||
actualTimeMs: result.actualTimeMs,
|
||||
actualReps: result.actualReps,
|
||||
actualScore: result.actualScore,
|
||||
scoreLabelSnapshot:
|
||||
result.scoreLabelSnapshot ?? snapshot?.scoreLabelSnapshot,
|
||||
scoreUnitSnapshot:
|
||||
result.scoreUnitSnapshot ?? snapshot?.scoreUnitSnapshot,
|
||||
startedAt: result.startedAt,
|
||||
completedAt: result.completedAt,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Map<String, _ResolvedExerciseSnapshot> _exerciseSnapshotsById(
|
||||
String resolvedTemplateSnapshotJson,
|
||||
) {
|
||||
final decoded =
|
||||
jsonDecode(resolvedTemplateSnapshotJson) as Map<String, dynamic>;
|
||||
final programs = (decoded['programs'] as List<dynamic>? ?? const []);
|
||||
final snapshots = <String, _ResolvedExerciseSnapshot>{};
|
||||
for (final program in programs.cast<Map<String, dynamic>>()) {
|
||||
final programName = program['programNameSnapshot'] as String? ?? '';
|
||||
final programSnapshotJson = program['programSnapshotJson'] as String?;
|
||||
if (programSnapshotJson == null) {
|
||||
continue;
|
||||
}
|
||||
final programSnapshot =
|
||||
jsonDecode(programSnapshotJson) as Map<String, dynamic>;
|
||||
final exercises =
|
||||
(programSnapshot['exercises'] as List<dynamic>? ?? const []);
|
||||
for (final exercise in exercises.cast<Map<String, dynamic>>()) {
|
||||
final id = exercise['id'] as String?;
|
||||
if (id == null) {
|
||||
continue;
|
||||
}
|
||||
snapshots[id] = _ResolvedExerciseSnapshot(
|
||||
programNameSnapshot: programName,
|
||||
exerciseNameSnapshot: exercise['exerciseNameSnapshot'] as String? ?? id,
|
||||
timeEnabled: exercise['timeEnabled'] == true,
|
||||
repsEnabled: exercise['repsEnabled'] == true,
|
||||
scoreEnabled: exercise['scoreEnabled'] == true,
|
||||
targetTimeSeconds: exercise['targetTimeSeconds'] as int?,
|
||||
targetReps: exercise['targetReps'] as int?,
|
||||
targetScore: (exercise['targetScore'] as num?)?.toDouble(),
|
||||
scoreLabelSnapshot: exercise['scoreLabelSnapshot'] as String?,
|
||||
scoreUnitSnapshot: exercise['scoreUnitSnapshot'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
return snapshots;
|
||||
}
|
||||
|
||||
final class _ResolvedExerciseSnapshot {
|
||||
const _ResolvedExerciseSnapshot({
|
||||
required this.programNameSnapshot,
|
||||
required this.exerciseNameSnapshot,
|
||||
required this.timeEnabled,
|
||||
required this.repsEnabled,
|
||||
required this.scoreEnabled,
|
||||
this.targetTimeSeconds,
|
||||
this.targetReps,
|
||||
this.targetScore,
|
||||
this.scoreLabelSnapshot,
|
||||
this.scoreUnitSnapshot,
|
||||
});
|
||||
|
||||
final String programNameSnapshot;
|
||||
final String exerciseNameSnapshot;
|
||||
final bool timeEnabled;
|
||||
final bool repsEnabled;
|
||||
final bool scoreEnabled;
|
||||
final int? targetTimeSeconds;
|
||||
final int? targetReps;
|
||||
final double? targetScore;
|
||||
final String? scoreLabelSnapshot;
|
||||
final String? scoreUnitSnapshot;
|
||||
}
|
||||
|
||||
EntityMetadata _newMetadata(
|
||||
IdGenerator ids,
|
||||
String originDeviceId,
|
||||
|
||||
@ -530,7 +530,7 @@ domain.EntityMetadata _metadataFromRow(dynamic row) {
|
||||
);
|
||||
}
|
||||
|
||||
List<Value<Object?>> _metadataValues(domain.EntityMetadata metadata) => [
|
||||
List<dynamic> _metadataValues(domain.EntityMetadata metadata) => [
|
||||
Value(metadata.id),
|
||||
Value(metadata.createdAt),
|
||||
Value(metadata.updatedAt),
|
||||
|
||||
Reference in New Issue
Block a user