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, defaultTargetReps: 10, ); 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 managedUris = const {}; final deletedUris = []; @override Future 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> listManagedLocalUris() async => managedUris; @override Future deleteByLocalUri(String localUri) async { deletedUris.add(localUri); } } final class _FakeMediaAssetRepository implements MediaAssetRepository { MediaAsset? asset; List activeAssets = const []; final saved = []; @override Future findById(String id) async => asset; @override Future> listActive() async => activeAssets; @override Future save(MediaAsset mediaAsset) async { saved.add(mediaAsset); asset = mediaAsset; } } final class _FakeExerciseRepository implements ExerciseRepository { Exercise? exercise; final saved = []; @override Future findById(String id) async => exercise; @override Future> listActive() async { return exercise == null ? const [] : [exercise!]; } @override Future isReferencedByProgram(String id) async => false; @override Future save(Exercise exercise) async { saved.add(exercise); this.exercise = exercise; } }