import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:unionflow_mobile_apps/features/admin/bloc/admin_users_bloc.dart'; import 'package:unionflow_mobile_apps/features/admin/data/repositories/admin_user_repository.dart'; import 'package:unionflow_mobile_apps/features/admin/data/models/admin_user_model.dart'; @GenerateMocks([AdminUserRepository]) import 'admin_users_bloc_test.mocks.dart'; void main() { late AdminUsersBloc bloc; late MockAdminUserRepository mockRepository; // ── Fixtures ────────────────────────────────────────────────────────────── AdminUserModel fakeUser({String id = 'usr-1'}) => AdminUserModel( id: id, username: 'jdupont', email: 'j.dupont@test.com', prenom: 'Jean', nom: 'Dupont', enabled: true, ); AdminRoleModel fakeRole({String id = 'role-1', String name = 'MEMBRE'}) => AdminRoleModel(id: id, name: name, description: 'Role $name'); AdminUserSearchResult fakeSearchResult({List? users}) => AdminUserSearchResult( users: users ?? [fakeUser()], totalCount: users?.length ?? 1, currentPage: 0, pageSize: 20, totalPages: 1, ); setUp(() { mockRepository = MockAdminUserRepository(); bloc = AdminUsersBloc(mockRepository); }); tearDown(() => bloc.close()); // ── Initial state ───────────────────────────────────────────────────────── test('initial state is AdminUsersInitial', () { expect(bloc.state, isA()); }); // ── AdminUsersLoadRequested ─────────────────────────────────────────────── group('AdminUsersLoadRequested', () { blocTest( 'emits [AdminUsersLoading, AdminUsersLoaded] on success', build: () { when(mockRepository.search( page: anyNamed('page'), size: anyNamed('size'), search: anyNamed('search'), )).thenAnswer((_) async => fakeSearchResult()); return bloc; }, act: (b) => b.add(AdminUsersLoadRequested()), expect: () => [ isA(), isA() .having((s) => s.users.length, 'users.length', 1) .having((s) => s.totalCount, 'totalCount', 1) .having((s) => s.currentPage, 'currentPage', 0), ], verify: (_) { verify(mockRepository.search(page: 0, size: 20, search: null)).called(1); }, ); blocTest( 'emits [AdminUsersLoading, AdminUsersLoaded] with search term', build: () { when(mockRepository.search( page: anyNamed('page'), size: anyNamed('size'), search: 'Jean', )).thenAnswer((_) async => fakeSearchResult()); return bloc; }, act: (b) => b.add(AdminUsersLoadRequested(search: 'Jean')), expect: () => [ isA(), isA(), ], verify: (_) { verify(mockRepository.search(page: 0, size: 20, search: 'Jean')).called(1); }, ); blocTest( 'emits [AdminUsersLoading, AdminUsersLoaded] with pagination', build: () { when(mockRepository.search( page: 2, size: 10, search: anyNamed('search'), )).thenAnswer((_) async => fakeSearchResult()); return bloc; }, act: (b) => b.add(AdminUsersLoadRequested(page: 2, size: 10)), expect: () => [isA(), isA()], verify: (_) { verify(mockRepository.search(page: 2, size: 10, search: null)).called(1); }, ); blocTest( 'emits [AdminUsersLoading, AdminUsersError] on failure', build: () { when(mockRepository.search( page: anyNamed('page'), size: anyNamed('size'), search: anyNamed('search'), )).thenThrow(Exception('network error')); return bloc; }, act: (b) => b.add(AdminUsersLoadRequested()), expect: () => [ isA(), isA().having( (s) => s.message, 'message', contains('Exception'), ), ], ); }); // ── AdminUserDetailRequested ────────────────────────────────────────────── group('AdminUserDetailRequested', () { blocTest( 'emits [AdminUsersLoading, AdminUserDetailLoaded] on success', build: () { when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser()); when(mockRepository.getUserRoles('usr-1')) .thenAnswer((_) async => [fakeRole()]); return bloc; }, act: (b) => b.add(AdminUserDetailRequested('usr-1')), expect: () => [ isA(), isA() .having((s) => s.user.id, 'user.id', 'usr-1') .having((s) => s.userRoles.length, 'userRoles.length', 1), ], verify: (_) { verify(mockRepository.getById('usr-1')).called(1); verify(mockRepository.getUserRoles('usr-1')).called(1); }, ); blocTest( 'emits [AdminUsersLoading, AdminUsersError] when user not found (null)', build: () { when(mockRepository.getById('usr-x')).thenAnswer((_) async => null); return bloc; }, act: (b) => b.add(AdminUserDetailRequested('usr-x')), expect: () => [ isA(), isA().having( (s) => s.message, 'message', 'Utilisateur non trouvé', ), ], ); blocTest( 'emits [AdminUsersLoading, AdminUsersError] on repository failure', build: () { when(mockRepository.getById(any)).thenThrow(Exception('server error')); return bloc; }, act: (b) => b.add(AdminUserDetailRequested('usr-1')), expect: () => [isA(), isA()], ); }); // ── AdminUserDetailWithRolesRequested ───────────────────────────────────── group('AdminUserDetailWithRolesRequested', () { blocTest( 'emits [AdminUsersLoading, AdminUserDetailLoaded] with allRoles on success', build: () { when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser()); when(mockRepository.getUserRoles('usr-1')) .thenAnswer((_) async => [fakeRole(name: 'MEMBRE')]); when(mockRepository.getRealmRoles()).thenAnswer( (_) async => [fakeRole(name: 'MEMBRE'), fakeRole(id: 'role-2', name: 'ADMIN')], ); return bloc; }, act: (b) => b.add(AdminUserDetailWithRolesRequested('usr-1')), expect: () => [ isA(), isA() .having((s) => s.allRoles.length, 'allRoles.length', 2) .having((s) => s.userRoles.length, 'userRoles.length', 1), ], verify: (_) { verify(mockRepository.getRealmRoles()).called(1); }, ); blocTest( 'emits [AdminUsersLoading, AdminUsersError] when user not found', build: () { when(mockRepository.getById('unknown')).thenAnswer((_) async => null); return bloc; }, act: (b) => b.add(AdminUserDetailWithRolesRequested('unknown')), expect: () => [ isA(), isA().having( (s) => s.message, 'message', 'Utilisateur non trouvé', ), ], ); blocTest( 'emits [AdminUsersLoading, AdminUsersError] on getRealmRoles failure', build: () { when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser()); when(mockRepository.getUserRoles(any)).thenAnswer((_) async => []); when(mockRepository.getRealmRoles()).thenThrow(Exception('roles error')); return bloc; }, act: (b) => b.add(AdminUserDetailWithRolesRequested('usr-1')), expect: () => [isA(), isA()], ); }); // ── AdminUserRolesUpdateRequested ───────────────────────────────────────── group('AdminUserRolesUpdateRequested', () { blocTest( 'emits [AdminUserRolesUpdated] on update success then reloads detail', build: () { when(mockRepository.setUserRoles('usr-1', ['ADMIN', 'MEMBRE'])) .thenAnswer((_) async {}); // For the auto-dispatched AdminUserDetailWithRolesRequested when(mockRepository.getById('usr-1')).thenAnswer((_) async => fakeUser()); when(mockRepository.getUserRoles('usr-1')) .thenAnswer((_) async => [fakeRole(name: 'ADMIN')]); when(mockRepository.getRealmRoles()).thenAnswer( (_) async => [fakeRole(name: 'ADMIN'), fakeRole(id: 'role-2', name: 'MEMBRE')], ); return bloc; }, act: (b) => b.add(AdminUserRolesUpdateRequested('usr-1', ['ADMIN', 'MEMBRE'])), expect: () => [ isA(), isA(), isA(), ], verify: (_) { verify(mockRepository.setUserRoles('usr-1', ['ADMIN', 'MEMBRE'])).called(1); }, ); blocTest( 'emits [AdminUsersError] on update failure', build: () { when(mockRepository.setUserRoles(any, any)).thenThrow(Exception('roles update error')); return bloc; }, act: (b) => b.add(AdminUserRolesUpdateRequested('usr-1', ['ADMIN'])), expect: () => [isA()], ); }); // ── AdminRolesLoadRequested ─────────────────────────────────────────────── group('AdminRolesLoadRequested', () { blocTest( 'emits [AdminRolesLoaded] on success', build: () { when(mockRepository.getRealmRoles()).thenAnswer( (_) async => [ fakeRole(name: 'MEMBRE'), fakeRole(id: 'role-2', name: 'ADMIN'), fakeRole(id: 'role-3', name: 'SUPER_ADMIN'), ], ); return bloc; }, act: (b) => b.add(AdminRolesLoadRequested()), expect: () => [ isA().having( (s) => s.roles.length, 'roles.length', 3, ), ], verify: (_) => verify(mockRepository.getRealmRoles()).called(1), ); blocTest( 'emits [AdminRolesLoaded] with empty list', build: () { when(mockRepository.getRealmRoles()).thenAnswer((_) async => []); return bloc; }, act: (b) => b.add(AdminRolesLoadRequested()), expect: () => [ isA().having((s) => s.roles, 'roles', isEmpty), ], ); blocTest( 'emits [AdminUsersError] on roles load failure', build: () { when(mockRepository.getRealmRoles()).thenThrow(Exception('keycloak error')); return bloc; }, act: (b) => b.add(AdminRolesLoadRequested()), expect: () => [isA()], ); }); }