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:
dahoud
2026-04-21 12:42:35 +00:00
parent 33f5b5a707
commit 37db88672b
142 changed files with 27599 additions and 16068 deletions

View File

@@ -0,0 +1,592 @@
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/settings/presentation/bloc/system_settings_bloc.dart';
import 'package:unionflow_mobile_apps/features/settings/presentation/bloc/system_settings_event.dart';
import 'package:unionflow_mobile_apps/features/settings/presentation/bloc/system_settings_state.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/get_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/update_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/get_cache_stats.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/clear_cache.dart' as uc;
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/reset_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/system_config_model.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/cache_stats_model.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/system_metrics_model.dart';
@GenerateMocks([
GetSettings,
UpdateSettings,
GetCacheStats,
uc.ClearCache,
ResetSettings,
ISystemConfigRepository,
])
import 'system_settings_bloc_test.mocks.dart';
void main() {
late SystemSettingsBloc bloc;
late MockGetSettings mockGetSettings;
late MockUpdateSettings mockUpdateSettings;
late MockGetCacheStats mockGetCacheStats;
late MockClearCache mockClearCache;
late MockResetSettings mockResetSettings;
late MockISystemConfigRepository mockRepository;
// ── Fixtures ──────────────────────────────────────────────────────────────
const fakeConfig = SystemConfigModel(
applicationName: 'UnionFlow',
version: '3.0.0',
maintenanceMode: false,
systemStatus: 'HEALTHY',
);
const fakeCacheStats = CacheStatsModel(
totalSizeBytes: 1048576,
totalSizeFormatted: '1 MB',
totalEntries: 250,
hitRate: 0.85,
);
const fakeMetrics = SystemMetricsModel(
cpuUsagePercent: 30.0,
memoryUsagePercent: 50.0,
activeConnections: 20,
);
setUp(() {
mockGetSettings = MockGetSettings();
mockUpdateSettings = MockUpdateSettings();
mockGetCacheStats = MockGetCacheStats();
mockClearCache = MockClearCache();
mockResetSettings = MockResetSettings();
mockRepository = MockISystemConfigRepository();
bloc = SystemSettingsBloc(
mockGetSettings,
mockUpdateSettings,
mockGetCacheStats,
mockClearCache,
mockResetSettings,
mockRepository,
);
});
tearDown(() => bloc.close());
// ── Initial state ─────────────────────────────────────────────────────────
test('initial state is SystemSettingsInitial', () {
expect(bloc.state, isA<SystemSettingsInitial>());
});
// ── LoadSystemConfig ──────────────────────────────────────────────────────
group('LoadSystemConfig', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemConfigLoaded] on success',
build: () {
when(mockGetSettings()).thenAnswer((_) async => fakeConfig);
return bloc;
},
act: (b) => b.add(LoadSystemConfig()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemConfigLoaded>().having(
(s) => s.config.applicationName,
'applicationName',
'UnionFlow',
),
],
verify: (_) => verify(mockGetSettings()).called(1),
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on failure',
build: () {
when(mockGetSettings()).thenThrow(Exception('config not found'));
return bloc;
},
act: (b) => b.add(LoadSystemConfig()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsError>().having(
(s) => s.error,
'error',
contains('Erreur de chargement'),
),
],
);
});
// ── UpdateSystemConfig ────────────────────────────────────────────────────
group('UpdateSystemConfig', () {
final configData = <String, dynamic>{'maintenanceMode': true};
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemConfigLoaded, SystemSettingsSuccess] on success',
build: () {
when(mockUpdateSettings(configData))
.thenAnswer((_) async => const SystemConfigModel(maintenanceMode: true));
return bloc;
},
act: (b) => b.add(UpdateSystemConfig(configData)),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemConfigLoaded>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Configuration mise à jour',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on update failure',
build: () {
when(mockUpdateSettings(any)).thenThrow(Exception('update error'));
return bloc;
},
act: (b) => b.add(UpdateSystemConfig({})),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── LoadCacheStats ────────────────────────────────────────────────────────
group('LoadCacheStats', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, CacheStatsLoaded] on success',
build: () {
when(mockGetCacheStats()).thenAnswer((_) async => fakeCacheStats);
return bloc;
},
act: (b) => b.add(LoadCacheStats()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<CacheStatsLoaded>().having(
(s) => s.stats.hitRate,
'hitRate',
0.85,
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on cache stats failure',
build: () {
when(mockGetCacheStats()).thenThrow(Exception('cache error'));
return bloc;
},
act: (b) => b.add(LoadCacheStats()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── LoadSystemMetrics ─────────────────────────────────────────────────────
group('LoadSystemMetrics', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemMetricsLoaded] on success',
build: () {
when(mockRepository.getMetrics()).thenAnswer((_) async => fakeMetrics);
return bloc;
},
act: (b) => b.add(LoadSystemMetrics()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemMetricsLoaded>().having(
(s) => s.metrics.cpuUsagePercent,
'cpuUsagePercent',
30.0,
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on metrics failure',
build: () {
when(mockRepository.getMetrics()).thenThrow(Exception('metrics error'));
return bloc;
},
act: (b) => b.add(LoadSystemMetrics()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── ClearCache ────────────────────────────────────────────────────────────
group('ClearCache', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockClearCache()).thenAnswer((_) async {});
return bloc;
},
act: (b) => b.add(ClearCache()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Cache vidé avec succès',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on clear cache failure',
build: () {
when(mockClearCache()).thenThrow(Exception('clear failed'));
return bloc;
},
act: (b) => b.add(ClearCache()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── TestDatabaseConnection ────────────────────────────────────────────────
group('TestDatabaseConnection', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] when testDatabase returns success=true',
build: () {
when(mockRepository.testDatabase()).thenAnswer(
(_) async => {'success': true, 'message': 'Connexion OK'},
);
return bloc;
},
act: (b) => b.add(TestDatabaseConnection()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Connexion OK',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] when testDatabase returns success=false',
build: () {
when(mockRepository.testDatabase()).thenAnswer(
(_) async => {'success': false, 'message': 'Connection refused'},
);
return bloc;
},
act: (b) => b.add(TestDatabaseConnection()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsError>().having(
(s) => s.error,
'error',
'Connection refused',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on exception',
build: () {
when(mockRepository.testDatabase()).thenThrow(Exception('db error'));
return bloc;
},
act: (b) => b.add(TestDatabaseConnection()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── TestEmailConfiguration ────────────────────────────────────────────────
group('TestEmailConfiguration', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] when email test returns success=true',
build: () {
when(mockRepository.testEmail()).thenAnswer(
(_) async => {'success': true, 'message': 'Email envoyé'},
);
return bloc;
},
act: (b) => b.add(TestEmailConfiguration()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Email envoyé',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] when email test fails',
build: () {
when(mockRepository.testEmail()).thenThrow(Exception('smtp error'));
return bloc;
},
act: (b) => b.add(TestEmailConfiguration()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── ResetSystemConfig ─────────────────────────────────────────────────────
group('ResetSystemConfig', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemConfigLoaded, SystemSettingsSuccess] on reset success',
build: () {
when(mockResetSettings()).thenAnswer((_) async => fakeConfig);
return bloc;
},
act: (b) => b.add(ResetSystemConfig()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemConfigLoaded>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Configuration réinitialisée',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on reset failure',
build: () {
when(mockResetSettings()).thenThrow(Exception('reset error'));
return bloc;
},
act: (b) => b.add(ResetSystemConfig()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
// ── Repository-delegated actions ──────────────────────────────────────────
group('OptimizeDatabase', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.optimizeDatabase())
.thenAnswer((_) async => {'message': 'Base de données optimisée'});
return bloc;
},
act: (b) => b.add(OptimizeDatabase()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Base de données optimisée',
),
],
);
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsError] on failure',
build: () {
when(mockRepository.optimizeDatabase()).thenThrow(Exception('optimize error'));
return bloc;
},
act: (b) => b.add(OptimizeDatabase()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsError>()],
);
});
group('ForceGlobalLogout', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.forceGlobalLogout())
.thenAnswer((_) async => {'message': 'Déconnexion globale déclenchée'});
return bloc;
},
act: (b) => b.add(ForceGlobalLogout()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>(),
],
);
});
group('CleanupSessions', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.cleanupSessions())
.thenAnswer((_) async => {'message': 'Sessions nettoyées'});
return bloc;
},
act: (b) => b.add(CleanupSessions()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('CleanOldLogs', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.cleanOldLogs())
.thenAnswer((_) async => {'message': 'Logs nettoyés'});
return bloc;
},
act: (b) => b.add(CleanOldLogs()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('ExportLogs', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] with count message',
build: () {
when(mockRepository.exportLogs())
.thenAnswer((_) async => {'count': 42});
return bloc;
},
act: (b) => b.add(ExportLogs()),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'42 log(s) exporté(s)',
),
],
);
});
group('PurgeExpiredData', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.purgeExpiredData())
.thenAnswer((_) async => {'message': 'Données purgées'});
return bloc;
},
act: (b) => b.add(PurgeExpiredData()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('ScheduleMaintenance', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.scheduleMaintenance(
scheduledAt: anyNamed('scheduledAt'),
reason: anyNamed('reason'),
)).thenAnswer((_) async => {'message': 'Maintenance planifiée'});
return bloc;
},
act: (b) => b.add(const ScheduleMaintenance(
scheduledAt: '2026-05-01T02:00:00',
reason: 'Mise à jour',
)),
expect: () => [
isA<SystemSettingsLoading>(),
isA<SystemSettingsSuccess>().having(
(s) => s.message,
'message',
'Maintenance planifiée',
),
],
);
});
group('CreateBackup (settings)', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.createBackup())
.thenAnswer((_) async => {'message': 'Sauvegarde créée'});
return bloc;
},
act: (b) => b.add(CreateBackup()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('GenerateAuditReport', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.generateAuditReport())
.thenAnswer((_) async => {'message': "Rapport d'audit généré"});
return bloc;
},
act: (b) => b.add(GenerateAuditReport()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('GenerateUsageReport', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.generateUsageReport())
.thenAnswer((_) async => {'message': 'Rapport généré'});
return bloc;
},
act: (b) => b.add(GenerateUsageReport()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('ExportGDPRData', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.exportGDPRData())
.thenAnswer((_) async => {'message': 'Export RGPD initié'});
return bloc;
},
act: (b) => b.add(ExportGDPRData()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('CheckUpdates', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.checkUpdates())
.thenAnswer((_) async => {'message': 'Vérification terminée'});
return bloc;
},
act: (b) => b.add(CheckUpdates()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('EmergencyMaintenance', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.emergencyMaintenance())
.thenAnswer((_) async => {'message': "Maintenance d'urgence activée"});
return bloc;
},
act: (b) => b.add(EmergencyMaintenance()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
group('AnalyzePerformance', () {
blocTest<SystemSettingsBloc, SystemSettingsState>(
'emits [Loading, SystemSettingsSuccess] on success',
build: () {
when(mockRepository.analyzePerformance())
.thenAnswer((_) async => {'message': 'Analyse terminée'});
return bloc;
},
act: (b) => b.add(AnalyzePerformance()),
expect: () => [isA<SystemSettingsLoading>(), isA<SystemSettingsSuccess>()],
);
});
}