diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index 9a31e26f5f..086138e550 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -1,10 +1,7 @@ -import 'package:immich_mobile/domain/models/user.model.dart'; - /// Key for each possible value in the `Store`. /// Defines the data type for each value enum StoreKey { version._(0), - currentUser._(2), deviceId._(4), advancedTroubleshooting._(114), enableHapticFeedback._(126), diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index 091ca222f0..173c6bdd63 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -2,12 +2,11 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; @@ -17,7 +16,7 @@ final syncLinkedAlbumServiceProvider = Provider( ref.watch(localAlbumRepository), ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider), - ref.watch(storeServiceProvider), + ref.watch(authUserRepositoryProvider), ), ); @@ -25,13 +24,13 @@ class SyncLinkedAlbumService { final DriftLocalAlbumRepository _localAlbumRepository; final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; - final StoreService _storeService; + final DriftAuthUserRepository _authUserRepository; SyncLinkedAlbumService( this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository, - this._storeService, + this._authUserRepository, ); final _log = Logger("SyncLinkedAlbumService"); @@ -119,11 +118,12 @@ class SyncLinkedAlbumService { /// Creates a new remote album and links it to the local album Future _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async { dPrint(() => "Creating new remote album for local album: ${localAlbum.name}"); - final newRemoteAlbum = await _albumApiRepository.createDriftAlbum( - localAlbum.name, - _storeService.get(StoreKey.currentUser), - assetIds: [], - ); + final currentUser = await _authUserRepository.get(); + if (currentUser == null) { + _log.warning("No user logged in, skipping remote album creation for local album: ${localAlbum.name}"); + return; + } + final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, currentUser, assetIds: []); await _remoteAlbumRepository.create(newRemoteAlbum, []); return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id); } diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart index d5c1a6c4f7..6f9a5389e4 100644 --- a/mobile/lib/domain/services/user.service.dart +++ b/mobile/lib/domain/services/user.service.dart @@ -1,29 +1,24 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:logging/logging.dart'; class UserService { final Logger _log = Logger("UserService"); final UserApiRepository _userApiRepository; - final StoreService _storeService; + final DriftAuthUserRepository _authUserRepository; - UserService({required this._userApiRepository, required this._storeService}); + UserService({required this._userApiRepository, required this._authUserRepository}); - UserDto getMyUser() { - return _storeService.get(StoreKey.currentUser); - } - - UserDto? tryGetMyUser() { - return _storeService.tryGet(StoreKey.currentUser); + Future tryGetMyUser() { + return _authUserRepository.get(); } Stream watchMyUser() { - return _storeService.watch(StoreKey.currentUser); + return _authUserRepository.watch(); } Future refreshMyUser() async { @@ -31,15 +26,17 @@ class UserService { if (user == null) { return null; } - await _storeService.put(StoreKey.currentUser, user); + await _authUserRepository.upsert(user); return user; } Future createProfileImage(String name, Uint8List image) async { try { final path = await _userApiRepository.createProfileImage(name: name, data: image); - final updatedUser = getMyUser(); - await _storeService.put(StoreKey.currentUser, updatedUser); + final updatedUser = await tryGetMyUser(); + if (updatedUser != null) { + await _authUserRepository.upsert(updatedUser); + } return path; } catch (e) { _log.warning("Failed to upload profile image", e); diff --git a/mobile/lib/domain/utils/sync_linked_album.dart b/mobile/lib/domain/utils/sync_linked_album.dart index bdf35d3e2a..5438703209 100644 --- a/mobile/lib/domain/utils/sync_linked_album.dart +++ b/mobile/lib/domain/utils/sync_linked_album.dart @@ -1,15 +1,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:logging/logging.dart'; -Future syncLinkedAlbumsIsolated(ProviderContainer ref) { - final user = Store.tryGet(StoreKey.currentUser); +Future syncLinkedAlbumsIsolated(ProviderContainer ref) async { + final user = await ref.read(authUserRepositoryProvider).get(); if (user == null) { Logger("SyncLinkedAlbum").warning("No user logged in, skipping linked album sync"); - return Future.value(); + return; } return ref .read(syncLinkedAlbumServiceProvider) diff --git a/mobile/lib/infrastructure/repositories/store.repository.dart b/mobile/lib/infrastructure/repositories/store.repository.dart index 9680aa0425..17b084d7fc 100644 --- a/mobile/lib/infrastructure/repositories/store.repository.dart +++ b/mobile/lib/infrastructure/repositories/store.repository.dart @@ -1,9 +1,7 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; class DriftStoreRepository extends DriftDatabaseRepository { final Drift _db; @@ -63,8 +61,6 @@ class DriftStoreRepository extends DriftDatabaseRepository { const (String) => entity.stringValue, const (bool) => entity.intValue == 1, const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), - const (UserDto) => - entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!), _ => null, } as T?; @@ -75,7 +71,6 @@ class DriftStoreRepository extends DriftDatabaseRepository { const (String) => (null, value as String), const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), - const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id), _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"), }; return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue)); diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart index 6df7344991..44a849b51c 100644 --- a/mobile/lib/infrastructure/repositories/user.repository.dart +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -13,20 +13,20 @@ class UserRepository { Stream> getAll() => _db.select(_db.userEntity).map(mapToUser).watch(); } +// TODO(shenlong): migrate this to return AuthUser instead class DriftAuthUserRepository extends DriftDatabaseRepository { final Drift _db; const DriftAuthUserRepository(super.db) : _db = db; - Future get(String id) async { - final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull(); + Selectable get _authUserQuery => (_db.authUserEntity.select()..limit(1)).asyncMap(_toDto); - if (user == null) { - return null; - } + Future get() => _authUserQuery.getSingleOrNull(); - final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(id)); + Stream watch() => _authUserQuery.watchSingleOrNull(); + + Future _toDto(AuthUserEntityData user) async { + final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(user.id)); final metadata = await query.map((row) => row.toDto()).get(); - return user.toDto(metadata); } diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index e2472f1063..5e5ecd4820 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -5,8 +5,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; @@ -85,7 +83,7 @@ class _DriftBackupPageState extends ConsumerState { final backupSyncManager = ref.watch(backgroundSyncProvider); Future startBackup() async { - final currentUser = Store.tryGet(StoreKey.currentUser); + final currentUser = ref.read(currentUserProvider); if (currentUser == null) { return; } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 761905c293..e65d408d09 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -8,16 +8,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/session.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/session.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; @@ -311,6 +311,7 @@ class SplashScreenPageState extends ConsumerState { final backgroundManager = ref.read(backgroundSyncProvider); final backupProvider = ref.read(driftBackupProvider.notifier); final viewIntentHandler = ref.read(viewIntentHandlerProvider); + final authUserRepository = ref.read(authUserRepositoryProvider); unawaited( ref @@ -333,9 +334,9 @@ class SplashScreenPageState extends ConsumerState { if (syncSuccess) { await Future.wait([ backgroundManager.hashAssets().then((_) { - unawaited(_resumeBackup(backupProvider)); + unawaited(_resumeBackup(backupProvider, authUserRepository)); }), - _resumeBackup(backupProvider), + _resumeBackup(backupProvider, authUserRepository), // TODO: Bring back when the soft freeze issue is addressed // backgroundManager.syncCloudIds(), ]); @@ -375,11 +376,11 @@ class SplashScreenPageState extends ConsumerState { } } - Future _resumeBackup(DriftBackupNotifier notifier) async { + Future _resumeBackup(DriftBackupNotifier notifier, DriftAuthUserRepository authUserRepository) async { final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled; if (isEnableBackup) { - final currentUser = Store.tryGet(StoreKey.currentUser); + final currentUser = await authUserRepository.get(); if (currentUser != null) { unawaited(notifier.startForegroundBackup(currentUser.id)); } diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 7dc475ab93..00f4a303e7 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -1,9 +1,7 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; @@ -14,6 +12,7 @@ import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:logging/logging.dart'; @@ -148,7 +147,7 @@ class AppLifeCycleNotifier extends StateNotifier { final isEnableBackup = _ref.read(appConfigProvider).backup.enabled; if (isEnableBackup) { - final currentUser = Store.tryGet(StoreKey.currentUser); + final currentUser = _ref.read(currentUserProvider); if (currentUser != null) { await _safeRun( () => _ref.read(driftBackupProvider.notifier).startForegroundBackup(currentUser.id), diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 037873ede2..97df10688f 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -138,7 +138,7 @@ class AuthNotifier extends StateNotifier { // Get the deviceid from the store if it exists, otherwise generate a new one final String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; - UserDto? user = _userService.tryGetMyUser(); + UserDto? user = await _userService.tryGetMyUser(); try { final serverUser = await _userService.refreshMyUser().timeout(_timeoutDuration); diff --git a/mobile/lib/providers/infrastructure/user.provider.dart b/mobile/lib/providers/infrastructure/user.provider.dart index 09f74db37d..3806551f6b 100644 --- a/mobile/lib/providers/infrastructure/user.provider.dart +++ b/mobile/lib/providers/infrastructure/user.provider.dart @@ -6,17 +6,18 @@ import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; final userRepositoryProvider = Provider((ref) => UserRepository(ref.watch(driftProvider))); +final authUserRepositoryProvider = Provider((ref) => DriftAuthUserRepository(ref.watch(driftProvider))); + final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi)); final userServiceProvider = Provider( (ref) => UserService( userApiRepository: ref.watch(userApiRepositoryProvider), - storeService: ref.watch(storeServiceProvider), + authUserRepository: ref.watch(authUserRepositoryProvider), ), ); diff --git a/mobile/lib/providers/user.provider.dart b/mobile/lib/providers/user.provider.dart index 427a1bf1c7..8b09ffb00f 100644 --- a/mobile/lib/providers/user.provider.dart +++ b/mobile/lib/providers/user.provider.dart @@ -7,7 +7,7 @@ import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; class CurrentUserProvider extends StateNotifier { CurrentUserProvider(this._userService) : super(null) { - state = _userService.tryGetMyUser(); + unawaited(_userService.tryGetMyUser().then((user) => state = user ?? state)); streamSub = _userService.watchMyUser().listen((user) => state = user ?? state); } diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 218ea7199a..e023d41f5b 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -3,9 +3,7 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/session.model.dart'; import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/session.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; @@ -120,7 +118,6 @@ class AuthService { await _backgroundSyncManager.cancel(); await Future.wait([ _authRepository.clearLocalData(), - Store.delete(StoreKey.currentUser), SessionRepository.instance.clear([SessionKey.accessToken]), SettingsRepository.instance.clear(const [ .networkAutoEndpointSwitching, diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 4c40fac7cf..fdf4d6344f 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -55,6 +55,8 @@ abstract final class Bootstrap { await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); await SessionRepository.ensureInitialized(drift); + await SessionRepository.ensureInitialized(drift); + final settingsRepo = await SettingsRepository.ensureInitialized(drift); final logDb = await _initLogger(settingsRepository: settingsRepo, shouldBufferLogs: shouldBufferLogs); diff --git a/mobile/test/domain/services/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index 090faed0dc..379976bbb4 100644 --- a/mobile/test/domain/services/store_service_test.dart +++ b/mobile/test/domain/services/store_service_test.dart @@ -49,7 +49,7 @@ void main() { expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting); expect(sut.tryGet(StoreKey.version), _kVersion); // Other keys should be null - expect(sut.tryGet(StoreKey.currentUser), isNull); + expect(sut.tryGet(StoreKey.deviceId), isNull); }); test('Listens to stream of store updates', () async { @@ -69,11 +69,11 @@ void main() { }); test('Throws StoreKeyNotFoundException for nonexistent keys', () { - expect(() => sut.get(StoreKey.currentUser), throwsA(isA())); + expect(() => sut.get(StoreKey.deviceId), throwsA(isA())); }); test('Returns the stored value for the given key or the defaultValue', () { - expect(sut.get(StoreKey.currentUser, 5), 5); + expect(sut.get(StoreKey.legacyBackupTriggerDelay, 5), 5); }); }); diff --git a/mobile/test/domain/services/sync_linked_album_service_test.dart b/mobile/test/domain/services/sync_linked_album_service_test.dart index e5f4c3b5dc..3053488af2 100644 --- a/mobile/test/domain/services/sync_linked_album_service_test.dart +++ b/mobile/test/domain/services/sync_linked_album_service_test.dart @@ -2,11 +2,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../service.mocks.dart'; void main() { // A container with the service's deps overridden but cancellationProvider left @@ -18,7 +17,7 @@ void main() { localAlbumRepository.overrideWithValue(MockLocalAlbumRepository()), remoteAlbumRepository.overrideWithValue(MockRemoteAlbumRepository()), driftAlbumApiRepositoryProvider.overrideWithValue(MockDriftAlbumApiRepository()), - storeServiceProvider.overrideWithValue(MockStoreService()), + authUserRepositoryProvider.overrideWithValue(MockDriftAuthUserRepository()), ], ); addTearDown(container.dispose); diff --git a/mobile/test/domain/services/user_service_test.dart b/mobile/test/domain/services/user_service_test.dart index 7b46a44339..0819a518e7 100644 --- a/mobile/test/domain/services/user_service_test.dart +++ b/mobile/test/domain/services/user_service_test.dart @@ -1,78 +1,62 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:mocktail/mocktail.dart'; import '../../fixtures/user.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../service.mocks.dart'; void main() { late UserService sut; late UserApiRepository mockUserApiRepo; - late StoreService mockStoreService; + late DriftAuthUserRepository mockAuthUserRepo; setUp(() { mockUserApiRepo = MockUserApiRepository(); - mockStoreService = MockStoreService(); - sut = UserService(userApiRepository: mockUserApiRepo, storeService: mockStoreService); + mockAuthUserRepo = MockDriftAuthUserRepository(); + sut = UserService(userApiRepository: mockUserApiRepo, authUserRepository: mockAuthUserRepo); registerFallbackValue(UserStub.admin); - when(() => mockStoreService.get(StoreKey.currentUser)).thenReturn(UserStub.admin); - when(() => mockStoreService.tryGet(StoreKey.currentUser)).thenReturn(UserStub.admin); - }); - - group('getMyUser', () { - test('should return user from store', () { - final result = sut.getMyUser(); - expect(result, UserStub.admin); - }); - - test('should handle user not found scenario', () { - when(() => mockStoreService.get(StoreKey.currentUser)).thenThrow(Exception('User not found')); - - expect(() => sut.getMyUser(), throwsA(isA())); - }); + when(() => mockAuthUserRepo.get()).thenAnswer((_) async => UserStub.admin); + when(() => mockAuthUserRepo.upsert(any())).thenAnswer((_) async => UserStub.admin); }); group('tryGetMyUser', () { - test('should return user from store', () { - final result = sut.tryGetMyUser(); + test('should return the current user from the auth user repository', () async { + final result = await sut.tryGetMyUser(); expect(result, UserStub.admin); }); - test('should return null if user not found', () { - when(() => mockStoreService.tryGet(StoreKey.currentUser)).thenReturn(null); - final result = sut.tryGetMyUser(); + test('should return null if no user is logged in', () async { + when(() => mockAuthUserRepo.get()).thenAnswer((_) async => null); + final result = await sut.tryGetMyUser(); expect(result, isNull); }); }); group('watchMyUser', () { - test('should return user stream from store', () { - when(() => mockStoreService.watch(StoreKey.currentUser)).thenAnswer((_) => Stream.value(UserStub.admin)); + test('should return the current user stream from the auth user repository', () { + when(() => mockAuthUserRepo.watch()).thenAnswer((_) => Stream.value(UserStub.admin)); final result = sut.watchMyUser(); expect(result, emits(UserStub.admin)); }); - test('should return an empty stream if user not found', () { - when(() => mockStoreService.watch(StoreKey.currentUser)).thenAnswer((_) => const Stream.empty()); + test('should return an empty stream if no user is logged in', () { + when(() => mockAuthUserRepo.watch()).thenAnswer((_) => const Stream.empty()); final result = sut.watchMyUser(); expect(result, emitsInOrder([])); }); }); group('refreshMyUser', () { - test('should return user from api and store it', () async { + test('should return user from api and persist it', () async { when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => UserStub.admin); - when(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).thenAnswer((_) async => true); final result = await sut.refreshMyUser(); - verify(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).called(1); + verify(() => mockAuthUserRepo.upsert(UserStub.admin)).called(1); expect(result, UserStub.admin); }); @@ -80,7 +64,7 @@ void main() { when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null); final result = await sut.refreshMyUser(); - verifyNever(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)); + verifyNever(() => mockAuthUserRepo.upsert(any())); expect(result, isNull); }); }); @@ -88,29 +72,26 @@ void main() { group('createProfileImage', () { test('should return profile image path', () async { const profileImagePath = 'profile.jpg'; - final updatedUser = UserStub.admin; when( () => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)), ).thenAnswer((_) async => profileImagePath); - when(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).thenAnswer((_) async => true); final result = await sut.createProfileImage(profileImagePath, Uint8List(0)); - verify(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).called(1); + verify(() => mockAuthUserRepo.upsert(UserStub.admin)).called(1); expect(result, profileImagePath); }); test('should return null if profile image creation fails', () async { const profileImagePath = 'profile.jpg'; - final updatedUser = UserStub.admin; when( () => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)), ).thenThrow(Exception('Failed to create profile image')); final result = await sut.createProfileImage(profileImagePath, Uint8List(0)); - verifyNever(() => mockStoreService.put(StoreKey.currentUser, updatedUser)); + verifyNever(() => mockAuthUserRepo.upsert(any())); expect(result, isNull); }); }); diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index b8baab614c..2286b99efe 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -4,17 +4,13 @@ import 'package:drift/drift.dart' hide isNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import '../../fixtures/user.stub.dart'; - const _kTestAccessToken = "#TestToken"; const _kTestVersion = 10; const _kTestAdvancedTroubleshooting = false; -final _kTestUser = UserStub.admin; Future _populateStore(Drift db) async { await db.batch((batch) async { @@ -82,14 +78,6 @@ void main() { advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting); }); - - test('converts user', () async { - UserDto? user = await sut.tryGet(StoreKey.currentUser); - expect(user, isNull); - await sut.upsert(StoreKey.currentUser, _kTestUser); - user = await sut.tryGet(StoreKey.currentUser); - expect(user, _kTestUser); - }); }); group('Store Repository Deletes:', () { diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index 355e128fc0..d9e77f8eeb 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -48,6 +48,8 @@ class MockSyncMigrationRepository extends Mock implements SyncMigrationRepositor class MockUserRepository extends Mock implements UserRepository {} +class MockDriftAuthUserRepository extends Mock implements DriftAuthUserRepository {} + class MockPartnerRepository extends Mock implements PartnerRepository {} // API Repos diff --git a/mobile/test/providers/infrastructure/memory_provider_test.dart b/mobile/test/providers/infrastructure/memory_provider_test.dart index e588153fc9..a017dfe703 100644 --- a/mobile/test/providers/infrastructure/memory_provider_test.dart +++ b/mobile/test/providers/infrastructure/memory_provider_test.dart @@ -40,7 +40,7 @@ void main() { userService = MockUserService(); when(() => memoryService.getMemoryLane('user-1')).thenAnswer((_) async => []); - when(() => userService.tryGetMyUser()).thenReturn(user()); + when(() => userService.tryGetMyUser()).thenAnswer((_) async => user()); when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty()); }); @@ -49,7 +49,7 @@ void main() { fakeAsync((async) { final container = makeContainer(); container.listen(driftMemoryFutureProvider, (_, __) {}); - async.flushMicrotasks(); + async.elapse(Duration.zero); verify(() => memoryService.getMemoryLane('user-1')).called(1); @@ -67,7 +67,7 @@ void main() { fakeAsync((async) { final container = makeContainer(); final subscription = container.listen(driftMemoryFutureProvider, (_, __) {}); - async.flushMicrotasks(); + async.elapse(Duration.zero); verify(() => memoryService.getMemoryLane('user-1')).called(1); subscription.close(); @@ -79,7 +79,7 @@ void main() { }); test('does not query or arm the timer when memories are disabled', () { - when(() => userService.tryGetMyUser()).thenReturn(user(memoryEnabled: false)); + when(() => userService.tryGetMyUser()).thenAnswer((_) async => user(memoryEnabled: false)); fakeAsync((async) { final container = makeContainer(); diff --git a/mobile/test/unit/mocks.dart b/mobile/test/unit/mocks.dart index 6c41320a85..0490b09a07 100644 --- a/mobile/test/unit/mocks.dart +++ b/mobile/test/unit/mocks.dart @@ -153,8 +153,7 @@ class ServiceMocks { } void _stubUserService() { - when(user.getMyUser).thenReturn(UserFactory.createDto()); - when(user.tryGetMyUser).thenReturn(null); + when(user.tryGetMyUser).thenAnswer((_) async => UserFactory.createDto()); when(user.watchMyUser).thenAnswer((_) => const Stream.empty()); when(user.refreshMyUser).thenAnswer((_) async => null); when(user.createProfileImage).thenAnswer((_) async => null); @@ -312,10 +311,7 @@ extension type const PartnerServiceStub(MockPartnerService service) implements S } extension type const UserServiceStub(MockUserService service) implements Stub { - UserDto Function() get getMyUser => - () => service.getMyUser(); - - UserDto? Function() get tryGetMyUser => + Future Function() get tryGetMyUser => () => service.tryGetMyUser(); Stream Function() get watchMyUser => diff --git a/mobile/test/unit/presentation/presentation_context.dart b/mobile/test/unit/presentation/presentation_context.dart index 95aa6c9dfe..89b717003c 100644 --- a/mobile/test/unit/presentation/presentation_context.dart +++ b/mobile/test/unit/presentation/presentation_context.dart @@ -50,6 +50,7 @@ class PresentationContext { List get overrides => [ currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)), + authUserProvider.overrideWithValue(currentUser), assetServiceProvider.overrideWithValue(service.asset.service), cleanupServiceProvider.overrideWithValue(service.cleanup.service), remoteAlbumServiceProvider.overrideWithValue(service.album.service), @@ -81,7 +82,7 @@ class PresentationContext { } void setup() { - when(service.user.tryGetMyUser).thenReturn(currentUser); + when(service.user.tryGetMyUser).thenAnswer((_) async => currentUser); } Future dispose() async {