refactor(mobile): move user service to domain (#16782)

* refactor: user entity

* chore: rebase fixes

* refactor(mobile): move user service to domain

* fix: timeline not visible on album selection page

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2025-03-14 08:50:26 +05:30
committed by GitHub
parent a65ce2ac55
commit b778a86c99
23 changed files with 408 additions and 183 deletions
+7
View File
@@ -0,0 +1,7 @@
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:mocktail/mocktail.dart';
class MockStoreService extends Mock implements StoreService {}
class MockUserService extends Mock implements UserService {}
@@ -0,0 +1,133 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.repository.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:mocktail/mocktail.dart';
import '../../fixtures/user.stub.dart';
import '../../infrastructure/repository.mock.dart';
import '../service.mock.dart';
void main() {
late UserService sut;
late IUserRepository mockUserRepo;
late IUserApiRepository mockUserApiRepo;
late StoreService mockStoreService;
setUp(() {
mockUserRepo = MockUserRepository();
mockUserApiRepo = MockUserApiRepository();
mockStoreService = MockStoreService();
sut = UserService(
userRepository: mockUserRepo,
userApiRepository: mockUserApiRepo,
storeService: mockStoreService,
);
});
group('getMyUser', () {
test('should return user from store', () {
when(() => mockStoreService.get(StoreKey.currentUser))
.thenReturn(UserStub.admin);
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<Exception>()));
});
});
group('tryGetMyUser', () {
test('should return user from store', () {
when(() => mockStoreService.tryGet(StoreKey.currentUser))
.thenReturn(UserStub.admin);
final result = 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();
expect(result, isNull);
});
});
group('watchMyUser', () {
test('should return user stream from store', () {
when(() => mockStoreService.watch(StoreKey.currentUser))
.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());
final result = sut.watchMyUser();
expect(result, emitsInOrder([]));
});
});
group('refreshMyUser', () {
test('should return user from api and store it', () async {
when(() => mockUserApiRepo.getMyUser())
.thenAnswer((_) async => UserStub.admin);
when(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin))
.thenAnswer((_) async => true);
when(() => mockUserRepo.update(UserStub.admin))
.thenAnswer((_) async => UserStub.admin);
final result = await sut.refreshMyUser();
verify(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin))
.called(1);
verify(() => mockUserRepo.update(UserStub.admin)).called(1);
expect(result, UserStub.admin);
});
test('should return null if user not found', () async {
when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => null);
final result = await sut.refreshMyUser();
verifyNever(
() => mockStoreService.put(StoreKey.currentUser, UserStub.admin),
);
verifyNever(() => mockUserRepo.update(UserStub.admin));
expect(result, isNull);
});
});
group('createProfileImage', () {
test('should return profile image path', () async {
when(
() => mockUserApiRepo.createProfileImage(
name: 'profile.jpg',
data: Uint8List(0),
),
).thenAnswer((_) async => 'profile.jpg');
final result = await sut.createProfileImage('profile.jpg', Uint8List(0));
expect(result, 'profile.jpg');
});
test('should return null if profile image creation fails', () async {
when(
() => mockUserApiRepo.createProfileImage(
name: 'profile.jpg',
data: Uint8List(0),
),
).thenThrow(Exception('Failed to create profile image'));
final result = await sut.createProfileImage('profile.jpg', Uint8List(0));
expect(result, isNull);
});
});
}
@@ -1,7 +1,14 @@
import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/interfaces/user_api.repository.dart';
import 'package:mocktail/mocktail.dart';
class MockStoreRepository extends Mock implements IStoreRepository {}
class MockLogRepository extends Mock implements ILogRepository {}
class MockUserRepository extends Mock implements IUserRepository {}
// API Repos
class MockUserApiRepository extends Mock implements IUserApiRepository {}
@@ -11,9 +11,11 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/services/sync.service.dart';
import 'package:mocktail/mocktail.dart';
import '../../infrastructure/repository.mock.dart';
import '../../repository.mocks.dart';
import '../../service.mocks.dart';
import '../../test_utils.dart';
@@ -56,6 +58,9 @@ void main() {
final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository();
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
final MockPartnerApiRepository partnerApiRepository =
MockPartnerApiRepository();
final MockUserApiRepository userApiRepository = MockUserApiRepository();
final MockPartnerRepository partnerRepository = MockPartnerRepository();
final owner = UserDto(
@@ -98,6 +103,8 @@ void main() {
userRepository,
StoreService.I,
eTagRepository,
partnerApiRepository,
userApiRepository,
);
when(() => eTagRepository.get(owner.id))
.thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now()));
@@ -125,6 +132,10 @@ void main() {
when(() => assetRepository.transaction<Null>(any())).thenAnswer(
(call) => (call.positionalArguments.first as Function).call(),
);
when(() => userApiRepository.getAll()).thenAnswer((_) async => [owner]);
registerFallbackValue(Direction.sharedByMe);
when(() => partnerApiRepository.getAll(any()))
.thenAnswer((_) async => []);
});
test('test inserting existing assets', () async {
final List<Asset> remoteAssets = [
@@ -136,7 +147,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c1, isFalse);
verifyNever(() => assetRepository.updateAll(any()));
@@ -155,7 +165,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c1, isTrue);
final updatedAsset = initialAssets[3].updatedCopy(remoteAssets[3]);
@@ -178,7 +187,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c1, isTrue);
when(
@@ -191,7 +199,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c2, isFalse);
final currentState = [...remoteAssets];
@@ -206,7 +213,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c3, isTrue);
remoteAssets.add(makeAsset(checksum: "k", remoteId: "2-1e"));
@@ -215,7 +221,6 @@ void main() {
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
refreshUsers: () => [owner],
);
expect(c4, isTrue);
});
@@ -246,7 +251,6 @@ void main() {
users: [owner],
getChangedAssets: (user, since) async => (toUpsert, toDelete),
loadAssets: (user, date) => throw Exception(),
refreshUsers: () => throw Exception(),
);
expect(c, isTrue);
verify(() => assetRepository.updateAll(expected));
+3 -3
View File
@@ -1,5 +1,4 @@
import 'package:immich_mobile/domain/interfaces/exif.interface.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/interfaces/album.interface.dart';
import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
@@ -10,6 +9,7 @@ import 'package:immich_mobile/interfaces/auth_api.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:mocktail/mocktail.dart';
@@ -17,8 +17,6 @@ class MockAlbumRepository extends Mock implements IAlbumRepository {}
class MockAssetRepository extends Mock implements IAssetRepository {}
class MockUserRepository extends Mock implements IUserRepository {}
class MockBackupRepository extends Mock implements IBackupAlbumRepository {}
class MockExifInfoRepository extends Mock implements IExifInfoRepository {}
@@ -37,4 +35,6 @@ class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
class MockAuthRepository extends Mock implements IAuthRepository {}
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
class MockPartnerRepository extends Mock implements IPartnerRepository {}
-3
View File
@@ -3,14 +3,11 @@ import 'package:immich_mobile/services/entity.service.dart';
import 'package:immich_mobile/services/hash.service.dart';
import 'package:immich_mobile/services/network.service.dart';
import 'package:immich_mobile/services/sync.service.dart';
import 'package:immich_mobile/services/user.service.dart';
import 'package:mocktail/mocktail.dart';
import 'package:openapi/api.dart';
class MockApiService extends Mock implements ApiService {}
class MockUserService extends Mock implements UserService {}
class MockSyncService extends Mock implements SyncService {}
class MockHashService extends Mock implements HashService {}
+3 -3
View File
@@ -3,6 +3,7 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:mocktail/mocktail.dart';
import '../domain/service.mock.dart';
import '../fixtures/album.stub.dart';
import '../fixtures/asset.stub.dart';
import '../fixtures/user.stub.dart';
@@ -38,7 +39,6 @@ void main() {
);
sut = AlbumService(
userService,
syncService,
entityService,
albumRepository,
@@ -84,7 +84,7 @@ void main() {
group('refreshRemoteAlbums', () {
test('is working', () async {
when(() => userService.getUsersFromServer()).thenAnswer((_) async => []);
when(() => syncService.getUsersFromServer()).thenAnswer((_) async => []);
when(() => syncService.syncUsersFromServer(any()))
.thenAnswer((_) async => true);
when(() => albumApiRepository.getAll(shared: true))
@@ -102,7 +102,7 @@ void main() {
).thenAnswer((_) async => true);
final result = await sut.refreshRemoteAlbums();
expect(result, true);
verify(() => userService.getUsersFromServer()).called(1);
verify(() => syncService.getUsersFromServer()).called(1);
verify(() => syncService.syncUsersFromServer([])).called(1);
verify(() => albumApiRepository.getAll(shared: true)).called(1);
verify(() => albumApiRepository.getAll(shared: null)).called(1);
@@ -6,6 +6,7 @@ import 'package:mocktail/mocktail.dart';
import '../fixtures/asset.stub.dart';
import '../fixtures/user.stub.dart';
import '../infrastructure/repository.mock.dart';
import '../repository.mocks.dart';
void main() {