From 4b8f90aa55bbd02f6cce3666c6a29abac1a079e4 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:55:07 +0530 Subject: [PATCH] refactor: remote album repository test to use context (#26481) * refactor: remote album repository test to use context * refactor: medium repo context (#26482) * refactor: medium repo context * store userId in closure --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../local_asset_repository_test.dart | 343 +++++++++--------- .../remote_album_repository_test.dart | 333 ++++++----------- mobile/test/medium/repository_context.dart | 148 ++++---- 3 files changed, 375 insertions(+), 449 deletions(-) diff --git a/mobile/test/infrastructure/repositories/local_asset_repository_test.dart b/mobile/test/infrastructure/repositories/local_asset_repository_test.dart index dcfc01a55d..88f8d00e03 100644 --- a/mobile/test/infrastructure/repositories/local_asset_repository_test.dart +++ b/mobile/test/infrastructure/repositories/local_asset_repository_test.dart @@ -1,7 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/utils/option.dart'; @@ -11,8 +10,8 @@ void main() { late MediumRepositoryContext ctx; late DriftLocalAssetRepository sut; - setUp(() async { - ctx = await MediumRepositoryContext.create(); + setUp(() { + ctx = MediumRepositoryContext(); sut = DriftLocalAssetRepository(ctx.db); }); @@ -24,103 +23,104 @@ void main() { final cutoffDate = DateTime(2024, 1, 1); final beforeCutoff = DateTime(2023, 12, 31); final afterCutoff = DateTime(2024, 1, 2); - late UserEntityData user; + late String userId; - setUp(() { - user = ctx.user; + setUp(() async { + final user = await ctx.newUser(); + userId = user.id; }); test('returns only assets that match all criteria', () async { - final otherUser = await ctx.insertUser(); + final otherUser = await ctx.newUser(); // Asset 1: Should be included - backed up, before cutoff, correct owner, not deleted, not favorite - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final includedAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final includedAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); // Asset 2: Should NOT be included - not backed up (no remote asset) - await ctx.insertLocalAsset(createdAt: beforeCutoff); + await ctx.newLocalAsset(createdAt: beforeCutoff); // Asset 3: Should NOT be included - after cutoff date - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: afterCutoff); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: afterCutoff); // Asset 4: Should NOT be included - different owner - final otherRemoteAsset = await ctx.insertRemoteAsset(ownerId: otherUser.id); - await ctx.insertLocalAsset(checksum: otherRemoteAsset.checksum, createdAt: beforeCutoff); + final otherRemoteAsset = await ctx.newRemoteAsset(ownerId: otherUser.id); + await ctx.newLocalAsset(checksum: otherRemoteAsset.checksum, createdAt: beforeCutoff); // Asset 5: Should NOT be included - remote asset is deleted - final deletedAsset = await ctx.insertRemoteAsset(ownerId: user.id, deletedAt: DateTime(2024, 1, 1)); - await ctx.insertLocalAsset(checksum: deletedAsset.checksum, createdAt: beforeCutoff); + final deletedAsset = await ctx.newRemoteAsset(ownerId: userId, deletedAt: DateTime(2024, 1, 1)); + await ctx.newLocalAsset(checksum: deletedAsset.checksum, createdAt: beforeCutoff); // Asset 6: Should NOT be included - is favorite (when keepFavorites=true) - final favoriteAsset = await ctx.insertRemoteAsset(ownerId: user.id, isFavorite: true); - await ctx.insertLocalAsset(checksum: favoriteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); + final favoriteAsset = await ctx.newRemoteAsset(ownerId: userId, isFavorite: true); + await ctx.newLocalAsset(checksum: favoriteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: true); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: true); expect(result.assets.length, 1); expect(result.assets.first.id, includedAsset.id); }); test('includes favorites when keepFavorites is false', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final favoriteAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final favoriteAsset = await ctx.newLocalAsset( checksum: remoteAsset.checksum, createdAt: beforeCutoff, isFavorite: true, ); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: false); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: false); expect(result.assets.length, 1); expect(result.assets.first.id, favoriteAsset.id); expect(result.assets.first.isFavorite, true); }); test('excludes asset when both local and remote are favorites', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id, isFavorite: true); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId, isFavorite: true); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: true); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: true); expect(result.assets, isEmpty); }); test('excludes asset when only local is favorite', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff, isFavorite: true); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: true); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: true); expect(result.assets, isEmpty); }); test('excludes asset when only remote is favorite', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id, isFavorite: true); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId, isFavorite: true); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: true); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: true); expect(result.assets, isEmpty); }); test('includes asset when neither local nor remote is favorite', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final localAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final localAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepFavorites: true); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepFavorites: true); expect(result.assets.length, 1); expect(result.assets.first.id, localAsset.id); }); test('keepMediaType photosOnly returns only videos for deletion', () async { - final photoAsset = await ctx.insertRemoteAsset(ownerId: user.id); + final photoAsset = await ctx.newRemoteAsset(ownerId: userId); // Photo - should be kept - await ctx.insertLocalAsset(checksum: photoAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAsset(checksum: photoAsset.checksum, createdAt: beforeCutoff); - final videoRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); + final videoRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); // Video - should be deleted - final videoLocalAsset = await ctx.insertLocalAsset( + final videoLocalAsset = await ctx.newLocalAsset( checksum: videoRemoteAsset.checksum, createdAt: beforeCutoff, type: AssetType.video, ); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepMediaType: AssetKeepType.photosOnly); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepMediaType: AssetKeepType.photosOnly); expect(result.assets.length, 1); expect(result.assets.first.id, videoLocalAsset.id); expect(result.assets.first.type, AssetType.video); @@ -128,14 +128,14 @@ void main() { test('keepMediaType videosOnly returns only photos for deletion', () async { // Photo - should be deleted - final photoRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final photoAsset = await ctx.insertLocalAsset(checksum: photoRemoteAsset.checksum, createdAt: beforeCutoff); + final photoRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final photoAsset = await ctx.newLocalAsset(checksum: photoRemoteAsset.checksum, createdAt: beforeCutoff); // Video - should be kept - final videoRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - await ctx.insertLocalAsset(checksum: videoRemoteAsset.checksum, createdAt: beforeCutoff, type: AssetType.video); + final videoRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + await ctx.newLocalAsset(checksum: videoRemoteAsset.checksum, createdAt: beforeCutoff, type: AssetType.video); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepMediaType: AssetKeepType.videosOnly); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepMediaType: AssetKeepType.videosOnly); expect(result.assets.length, 1); expect(result.assets.first.id, photoAsset.id); expect(result.assets.first.type, AssetType.image); @@ -143,18 +143,18 @@ void main() { test('returns both photos and videos with keepMediaType.all', () async { // Photo - final photoRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final photoAsset = await ctx.insertLocalAsset(checksum: photoRemoteAsset.checksum, createdAt: beforeCutoff); + final photoRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final photoAsset = await ctx.newLocalAsset(checksum: photoRemoteAsset.checksum, createdAt: beforeCutoff); // Video - final videoRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final videoAsset = await ctx.insertLocalAsset( + final videoRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final videoAsset = await ctx.newLocalAsset( checksum: videoRemoteAsset.checksum, createdAt: beforeCutoff, type: AssetType.video, ); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepMediaType: AssetKeepType.none); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepMediaType: AssetKeepType.none); expect(result.assets.length, 2); final ids = result.assets.map((a) => a.id).toSet(); expect(ids, containsAll([photoAsset.id, videoAsset.id])); @@ -162,106 +162,106 @@ void main() { test('excludes assets in iOS shared albums', () async { // Regular album - final regularAlbum = await ctx.insertLocalAlbum(); + final regularAlbum = await ctx.newLocalAlbum(); // iOS shared album - final sharedAlbum = await ctx.insertLocalAlbum(isIosSharedAlbum: true); + final sharedAlbum = await ctx.newLocalAlbum(isIosSharedAlbum: true); // Asset in regular album (should be included) - final regularRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final regularAsset = await ctx.insertLocalAsset(checksum: regularRemoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: regularAlbum.id, assetId: regularAsset.id); + final regularRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final regularAsset = await ctx.newLocalAsset(checksum: regularRemoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: regularAlbum.id, assetId: regularAsset.id); // Asset in iOS shared album (should be excluded) - final sharedRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final sharedAsset = await ctx.insertLocalAsset(checksum: sharedRemoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: sharedAlbum.id, assetId: sharedAsset.id); + final sharedRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final sharedAsset = await ctx.newLocalAsset(checksum: sharedRemoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: sharedAlbum.id, assetId: sharedAsset.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets.length, 1); expect(result.assets.first.id, regularAsset.id); }); test('includes assets at exact cutoff date', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final localAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: cutoffDate); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final localAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: cutoffDate); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets.length, 1); expect(result.assets.first.id, localAsset.id); }); test('returns empty list when no assets match criteria', () async { // Only assets after cutoff - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: afterCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: afterCutoff); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets, isEmpty); }); test('handles multiple assets with same checksum', () async { // Two local assets with same checksum (edge case, but should handle it) - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets.length, 2); expect(result.assets.map((a) => a.checksum).toSet(), equals({remoteAsset.checksum})); }); test('includes assets not in any album', () async { // Asset not in any album should be included - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final localAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final localAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets.length, 1); expect(result.assets.first.id, localAsset.id); }); test('excludes asset that is in both regular and iOS shared album', () async { // Regular album - final regularAlbum = await ctx.insertLocalAlbum(); + final regularAlbum = await ctx.newLocalAlbum(); // iOS shared album - final sharedAlbum = await ctx.insertLocalAlbum(isIosSharedAlbum: true); + final sharedAlbum = await ctx.newLocalAlbum(isIosSharedAlbum: true); // Asset in BOTH albums - should be excluded because it's in an iOS shared album - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final localAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: regularAlbum.id, assetId: localAsset.id); - await ctx.insertLocalAlbumAsset(albumId: sharedAlbum.id, assetId: localAsset.id); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final localAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: regularAlbum.id, assetId: localAsset.id); + await ctx.newLocalAlbumAsset(albumId: sharedAlbum.id, assetId: localAsset.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets, isEmpty); }); test('excludes assets with null checksum (not backed up)', () async { // Asset with null checksum cannot be matched to remote asset - await ctx.insertLocalAsset(checksumOption: const Option.none()); + await ctx.newLocalAsset(checksumOption: const Option.none()); - final result = await sut.getRemovalCandidates(user.id, cutoffDate); + final result = await sut.getRemovalCandidates(userId, cutoffDate); expect(result.assets, isEmpty); }); test('excludes assets in user-excluded albums', () async { // Create two regular albums - final includeAlbum = await ctx.insertLocalAlbum(); - final excludeAlbum = await ctx.insertLocalAlbum(); + final includeAlbum = await ctx.newLocalAlbum(); + final excludeAlbum = await ctx.newLocalAlbum(); // Asset in included album - should be included - final includedRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final includedAsset = await ctx.insertLocalAsset(checksum: includedRemoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: includeAlbum.id, assetId: includedAsset.id); + final includedRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final includedAsset = await ctx.newLocalAsset(checksum: includedRemoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: includeAlbum.id, assetId: includedAsset.id); // Asset in excluded album - should NOT be included - final excludedRemoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final excludedAsset = await ctx.insertLocalAsset(checksum: excludedRemoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: excludeAlbum.id, assetId: excludedAsset.id); + final excludedRemoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final excludedAsset = await ctx.newLocalAsset(checksum: excludedRemoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: excludeAlbum.id, assetId: excludedAsset.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepAlbumIds: {excludeAlbum.id}); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {excludeAlbum.id}); expect(result.assets.length, 1); expect(result.assets.first.id, includedAsset.id); @@ -269,107 +269,104 @@ void main() { test('excludes assets that are in any of multiple excluded albums', () async { // Create multiple albums - final album1 = await ctx.insertLocalAlbum(); - final album2 = await ctx.insertLocalAlbum(); - final album3 = await ctx.insertLocalAlbum(); + final album1 = await ctx.newLocalAlbum(); + final album2 = await ctx.newLocalAlbum(); + final album3 = await ctx.newLocalAlbum(); // Asset in album-1 (excluded) - should NOT be included - final remote1 = await ctx.insertRemoteAsset(ownerId: user.id); - final local1 = await ctx.insertLocalAsset(checksum: remote1.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: album1.id, assetId: local1.id); + final remote1 = await ctx.newRemoteAsset(ownerId: userId); + final local1 = await ctx.newLocalAsset(checksum: remote1.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: album1.id, assetId: local1.id); // Asset in album-2 (excluded) - should NOT be included - final remote2 = await ctx.insertRemoteAsset(ownerId: user.id); - final local2 = await ctx.insertLocalAsset(checksum: remote2.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: album2.id, assetId: local2.id); + final remote2 = await ctx.newRemoteAsset(ownerId: userId); + final local2 = await ctx.newLocalAsset(checksum: remote2.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: album2.id, assetId: local2.id); // Asset in album-3 (not excluded) - should be included - final remote3 = await ctx.insertRemoteAsset(ownerId: user.id); - final local3 = await ctx.insertLocalAsset(checksum: remote3.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: album3.id, assetId: local3.id); + final remote3 = await ctx.newRemoteAsset(ownerId: userId); + final local3 = await ctx.newLocalAsset(checksum: remote3.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: album3.id, assetId: local3.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepAlbumIds: {album1.id, album2.id}); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {album1.id, album2.id}); expect(result.assets.length, 1); expect(result.assets.first.id, local3.id); }); test('excludes asset that is in both excluded and non-excluded album', () async { - final includedAlbum = await ctx.insertLocalAlbum(); - final excludedAlbum = await ctx.insertLocalAlbum(); + final includedAlbum = await ctx.newLocalAlbum(); + final excludedAlbum = await ctx.newLocalAlbum(); // Asset in BOTH albums - should be excluded because it's in an excluded album - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final localAsset = await ctx.insertLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: includedAlbum.id, assetId: localAsset.id); - await ctx.insertLocalAlbumAsset(albumId: excludedAlbum.id, assetId: localAsset.id); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final localAsset = await ctx.newLocalAsset(checksum: remoteAsset.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: includedAlbum.id, assetId: localAsset.id); + await ctx.newLocalAlbumAsset(albumId: excludedAlbum.id, assetId: localAsset.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepAlbumIds: {excludedAlbum.id}); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {excludedAlbum.id}); expect(result.assets, isEmpty); }); test('includes all assets when excludedAlbumIds is empty', () async { - final album1 = await ctx.insertLocalAlbum(); + final album1 = await ctx.newLocalAlbum(); - final remote1 = await ctx.insertRemoteAsset(ownerId: user.id); - final local1 = await ctx.insertLocalAsset(checksum: remote1.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: album1.id, assetId: local1.id); + final remote1 = await ctx.newRemoteAsset(ownerId: userId); + final local1 = await ctx.newLocalAsset(checksum: remote1.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: album1.id, assetId: local1.id); - final remote2 = await ctx.insertRemoteAsset(ownerId: user.id); - await ctx.insertLocalAsset(checksum: remote2.checksum, createdAt: beforeCutoff); + final remote2 = await ctx.newRemoteAsset(ownerId: userId); + await ctx.newLocalAsset(checksum: remote2.checksum, createdAt: beforeCutoff); // Empty excludedAlbumIds should include all eligible assets - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepAlbumIds: {}); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {}); expect(result.assets.length, 2); }); test('excludes asset not in any album when album is excluded', () async { - final excludedAlbum = await ctx.insertLocalAlbum(); + final excludedAlbum = await ctx.newLocalAlbum(); // Asset NOT in any album - should be included - final noAlbumRemote = await ctx.insertRemoteAsset(ownerId: user.id); - final noAlbumAsset = await ctx.insertLocalAsset(checksum: noAlbumRemote.checksum, createdAt: beforeCutoff); + final noAlbumRemote = await ctx.newRemoteAsset(ownerId: userId); + final noAlbumAsset = await ctx.newLocalAsset(checksum: noAlbumRemote.checksum, createdAt: beforeCutoff); // Asset in excluded album - should NOT be included - final excludedRemote = await ctx.insertRemoteAsset(ownerId: user.id); - final excludedAsset = await ctx.insertLocalAsset(checksum: excludedRemote.checksum, createdAt: beforeCutoff); - await ctx.insertLocalAlbumAsset(albumId: excludedAlbum.id, assetId: excludedAsset.id); + final excludedRemote = await ctx.newRemoteAsset(ownerId: userId); + final excludedAsset = await ctx.newLocalAsset(checksum: excludedRemote.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: excludedAlbum.id, assetId: excludedAsset.id); - final result = await sut.getRemovalCandidates(user.id, cutoffDate, keepAlbumIds: {excludedAlbum.id}); + final result = await sut.getRemovalCandidates(userId, cutoffDate, keepAlbumIds: {excludedAlbum.id}); expect(result.assets.length, 1); expect(result.assets.first.id, noAlbumAsset.id); }); test('combines excludedAlbumIds with keepMediaType correctly', () async { - final excludedAlbum = await ctx.insertLocalAlbum(); - final regularAlbum = await ctx.insertLocalAlbum(); + final excludedAlbum = await ctx.newLocalAlbum(); + final regularAlbum = await ctx.newLocalAlbum(); // Photo in excluded album - should NOT be included (album excluded) - final photoExcludedRemote = await ctx.insertRemoteAsset(ownerId: user.id); - final photoExcludedAsset = await ctx.insertLocalAsset( + final photoExcludedRemote = await ctx.newRemoteAsset(ownerId: userId); + final photoExcludedAsset = await ctx.newLocalAsset( checksum: photoExcludedRemote.checksum, createdAt: beforeCutoff, ); - await ctx.insertLocalAlbumAsset(albumId: excludedAlbum.id, assetId: photoExcludedAsset.id); + await ctx.newLocalAlbumAsset(albumId: excludedAlbum.id, assetId: photoExcludedAsset.id); // Video in regular album - should be included (keepMediaType photosOnly = delete videos) - final videoRemote = await ctx.insertRemoteAsset(ownerId: user.id); - final videoAsset = await ctx.insertLocalAsset( + final videoRemote = await ctx.newRemoteAsset(ownerId: userId); + final videoAsset = await ctx.newLocalAsset( checksum: videoRemote.checksum, createdAt: beforeCutoff, type: AssetType.video, ); - await ctx.insertLocalAlbumAsset(albumId: regularAlbum.id, assetId: videoAsset.id); + await ctx.newLocalAlbumAsset(albumId: regularAlbum.id, assetId: videoAsset.id); // Photo in regular album - should NOT be included (keepMediaType photosOnly = keep photos) - final photoRegularRemote = await ctx.insertRemoteAsset(ownerId: user.id); - final photoRegularAsset = await ctx.insertLocalAsset( - checksum: photoRegularRemote.checksum, - createdAt: beforeCutoff, - ); - await ctx.insertLocalAlbumAsset(albumId: regularAlbum.id, assetId: photoRegularAsset.id); + final photoRegularRemote = await ctx.newRemoteAsset(ownerId: userId); + final photoRegularAsset = await ctx.newLocalAsset(checksum: photoRegularRemote.checksum, createdAt: beforeCutoff); + await ctx.newLocalAlbumAsset(albumId: regularAlbum.id, assetId: photoRegularAsset.id); final result = await sut.getRemovalCandidates( - user.id, + userId, cutoffDate, keepMediaType: AssetKeepType.photosOnly, keepAlbumIds: {excludedAlbum.id}, @@ -381,16 +378,17 @@ void main() { }); group('reconcileHashesFromCloudId', () { - late UserEntityData user; + late String userId; - setUp(() { - user = ctx.user; + setUp(() async { + final user = await ctx.newUser(); + userId = user.id; }); test('updates local asset checksum when all metadata matches', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final remoteCloudAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final remoteCloudAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: remoteCloudAsset.cloudId, createdAt: remoteCloudAsset.createdAt, @@ -405,10 +403,10 @@ void main() { }); test('does not update when local asset already has checksum', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final remoteCloudAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id); + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final remoteCloudAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id); - final localAsset = await ctx.insertLocalAsset( + final localAsset = await ctx.newLocalAsset( checksum: 'existing', iCloudId: remoteCloudAsset.cloudId, createdAt: remoteCloudAsset.createdAt, @@ -423,12 +421,9 @@ void main() { }); test('does not update when adjustment_time does not match', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId( - id: remoteAsset.id, - adjustmentTime: DateTime(2024, 1, 12), - ); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, adjustmentTime: DateTime(2024, 1, 12)); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: cloudIdAsset.createdAt, @@ -443,9 +438,9 @@ void main() { }); test('does not update when latitude does not match', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id, latitude: const Option.none()); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, latitude: const Option.none()); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: cloudIdAsset.createdAt, @@ -460,9 +455,9 @@ void main() { }); test('does not update when longitude does not match', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id, longitude: (-74.006).toOption()); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, longitude: (-74.006).toOption()); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: cloudIdAsset.createdAt, @@ -477,9 +472,9 @@ void main() { }); test('does not update when createdAt does not match', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id, createdAt: DateTime(2024, 1, 5)); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, createdAt: DateTime(2024, 1, 5)); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: DateTime(2024, 6, 1), @@ -494,9 +489,9 @@ void main() { }); test('does not update when iCloudId is null', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: null, createdAt: cloudIdAsset.createdAt, @@ -511,9 +506,9 @@ void main() { }); test('does not update when cloudId does not match iCloudId', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: 'different-cloud-id', createdAt: cloudIdAsset.createdAt, @@ -528,12 +523,12 @@ void main() { }); test('handles partial null metadata fields matching correctly', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId( id: remoteAsset.id, adjustmentTimeOption: const Option.none(), ); - final localAsset = await ctx.insertLocalAsset( + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: cloudIdAsset.createdAt, @@ -548,9 +543,9 @@ void main() { }); test('does not update when one has null and other has value', () async { - final remoteAsset = await ctx.insertRemoteAsset(ownerId: user.id); - final cloudIdAsset = await ctx.insertRemoteAssetCloudId(id: remoteAsset.id); - final localAsset = await ctx.insertLocalAsset( + final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id); + final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, createdAt: cloudIdAsset.createdAt, @@ -565,7 +560,7 @@ void main() { }); test('handles no matching assets gracefully', () async { - final localAsset = await ctx.insertLocalAsset(checksumOption: const Option.none(), iCloudId: 'cloud-no-match'); + final localAsset = await ctx.newLocalAsset(checksumOption: const Option.none(), iCloudId: 'cloud-no-match'); await sut.reconcileHashesFromCloudId(); final updated = await sut.getById(localAsset.id); diff --git a/mobile/test/infrastructure/repositories/remote_album_repository_test.dart b/mobile/test/infrastructure/repositories/remote_album_repository_test.dart index bc39d7bf5e..1bc797f6e1 100644 --- a/mobile/test/infrastructure/repositories/remote_album_repository_test.dart +++ b/mobile/test/infrastructure/repositories/remote_album_repository_test.dart @@ -1,305 +1,210 @@ -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; -void main() { - late Drift db; - late DriftRemoteAlbumRepository repository; +import '../../medium/repository_context.dart'; - setUp(() { - db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - repository = DriftRemoteAlbumRepository(db); +void main() { + late MediumRepositoryContext ctx; + late DriftRemoteAlbumRepository sut; + + setUp(() async { + ctx = MediumRepositoryContext(); + sut = DriftRemoteAlbumRepository(ctx.db); }); tearDown(() async { - await db.close(); + await ctx.dispose(); }); group('getSortedAlbumIds', () { - Future createUser(String userId, String name) async { - await db - .into(db.userEntity) - .insert( - UserEntityCompanion( - id: Value(userId), - name: Value(name), - email: Value('$userId@test.com'), - avatarColor: const Value(AvatarColor.primary), - ), - ); - } + late String userId; - Future createAlbum(String albumId, String ownerId, String name) async { - await db - .into(db.remoteAlbumEntity) - .insert( - RemoteAlbumEntityCompanion( - id: Value(albumId), - name: Value(name), - ownerId: Value(ownerId), - createdAt: Value(DateTime.now()), - updatedAt: Value(DateTime.now()), - description: const Value(''), - isActivityEnabled: const Value(false), - order: const Value(AlbumAssetOrder.asc), - ), - ); - } - - Future createAsset(String assetId, String ownerId, DateTime createdAt) async { - await db - .into(db.remoteAssetEntity) - .insert( - RemoteAssetEntityCompanion( - id: Value(assetId), - checksum: Value('checksum-$assetId'), - name: Value('asset-$assetId'), - ownerId: Value(ownerId), - type: const Value(AssetType.image), - createdAt: Value(createdAt), - updatedAt: Value(createdAt), - localDateTime: Value(createdAt), - durationInSeconds: const Value(0), - height: const Value(1080), - width: const Value(1920), - visibility: const Value(AssetVisibility.timeline), - ), - ); - } - - Future linkAssetToAlbum(String albumId, String assetId) async { - await db - .into(db.remoteAlbumAssetEntity) - .insert(RemoteAlbumAssetEntityCompanion(albumId: Value(albumId), assetId: Value(assetId))); - } + setUp(() async { + final user = await ctx.newUser(); + userId = user.id; + }); test('returns empty list when albumIds is empty', () async { - final result = await repository.getSortedAlbumIds([], aggregation: AssetDateAggregation.start); - + final result = await sut.getSortedAlbumIds([], aggregation: AssetDateAggregation.start); expect(result, isEmpty); }); test('returns single album when only one album exists', () async { - const userId = 'user1'; - const albumId = 'album1'; + final album = await ctx.newRemoteAlbum(ownerId: userId); + final asset = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); + await ctx.insertRemoteAlbumAsset(albumId: album.id, assetId: asset.id); - await createUser(userId, 'Test User'); - await createAlbum(albumId, userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2024, 1, 1)); - await linkAssetToAlbum(albumId, 'asset1'); - - final result = await repository.getSortedAlbumIds([albumId], aggregation: AssetDateAggregation.start); - - expect(result, [albumId]); + final result = await sut.getSortedAlbumIds([album.id], aggregation: AssetDateAggregation.start); + expect(result, [album.id]); }); test('sorts albums by start date (MIN) ascending', () async { - const userId = 'user1'; - - await createUser(userId, 'Test User'); - // Album 1: Assets from Jan 10 to Jan 20 (start: Jan 10) - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2024, 1, 10)); - await createAsset('asset2', userId, DateTime(2024, 1, 20)); - await linkAssetToAlbum('album1', 'asset1'); - await linkAssetToAlbum('album1', 'asset2'); + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (start: Jan 5) - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset3', userId, DateTime(2024, 1, 5)); - await createAsset('asset4', userId, DateTime(2024, 1, 15)); - await linkAssetToAlbum('album2', 'asset3'); - await linkAssetToAlbum('album2', 'asset4'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); + final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (start: Jan 25) - await createAlbum('album3', userId, 'Album 3'); - await createAsset('asset5', userId, DateTime(2024, 1, 25)); - await createAsset('asset6', userId, DateTime(2024, 1, 30)); - await linkAssetToAlbum('album3', 'asset5'); - await linkAssetToAlbum('album3', 'asset6'); + final album3 = await ctx.newRemoteAlbum(ownerId: userId); + final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); + final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); - final result = await repository.getSortedAlbumIds([ - 'album1', - 'album2', - 'album3', + final result = await sut.getSortedAlbumIds([ + album1.id, + album2.id, + album3.id, ], aggregation: AssetDateAggregation.start); // Expected order: album2 (Jan 5), album1 (Jan 10), album3 (Jan 25) - expect(result, ['album2', 'album1', 'album3']); + expect(result, [album2.id, album1.id, album3.id]); }); test('sorts albums by end date (MAX) ascending', () async { - const userId = 'user1'; - - await createUser(userId, 'Test User'); - // Album 1: Assets from Jan 10 to Jan 20 (end: Jan 20) - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2024, 1, 10)); - await createAsset('asset2', userId, DateTime(2024, 1, 20)); - await linkAssetToAlbum('album1', 'asset1'); - await linkAssetToAlbum('album1', 'asset2'); + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (end: Jan 15) - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset3', userId, DateTime(2024, 1, 5)); - await createAsset('asset4', userId, DateTime(2024, 1, 15)); - await linkAssetToAlbum('album2', 'asset3'); - await linkAssetToAlbum('album2', 'asset4'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); + final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (end: Jan 30) - await createAlbum('album3', userId, 'Album 3'); - await createAsset('asset5', userId, DateTime(2024, 1, 25)); - await createAsset('asset6', userId, DateTime(2024, 1, 30)); - await linkAssetToAlbum('album3', 'asset5'); - await linkAssetToAlbum('album3', 'asset6'); + final album3 = await ctx.newRemoteAlbum(ownerId: userId); + final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); + final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); - final result = await repository.getSortedAlbumIds([ - 'album1', - 'album2', - 'album3', + final result = await sut.getSortedAlbumIds([ + album1.id, + album2.id, + album3.id, ], aggregation: AssetDateAggregation.end); // Expected order: album2 (Jan 15), album1 (Jan 20), album3 (Jan 30) - expect(result, ['album2', 'album1', 'album3']); + expect(result, [album2.id, album1.id, album3.id]); }); test('handles albums with single asset', () async { - const userId = 'user1'; + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await createUser(userId, 'Test User'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2024, 1, 15)); - await linkAssetToAlbum('album1', 'asset1'); + final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset2', userId, DateTime(2024, 1, 10)); - await linkAssetToAlbum('album2', 'asset2'); - - final result = await repository.getSortedAlbumIds(['album1', 'album2'], aggregation: AssetDateAggregation.start); - - expect(result, ['album2', 'album1']); + expect(result, [album2.id, album1.id]); }); test('only returns requested album IDs in the result', () async { - const userId = 'user1'; - - await createUser(userId, 'Test User'); - // Create 3 albums - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2024, 1, 10)); - await linkAssetToAlbum('album1', 'asset1'); + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset2', userId, DateTime(2024, 1, 5)); - await linkAssetToAlbum('album2', 'asset2'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); - await createAlbum('album3', userId, 'Album 3'); - await createAsset('asset3', userId, DateTime(2024, 1, 15)); - await linkAssetToAlbum('album3', 'asset3'); + final album3 = await ctx.newRemoteAlbum(ownerId: userId); + final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); // Only request album1 and album3 - final result = await repository.getSortedAlbumIds(['album1', 'album3'], aggregation: AssetDateAggregation.start); + final result = await sut.getSortedAlbumIds([album1.id, album3.id], aggregation: AssetDateAggregation.start); // Should only return album1 and album3, not album2 - expect(result, ['album1', 'album3']); + expect(result, [album1.id, album3.id]); }); test('handles albums with same date correctly', () async { - const userId = 'user1'; - - await createUser(userId, 'Test User'); - final sameDate = DateTime(2024, 1, 10); - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, sameDate); - await linkAssetToAlbum('album1', 'asset1'); + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset2', userId, sameDate); - await linkAssetToAlbum('album2', 'asset2'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); - final result = await repository.getSortedAlbumIds(['album1', 'album2'], aggregation: AssetDateAggregation.start); + final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); // Both albums have the same date, so both should be returned expect(result, hasLength(2)); - expect(result, containsAll(['album1', 'album2'])); + expect(result, containsAll([album1.id, album2.id])); }); test('handles albums across different years', () async { - const userId = 'user1'; + final album1 = await ctx.newRemoteAlbum(ownerId: userId); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2023, 12, 25)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await createUser(userId, 'Test User'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); - await createAlbum('album1', userId, 'Album 1'); - await createAsset('asset1', userId, DateTime(2023, 12, 25)); - await linkAssetToAlbum('album1', 'asset1'); + final album3 = await ctx.newRemoteAlbum(ownerId: userId); + final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2025, 1, 1)); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset2', userId, DateTime(2024, 1, 5)); - await linkAssetToAlbum('album2', 'asset2'); - - await createAlbum('album3', userId, 'Album 3'); - await createAsset('asset3', userId, DateTime(2025, 1, 1)); - await linkAssetToAlbum('album3', 'asset3'); - - final result = await repository.getSortedAlbumIds([ - 'album1', - 'album2', - 'album3', + final result = await sut.getSortedAlbumIds([ + album1.id, + album2.id, + album3.id, ], aggregation: AssetDateAggregation.start); - expect(result, ['album1', 'album2', 'album3']); + expect(result, [album1.id, album2.id, album3.id]); }); test('handles album with multiple assets correctly', () async { - const userId = 'user1'; - - await createUser(userId, 'Test User'); - - await createAlbum('album1', userId, 'Album 1'); + final album1 = await ctx.newRemoteAlbum(ownerId: userId); // Album 1 has 5 assets from Jan 5 to Jan 25 - await createAsset('asset1', userId, DateTime(2024, 1, 5)); - await createAsset('asset2', userId, DateTime(2024, 1, 10)); - await createAsset('asset3', userId, DateTime(2024, 1, 15)); - await createAsset('asset4', userId, DateTime(2024, 1, 20)); - await createAsset('asset5', userId, DateTime(2024, 1, 25)); - await linkAssetToAlbum('album1', 'asset1'); - await linkAssetToAlbum('album1', 'asset2'); - await linkAssetToAlbum('album1', 'asset3'); - await linkAssetToAlbum('album1', 'asset4'); - await linkAssetToAlbum('album1', 'asset5'); + final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); + final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); + final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); + final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); + final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset4.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset5.id); - await createAlbum('album2', userId, 'Album 2'); - await createAsset('asset6', userId, DateTime(2024, 1, 1)); - await linkAssetToAlbum('album2', 'asset6'); + final album2 = await ctx.newRemoteAlbum(ownerId: userId); + final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset6.id); - final resultStart = await repository.getSortedAlbumIds([ - 'album1', - 'album2', - ], aggregation: AssetDateAggregation.start); + final resultStart = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); // album2 (Jan 1) should come before album1 (Jan 5) - expect(resultStart, ['album2', 'album1']); + expect(resultStart, [album2.id, album1.id]); - final resultEnd = await repository.getSortedAlbumIds(['album1', 'album2'], aggregation: AssetDateAggregation.end); + final resultEnd = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.end); // album2 (Jan 1) should come before album1 (Jan 25) - expect(resultEnd, ['album2', 'album1']); + expect(resultEnd, [album2.id, album1.id]); }); }); } diff --git a/mobile/test/medium/repository_context.dart b/mobile/test/medium/repository_context.dart index 6e8bb3df98..2c4758400c 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -2,12 +2,15 @@ import 'dart:math'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; @@ -17,21 +20,9 @@ import 'package:uuid/uuid.dart'; class MediumRepositoryContext { final Drift db; - late UserEntityData user; final Random _random = Random(); - MediumRepositoryContext._() - : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - - static Future create() async { - final ctx = MediumRepositoryContext._(); - await ctx.setup(); - return ctx; - } - - Future setup() async { - user = await insertUser(); - } + MediumRepositoryContext() : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); Future dispose() async { await db.close(); @@ -53,7 +44,7 @@ class MediumRepositoryContext { return Value(fallback); } - Future insertUser({ + Future newUser({ String? id, String? email, AvatarColor? avatarColor, @@ -75,50 +66,7 @@ class MediumRepositoryContext { ); } - Future insertLocalAsset({ - String? id, - String? name, - String? checksum, - Option? checksumOption, - DateTime? createdAt, - AssetType? type, - bool? isFavorite, - String? iCloudId, - DateTime? adjustmentTime, - Option? adjustmentTimeOption, - double? latitude, - double? longitude, - int? width, - int? height, - int? durationInSeconds, - int? orientation, - DateTime? updatedAt, - }) async { - id = id ?? const Uuid().v4(); - return db - .into(db.localAssetEntity) - .insertReturning( - LocalAssetEntityCompanion( - id: Value(id), - name: Value(name ?? 'local_$id.jpg'), - height: Value(height ?? _random.nextInt(1000)), - width: Value(width ?? _random.nextInt(1000)), - durationInSeconds: Value(durationInSeconds ?? 0), - orientation: Value(orientation ?? 0), - updatedAt: Value(updatedAt ?? DateTime.now()), - checksum: _resolveUndefined(checksum, checksumOption, const Uuid().v4()), - createdAt: Value(createdAt ?? DateTime.now()), - type: Value(type ?? AssetType.image), - isFavorite: Value(isFavorite ?? false), - iCloudId: Value(iCloudId ?? const Uuid().v4()), - adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), - latitude: Value(latitude ?? _random.nextDouble() * 180 - 90), - longitude: Value(longitude ?? _random.nextDouble() * 360 - 180), - ), - ); - } - - Future insertRemoteAsset({ + Future newRemoteAsset({ String? id, String? checksum, String? ownerId, @@ -166,7 +114,7 @@ class MediumRepositoryContext { ); } - Future insertRemoteAssetCloudId({ + Future newRemoteAssetCloudId({ String? id, String? cloudId, DateTime? createdAt, @@ -189,7 +137,85 @@ class MediumRepositoryContext { ); } - Future insertLocalAlbum({ + Future newRemoteAlbum({ + String? id, + String? name, + String? ownerId, + DateTime? createdAt, + DateTime? updatedAt, + String? description, + bool? isActivityEnabled, + AlbumAssetOrder? order, + String? thumbnailAssetId, + }) async { + id = id ?? const Uuid().v4(); + return db + .into(db.remoteAlbumEntity) + .insertReturning( + RemoteAlbumEntityCompanion( + id: Value(id), + name: Value(name ?? 'remote_album_$id'), + ownerId: Value(ownerId ?? const Uuid().v4()), + createdAt: Value(createdAt ?? DateTime.now()), + updatedAt: Value(updatedAt ?? DateTime.now()), + description: Value(description ?? 'Description for album $id'), + isActivityEnabled: Value(isActivityEnabled ?? false), + order: Value(order ?? AlbumAssetOrder.asc), + thumbnailAssetId: Value(thumbnailAssetId), + ), + ); + } + + Future insertRemoteAlbumAsset({required String albumId, required String assetId}) { + return db + .into(db.remoteAlbumAssetEntity) + .insert(RemoteAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId)); + } + + Future newLocalAsset({ + String? id, + String? name, + String? checksum, + Option? checksumOption, + DateTime? createdAt, + AssetType? type, + bool? isFavorite, + String? iCloudId, + DateTime? adjustmentTime, + Option? adjustmentTimeOption, + double? latitude, + double? longitude, + int? width, + int? height, + int? durationInSeconds, + int? orientation, + DateTime? updatedAt, + }) async { + id = id ?? const Uuid().v4(); + return db + .into(db.localAssetEntity) + .insertReturning( + LocalAssetEntityCompanion( + id: Value(id), + name: Value(name ?? 'local_$id.jpg'), + height: Value(height ?? _random.nextInt(1000)), + width: Value(width ?? _random.nextInt(1000)), + durationInSeconds: Value(durationInSeconds ?? 0), + orientation: Value(orientation ?? 0), + updatedAt: Value(updatedAt ?? DateTime.now()), + checksum: _resolveUndefined(checksum, checksumOption, const Uuid().v4()), + createdAt: Value(createdAt ?? DateTime.now()), + type: Value(type ?? AssetType.image), + isFavorite: Value(isFavorite ?? false), + iCloudId: Value(iCloudId ?? const Uuid().v4()), + adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), + latitude: Value(latitude ?? _random.nextDouble() * 180 - 90), + longitude: Value(longitude ?? _random.nextDouble() * 360 - 180), + ), + ); + } + + Future newLocalAlbum({ String? id, String? name, DateTime? updatedAt, @@ -212,7 +238,7 @@ class MediumRepositoryContext { ); } - Future insertLocalAlbumAsset({required String albumId, required String assetId}) { + Future newLocalAlbumAsset({required String albumId, required String assetId}) { return db .into(db.localAlbumAssetEntity) .insert(LocalAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId));