Files
GameTime/test/application/media_use_cases_test.dart
Blomios 318fc6fcb9 feat(ui): écran Bibliothèque d'exercices
Ajoute l'écran de bibliothèque d'exercices (presentation/exercise_library_screen.dart)
et ajuste la couche application/domaine (bootstrap, ports, use cases,
entités, repositories Drift) pour l'alimenter. flutter analyze propre,
9/9 tests verts, build APK debug validé.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:42:41 +02:00

192 lines
5.1 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:gametime/application/application.dart';
import 'package:gametime/domain/domain.dart';
void main() {
test('imports media and stores sync-ready asset metadata', () async {
final mediaRepository = _FakeMediaAssetRepository();
final useCase = MediaUseCases(
mediaRepository: mediaRepository,
exerciseRepository: _FakeExerciseRepository(),
storage: _FakeMediaStorage(),
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final asset = await useCase.importMedia(
sourcePath: '/tmp/source.png',
kind: MediaKind.image,
);
expect(asset.kind, MediaKind.image);
expect(asset.localUri, 'file:///app/media/id-1.png');
expect(asset.mimeType, 'image/png');
expect(asset.sizeBytes, 128);
expect(asset.width, 10);
expect(asset.height, 20);
expect(asset.remoteUri, isNull);
expect(asset.checksum, isNull);
expect(mediaRepository.saved.single.metadata.id, asset.metadata.id);
});
test('attaches imported image to exercise image field', () async {
final exerciseRepository = _FakeExerciseRepository()
..exercise = Exercise(
metadata: _metadata('exercise-1'),
name: 'Squat',
hasTimeMeasure: false,
hasRepsMeasure: true,
hasScoreMeasure: false,
);
final mediaRepository = _FakeMediaAssetRepository()
..asset = MediaAsset(
metadata: _metadata('media-1'),
kind: MediaKind.image,
localUri: 'file:///app/media/media-1.png',
);
final useCase = MediaUseCases(
mediaRepository: mediaRepository,
exerciseRepository: exerciseRepository,
storage: _FakeMediaStorage(),
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final updated = await useCase.attachToExercise(
exerciseId: 'exercise-1',
mediaAssetId: 'media-1',
);
expect(updated.imageMediaId, 'media-1');
expect(updated.videoMediaId, isNull);
expect(exerciseRepository.saved.single.imageMediaId, 'media-1');
});
test('deletes managed files not referenced by active media assets', () async {
final storage = _FakeMediaStorage()
..managedUris = {
'file:///app/media/kept.png',
'file:///app/media/orphan.png',
};
final mediaRepository = _FakeMediaAssetRepository()
..activeAssets = [
MediaAsset(
metadata: _metadata('media-1'),
kind: MediaKind.image,
localUri: 'file:///app/media/kept.png',
),
];
final useCase = MediaUseCases(
mediaRepository: mediaRepository,
exerciseRepository: _FakeExerciseRepository(),
storage: storage,
clock: _FakeClock(DateTime.utc(2026, 7, 17, 12)),
ids: _FakeIds(),
originDeviceId: 'device-1',
);
final deletedCount = await useCase.deleteOrphanedManagedFiles();
expect(deletedCount, 1);
expect(storage.deletedUris, ['file:///app/media/orphan.png']);
});
}
EntityMetadata _metadata(String id) {
return EntityMetadata(
id: id,
createdAt: DateTime.utc(2026, 7, 17),
updatedAt: DateTime.utc(2026, 7, 17),
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';
}
}
final class _FakeMediaStorage implements LocalMediaStorage {
Set<String> managedUris = const {};
final deletedUris = <String>[];
@override
Future<StoredMediaFile> importFile({
required String sourcePath,
required MediaKind kind,
required String stableFileName,
}) async {
return StoredMediaFile(
localUri: 'file:///app/media/$stableFileName.png',
mimeType: 'image/png',
sizeBytes: 128,
width: 10,
height: 20,
);
}
@override
Future<Set<String>> listManagedLocalUris() async => managedUris;
@override
Future<void> deleteByLocalUri(String localUri) async {
deletedUris.add(localUri);
}
}
final class _FakeMediaAssetRepository implements MediaAssetRepository {
MediaAsset? asset;
List<MediaAsset> activeAssets = const [];
final saved = <MediaAsset>[];
@override
Future<MediaAsset?> findById(String id) async => asset;
@override
Future<List<MediaAsset>> listActive() async => activeAssets;
@override
Future<void> save(MediaAsset mediaAsset) async {
saved.add(mediaAsset);
asset = mediaAsset;
}
}
final class _FakeExerciseRepository implements ExerciseRepository {
Exercise? exercise;
final saved = <Exercise>[];
@override
Future<Exercise?> findById(String id) async => exercise;
@override
Future<List<Exercise>> listActive() async {
return exercise == null ? const [] : [exercise!];
}
@override
Future<bool> isReferencedByProgram(String id) async => false;
@override
Future<void> save(Exercise exercise) async {
saved.add(exercise);
this.exercise = exercise;
}
}