feat: BLoC tests complets + sécurité production + freerasp 7.5.1 migration
## Tests BLoC (Task P2.4 Mobile) - 25 nouveaux fichiers *_bloc_test.dart + mocks générés (build_runner) - Features couvertes : authentication, admin_users, adhesions, backup, communication/messaging, contributions, dashboard, finance (approval/budget), events, explore/network, feed, logs_monitoring, notifications, onboarding, organizations (switcher/types/CRUD), profile, reports, settings, solidarity - ~380 tests, > 80% coverage BLoCs ## Sécurité Production (Task P2.2) - lib/core/security/app_integrity_service.dart (freerasp 7.5.1) - Migration API breaking changes freerasp 7.5.1 : - onRootDetected → onPrivilegedAccess - onDebuggerDetected → onDebug - onSignatureDetected → onAppIntegrity - onHookDetected → onHooks - onEmulatorDetected → onSimulator - onUntrustedInstallationSourceDetected → onUnofficialStore - onDeviceBindingDetected → onDeviceBinding - onObfuscationIssuesDetected → onObfuscationIssues - Talsec.start() split → start() + attachListener() - const AndroidConfig/IOSConfig → final (constructors call ConfigVerifier) - supportedAlternativeStores → supportedStores ## Pubspec - bloc_test: ^9.1.7 → ^10.0.0 (compat flutter_bloc ^9.0.0) - freerasp 7.5.1 ## Config - android/app/build.gradle : ajustements release - lib/core/config/environment.dart : URLs API actualisées - lib/main.dart + app_router : intégrations sécurité/BLoC ## Cleanup - Suppression docs intermédiaires (TACHES_*.md, TASK_*_COMPLETION_REPORT.md, TESTS_UNITAIRES_PROGRESS.md) - .g.dart régénérés (json_serializable) - .mocks.dart régénérés (mockito) ## Résultat - 142 fichiers, +27 596 insertions - Toutes les tâches P2 mobile complétées Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
633
test/features/profile/bloc/profile_bloc_test.dart
Normal file
633
test/features/profile/bloc/profile_bloc_test.dart
Normal file
@@ -0,0 +1,633 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'package:unionflow_mobile_apps/features/profile/presentation/bloc/profile_bloc.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/get_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([GetProfile, UpdateProfile, IProfileRepository])
|
||||
import 'profile_bloc_test.mocks.dart';
|
||||
|
||||
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
||||
|
||||
MembreCompletModel _membre({String id = 'membre-1'}) => MembreCompletModel(
|
||||
id: id,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@test.com',
|
||||
);
|
||||
|
||||
DioException _dioError(int statusCode) => DioException(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
response: Response(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
statusCode: statusCode,
|
||||
),
|
||||
type: DioExceptionType.badResponse,
|
||||
);
|
||||
|
||||
DioException _networkError() => DioException(
|
||||
requestOptions: RequestOptions(path: '/api/membres/me'),
|
||||
type: DioExceptionType.connectionTimeout,
|
||||
);
|
||||
|
||||
// ─── Tests ───────────────────────────────────────────────────────────────────
|
||||
|
||||
void main() {
|
||||
late ProfileBloc bloc;
|
||||
late MockGetProfile mockGetProfile;
|
||||
late MockUpdateProfile mockUpdateProfile;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockGetProfile = MockGetProfile();
|
||||
mockUpdateProfile = MockUpdateProfile();
|
||||
mockRepository = MockIProfileRepository();
|
||||
|
||||
bloc = ProfileBloc(mockGetProfile, mockUpdateProfile, mockRepository);
|
||||
});
|
||||
|
||||
tearDown(() => bloc.close());
|
||||
|
||||
// ─── Initial state ──────────────────────────────────────────────────────────
|
||||
|
||||
test('initial state is ProfileInitial', () {
|
||||
expect(bloc.state, isA<ProfileInitial>());
|
||||
});
|
||||
|
||||
// ─── LoadMe ─────────────────────────────────────────────────────────────────
|
||||
|
||||
group('LoadMe', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileLoaded] on success',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileLoaded>()
|
||||
.having((s) => s.membre.id, 'id', 'membre-1')
|
||||
.having((s) => s.membre.email, 'email', 'jean.dupont@test.com'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileNotFound] when getProfile returns null',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenAnswer((_) async => null);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileNotFound>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (401)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Non autorisé'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (404)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(404));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('non trouvé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException (500)',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_dioError(500));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur serveur')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on network timeout',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(_networkError());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Délai de connexion'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(Exception('Unexpected failure'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Erreur lors du chargement'),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── LoadMyProfile ──────────────────────────────────────────────────────────
|
||||
|
||||
group('LoadMyProfile', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileLoaded] on success',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail('jean@test.com'))
|
||||
.thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileLoaded>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileNotFound] when repository returns null',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenAnswer((_) async => null);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('unknown@test.com')),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileNotFound>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on DioException',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenThrow(_dioError(404));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [isA<ProfileLoading>(), isA<ProfileError>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileLoading, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.getProfileByEmail(any))
|
||||
.thenThrow(Exception('DB error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMyProfile('jean@test.com')),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur lors du chargement')),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── UpdateMyProfile ────────────────────────────────────────────────────────
|
||||
|
||||
group('UpdateMyProfile', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileUpdated] when state is ProfileLoaded on success',
|
||||
build: () {
|
||||
final updatedMembre = _membre(id: 'membre-1');
|
||||
when(mockUpdateProfile('membre-1', any))
|
||||
.thenAnswer((_) async => updatedMembre);
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
isA<ProfileUpdated>().having((s) => s.membre.id, 'id', 'membre-1'),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdated] from ProfileInitial (no ProfileUpdating)',
|
||||
build: () {
|
||||
when(mockUpdateProfile('membre-1', any))
|
||||
.thenAnswer((_) async => _membre());
|
||||
return bloc;
|
||||
},
|
||||
// No seed → initial state is ProfileInitial (not ProfileLoaded)
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [isA<ProfileUpdated>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileLoaded, ProfileError] on DioException when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any)).thenThrow(_dioError(400));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>(),
|
||||
isA<ProfileLoaded>(), // restored previous state
|
||||
isA<ProfileError>(),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileError] on DioException when state is not ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any)).thenThrow(_dioError(500));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [isA<ProfileError>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [ProfileUpdating, ProfileError] on generic exception when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockUpdateProfile(any, any))
|
||||
.thenThrow(Exception('Serialization error'));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(UpdateMyProfile(
|
||||
membreId: 'membre-1',
|
||||
membre: _membre(),
|
||||
)),
|
||||
expect: () => [
|
||||
isA<ProfileUpdating>(),
|
||||
isA<ProfileLoaded>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('mise à jour'),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── ChangePassword ─────────────────────────────────────────────────────────
|
||||
|
||||
group('ChangePassword', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, PasswordChanged] on success from ProfileInitial',
|
||||
build: () {
|
||||
when(mockRepository.changePassword('membre-1', 'old123', 'new456'))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old123',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [isA<PasswordChanging>(), isA<PasswordChanged>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'restores ProfileLoaded after PasswordChanged when previous state was ProfileLoaded',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old123',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<PasswordChanged>(),
|
||||
isA<ProfileLoaded>(), // restored
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'wrong',
|
||||
newPassword: 'new',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError, ProfileLoaded] on DioException when state is ProfileLoaded',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
seed: () => ProfileLoaded(_membre()),
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'old',
|
||||
newPassword: 'new',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>(),
|
||||
isA<ProfileLoaded>(), // restored
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [PasswordChanging, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(Exception('Wrong old password'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'wrong',
|
||||
newPassword: 'new456',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>(),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'strips Exception: prefix from generic error message',
|
||||
build: () {
|
||||
when(mockRepository.changePassword(any, any, any))
|
||||
.thenThrow(Exception('Custom error message'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const ChangePassword(
|
||||
membreId: 'membre-1',
|
||||
oldPassword: 'x',
|
||||
newPassword: 'y',
|
||||
)),
|
||||
expect: () => [
|
||||
isA<PasswordChanging>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
isNot(contains('Exception:')),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── DeleteAccount ──────────────────────────────────────────────────────────
|
||||
|
||||
group('DeleteAccount', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, AccountDeleted] on success',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount('membre-1'))
|
||||
.thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [isA<AccountDeleting>(), isA<AccountDeleted>()],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on DioException (401)',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_dioError(401));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Non autorisé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on DioException (403)',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_dioError(403));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Accès refusé')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on network error',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any)).thenThrow(_networkError());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [
|
||||
isA<AccountDeleting>(),
|
||||
isA<ProfileError>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
contains('Délai de connexion'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'emits [AccountDeleting, ProfileError] on generic exception',
|
||||
build: () {
|
||||
when(mockRepository.deleteAccount(any))
|
||||
.thenThrow(Exception('Server unreachable'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const DeleteAccount('membre-1')),
|
||||
expect: () => [isA<AccountDeleting>(), isA<ProfileError>()],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── _networkErrorMessage coverage ─────────────────────────────────────────
|
||||
|
||||
group('_networkErrorMessage DioExceptionType coverage', () {
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'sendTimeout returns Délai de connexion dépassé',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.sendTimeout,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Délai de connexion')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'receiveTimeout returns Délai de connexion dépassé',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.receiveTimeout,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Délai de connexion')),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<ProfileBloc, ProfileState>(
|
||||
'unknown DioExceptionType returns Erreur réseau',
|
||||
build: () {
|
||||
when(mockGetProfile()).thenThrow(DioException(
|
||||
requestOptions: RequestOptions(path: '/'),
|
||||
type: DioExceptionType.unknown,
|
||||
));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(const LoadMe()),
|
||||
expect: () => [
|
||||
isA<ProfileLoading>(),
|
||||
isA<ProfileError>()
|
||||
.having((s) => s.message, 'message', contains('Erreur réseau')),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ─── State equality ─────────────────────────────────────────────────────────
|
||||
|
||||
group('ProfileState equality', () {
|
||||
test('ProfileLoaded with same membre are equal', () {
|
||||
final m = _membre();
|
||||
expect(ProfileLoaded(m), equals(ProfileLoaded(m)));
|
||||
});
|
||||
|
||||
test('ProfileError with same message are equal', () {
|
||||
const s1 = ProfileError('error');
|
||||
const s2 = ProfileError('error');
|
||||
expect(s1, equals(s2));
|
||||
});
|
||||
|
||||
test('ProfileError with different messages are not equal', () {
|
||||
expect(const ProfileError('a'), isNot(equals(const ProfileError('b'))));
|
||||
});
|
||||
|
||||
test('ProfileUpdating contains membre in props', () {
|
||||
final m = _membre();
|
||||
expect(ProfileUpdating(m).props, contains(m));
|
||||
});
|
||||
|
||||
test('ProfileUpdated contains membre in props', () {
|
||||
final m = _membre();
|
||||
expect(ProfileUpdated(m).props, contains(m));
|
||||
});
|
||||
});
|
||||
|
||||
// ─── ProfileEvent equality ──────────────────────────────────────────────────
|
||||
|
||||
group('ProfileEvent equality', () {
|
||||
test('LoadMe events are equal', () {
|
||||
expect(const LoadMe(), equals(const LoadMe()));
|
||||
});
|
||||
|
||||
test('LoadMyProfile with same email are equal', () {
|
||||
expect(
|
||||
const LoadMyProfile('a@b.com'),
|
||||
equals(const LoadMyProfile('a@b.com')),
|
||||
);
|
||||
});
|
||||
|
||||
test('ChangePassword props are correct', () {
|
||||
const event = ChangePassword(
|
||||
membreId: 'm-1',
|
||||
oldPassword: 'old',
|
||||
newPassword: 'new',
|
||||
);
|
||||
expect(event.props, containsAll(['m-1', 'old', 'new']));
|
||||
});
|
||||
|
||||
test('DeleteAccount props are correct', () {
|
||||
const event = DeleteAccount('m-99');
|
||||
expect(event.props, contains('m-99'));
|
||||
});
|
||||
});
|
||||
}
|
||||
172
test/features/profile/bloc/profile_bloc_test.mocks.dart
Normal file
172
test/features/profile/bloc/profile_bloc_test.mocks.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
// Mocks generated by Mockito 5.4.6 from annotations
|
||||
// in unionflow_mobile_apps/test/features/profile/bloc/profile_bloc_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _i4;
|
||||
|
||||
import 'package:mockito/mockito.dart' as _i1;
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart'
|
||||
as _i2;
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart'
|
||||
as _i6;
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/get_profile.dart'
|
||||
as _i3;
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_profile.dart'
|
||||
as _i5;
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: avoid_redundant_argument_values
|
||||
// ignore_for_file: avoid_setters_without_getters
|
||||
// ignore_for_file: comment_references
|
||||
// ignore_for_file: deprecated_member_use
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
// ignore_for_file: subtype_of_sealed_class
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
class _FakeMembreCompletModel_0 extends _i1.SmartFake
|
||||
implements _i2.MembreCompletModel {
|
||||
_FakeMembreCompletModel_0(Object parent, Invocation parentInvocation)
|
||||
: super(parent, parentInvocation);
|
||||
}
|
||||
|
||||
/// A class which mocks [GetProfile].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockGetProfile extends _i1.Mock implements _i3.GetProfile {
|
||||
MockGetProfile() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel?> call() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#call, []),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel?>.value(),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel?>);
|
||||
}
|
||||
|
||||
/// A class which mocks [UpdateProfile].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockUpdateProfile extends _i1.Mock implements _i5.UpdateProfile {
|
||||
MockUpdateProfile() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel> call(
|
||||
String? id,
|
||||
_i2.MembreCompletModel? membre,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#call, [id, membre]),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel>.value(
|
||||
_FakeMembreCompletModel_0(
|
||||
this,
|
||||
Invocation.method(#call, [id, membre]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel>);
|
||||
}
|
||||
|
||||
/// A class which mocks [IProfileRepository].
|
||||
///
|
||||
/// See the documentation for Mockito's code generation for more information.
|
||||
class MockIProfileRepository extends _i1.Mock
|
||||
implements _i6.IProfileRepository {
|
||||
MockIProfileRepository() {
|
||||
_i1.throwOnMissingStub(this);
|
||||
}
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel?> getMe() =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getMe, []),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel?>.value(),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel?>);
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel?> getProfileByEmail(String? email) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#getProfileByEmail, [email]),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel?>.value(),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel?>);
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel> updateProfile(
|
||||
String? id,
|
||||
_i2.MembreCompletModel? membre,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#updateProfile, [id, membre]),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel>.value(
|
||||
_FakeMembreCompletModel_0(
|
||||
this,
|
||||
Invocation.method(#updateProfile, [id, membre]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel>);
|
||||
|
||||
@override
|
||||
_i4.Future<_i2.MembreCompletModel> updateAvatar(
|
||||
String? id,
|
||||
String? photoUrl,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#updateAvatar, [id, photoUrl]),
|
||||
returnValue: _i4.Future<_i2.MembreCompletModel>.value(
|
||||
_FakeMembreCompletModel_0(
|
||||
this,
|
||||
Invocation.method(#updateAvatar, [id, photoUrl]),
|
||||
),
|
||||
),
|
||||
)
|
||||
as _i4.Future<_i2.MembreCompletModel>);
|
||||
|
||||
@override
|
||||
_i4.Future<void> changePassword(
|
||||
String? id,
|
||||
String? oldPassword,
|
||||
String? newPassword,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#changePassword, [id, oldPassword, newPassword]),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i4.Future<void>);
|
||||
|
||||
@override
|
||||
_i4.Future<Map<String, dynamic>> updatePreferences(
|
||||
String? id,
|
||||
Map<String, dynamic>? preferences,
|
||||
) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#updatePreferences, [id, preferences]),
|
||||
returnValue: _i4.Future<Map<String, dynamic>>.value(
|
||||
<String, dynamic>{},
|
||||
),
|
||||
)
|
||||
as _i4.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i4.Future<void> deleteAccount(String? id) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(#deleteAccount, [id]),
|
||||
returnValue: _i4.Future<void>.value(),
|
||||
returnValueForMissingStub: _i4.Future<void>.value(),
|
||||
)
|
||||
as _i4.Future<void>);
|
||||
}
|
||||
Reference in New Issue
Block a user