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:
275
test/features/backup/bloc/backup_bloc_test.dart
Normal file
275
test/features/backup/bloc/backup_bloc_test.dart
Normal file
@@ -0,0 +1,275 @@
|
||||
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/backup/presentation/bloc/backup_bloc.dart';
|
||||
import 'package:unionflow_mobile_apps/features/backup/data/repositories/backup_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/backup/data/models/backup_model.dart';
|
||||
import 'package:unionflow_mobile_apps/features/backup/data/models/backup_config_model.dart';
|
||||
|
||||
@GenerateMocks([BackupRepository])
|
||||
import 'backup_bloc_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late BackupBloc bloc;
|
||||
late MockBackupRepository mockRepository;
|
||||
|
||||
// ── Fixtures ──────────────────────────────────────────────────────────────
|
||||
|
||||
BackupModel fakeBackup({String id = 'bk-1', String name = 'Test'}) => BackupModel(
|
||||
id: id,
|
||||
name: name,
|
||||
type: 'MANUAL',
|
||||
status: 'COMPLETED',
|
||||
sizeBytes: 1024,
|
||||
sizeFormatted: '1 KB',
|
||||
);
|
||||
|
||||
BackupConfigModel fakeConfig() => const BackupConfigModel(
|
||||
autoBackupEnabled: true,
|
||||
frequency: 'DAILY',
|
||||
retentionDays: 30,
|
||||
totalBackups: 5,
|
||||
);
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockBackupRepository();
|
||||
bloc = BackupBloc(mockRepository);
|
||||
});
|
||||
|
||||
tearDown(() => bloc.close());
|
||||
|
||||
// ── Initial state ─────────────────────────────────────────────────────────
|
||||
|
||||
test('initial state is BackupInitial', () {
|
||||
expect(bloc.state, isA<BackupInitial>());
|
||||
});
|
||||
|
||||
// ── LoadBackups ───────────────────────────────────────────────────────────
|
||||
|
||||
group('LoadBackups', () {
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupsLoaded] on success',
|
||||
build: () {
|
||||
when(mockRepository.getAll())
|
||||
.thenAnswer((_) async => [fakeBackup()]);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(LoadBackups()),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupsLoaded>().having(
|
||||
(s) => s.backups.length,
|
||||
'backups.length',
|
||||
1,
|
||||
),
|
||||
],
|
||||
verify: (_) => verify(mockRepository.getAll()).called(1),
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on failure',
|
||||
build: () {
|
||||
when(mockRepository.getAll()).thenThrow(Exception('network error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(LoadBackups()),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupError>().having(
|
||||
(s) => s.error,
|
||||
'error',
|
||||
contains('Erreur'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupsLoaded] with empty list',
|
||||
build: () {
|
||||
when(mockRepository.getAll()).thenAnswer((_) async => []);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(LoadBackups()),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupsLoaded>().having((s) => s.backups, 'backups', isEmpty),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ── CreateBackup ──────────────────────────────────────────────────────────
|
||||
|
||||
group('CreateBackup', () {
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupsLoaded, BackupSuccess] on success',
|
||||
build: () {
|
||||
when(mockRepository.create('Ma sauvegarde', description: anyNamed('description')))
|
||||
.thenAnswer((_) async => fakeBackup());
|
||||
when(mockRepository.getAll())
|
||||
.thenAnswer((_) async => [fakeBackup()]);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(CreateBackup('Ma sauvegarde', description: 'desc')),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupsLoaded>(),
|
||||
isA<BackupSuccess>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
'Sauvegarde créée',
|
||||
),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(mockRepository.create('Ma sauvegarde', description: 'desc')).called(1);
|
||||
verify(mockRepository.getAll()).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on create failure',
|
||||
build: () {
|
||||
when(mockRepository.create(any, description: anyNamed('description')))
|
||||
.thenThrow(Exception('Server error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(CreateBackup('Test')),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupError>(),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// ── RestoreBackup ─────────────────────────────────────────────────────────
|
||||
|
||||
group('RestoreBackup', () {
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupSuccess] on restore success',
|
||||
build: () {
|
||||
when(mockRepository.restore('bk-1')).thenAnswer((_) async {});
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(RestoreBackup('bk-1')),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupSuccess>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
'Restauration en cours',
|
||||
),
|
||||
],
|
||||
verify: (_) => verify(mockRepository.restore('bk-1')).called(1),
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on restore failure',
|
||||
build: () {
|
||||
when(mockRepository.restore(any)).thenThrow(Exception('restore failed'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(RestoreBackup('bk-1')),
|
||||
expect: () => [isA<BackupLoading>(), isA<BackupError>()],
|
||||
);
|
||||
});
|
||||
|
||||
// ── DeleteBackup ──────────────────────────────────────────────────────────
|
||||
|
||||
group('DeleteBackup', () {
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupsLoaded, BackupSuccess] on delete success',
|
||||
build: () {
|
||||
when(mockRepository.delete('bk-1')).thenAnswer((_) async {});
|
||||
when(mockRepository.getAll()).thenAnswer((_) async => []);
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(DeleteBackup('bk-1')),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupsLoaded>(),
|
||||
isA<BackupSuccess>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
'Sauvegarde supprimée',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on delete failure',
|
||||
build: () {
|
||||
when(mockRepository.delete(any)).thenThrow(Exception('delete failed'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(DeleteBackup('bk-1')),
|
||||
expect: () => [isA<BackupLoading>(), isA<BackupError>()],
|
||||
);
|
||||
});
|
||||
|
||||
// ── LoadBackupConfig ──────────────────────────────────────────────────────
|
||||
|
||||
group('LoadBackupConfig', () {
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupConfigLoaded] on success',
|
||||
build: () {
|
||||
when(mockRepository.getConfig()).thenAnswer((_) async => fakeConfig());
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(LoadBackupConfig()),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupConfigLoaded>().having(
|
||||
(s) => s.config.autoBackupEnabled,
|
||||
'autoBackupEnabled',
|
||||
true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on config load failure',
|
||||
build: () {
|
||||
when(mockRepository.getConfig()).thenThrow(Exception('config error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(LoadBackupConfig()),
|
||||
expect: () => [isA<BackupLoading>(), isA<BackupError>()],
|
||||
);
|
||||
});
|
||||
|
||||
// ── UpdateBackupConfig ────────────────────────────────────────────────────
|
||||
|
||||
group('UpdateBackupConfig', () {
|
||||
final configMap = <String, dynamic>{'autoBackupEnabled': false, 'frequency': 'WEEKLY'};
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupConfigLoaded, BackupSuccess] on success',
|
||||
build: () {
|
||||
when(mockRepository.updateConfig(configMap))
|
||||
.thenAnswer((_) async => const BackupConfigModel(autoBackupEnabled: false, frequency: 'WEEKLY'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(UpdateBackupConfig(configMap)),
|
||||
expect: () => [
|
||||
isA<BackupLoading>(),
|
||||
isA<BackupConfigLoaded>(),
|
||||
isA<BackupSuccess>().having(
|
||||
(s) => s.message,
|
||||
'message',
|
||||
'Configuration mise à jour',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<BackupBloc, BackupState>(
|
||||
'emits [BackupLoading, BackupError] on update failure',
|
||||
build: () {
|
||||
when(mockRepository.updateConfig(any)).thenThrow(Exception('update error'));
|
||||
return bloc;
|
||||
},
|
||||
act: (b) => b.add(UpdateBackupConfig({})),
|
||||
expect: () => [isA<BackupLoading>(), isA<BackupError>()],
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user