Files
unionflow-mobile-apps/test/features/logs/bloc/logs_monitoring_bloc_test.dart
dahoud 37db88672b 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>
2026-04-21 12:42:35 +00:00

254 lines
9.0 KiB
Dart

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/logs/presentation/bloc/logs_monitoring_bloc.dart';
import 'package:unionflow_mobile_apps/features/logs/data/repositories/logs_monitoring_repository.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_log_model.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_metrics_model.dart';
import 'package:unionflow_mobile_apps/features/logs/data/models/system_alert_model.dart';
@GenerateMocks([LogsMonitoringRepository])
import 'logs_monitoring_bloc_test.mocks.dart';
void main() {
late LogsMonitoringBloc bloc;
late MockLogsMonitoringRepository mockRepository;
// ── Fixtures ──────────────────────────────────────────────────────────────
SystemLogModel fakeLog({String id = 'log-1', String level = 'INFO'}) =>
SystemLogModel(id: id, level: level, source: 'API', message: 'Test log');
SystemMetricsModel fakeMetrics() => const SystemMetricsModel(
cpuUsagePercent: 45.0,
memoryUsagePercent: 60.0,
activeConnections: 10,
);
SystemAlertModel fakeAlert({String id = 'alert-1', bool acknowledged = false}) =>
SystemAlertModel(id: id, level: 'WARNING', title: 'CPU High', acknowledged: acknowledged);
setUp(() {
mockRepository = MockLogsMonitoringRepository();
bloc = LogsMonitoringBloc(mockRepository);
});
tearDown(() => bloc.close());
// ── Initial state ─────────────────────────────────────────────────────────
test('initial state is LogsMonitoringInitial', () {
expect(bloc.state, isA<LogsMonitoringInitial>());
});
// ── SearchLogs ────────────────────────────────────────────────────────────
group('SearchLogs', () {
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsLoaded] on success with default parameters',
build: () {
when(mockRepository.searchLogs(
level: anyNamed('level'),
source: anyNamed('source'),
searchQuery: anyNamed('searchQuery'),
timeRange: anyNamed('timeRange'),
)).thenAnswer((_) async => [fakeLog(), fakeLog(id: 'log-2')]);
return bloc;
},
act: (b) => b.add(SearchLogs()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<LogsLoaded>().having((s) => s.logs.length, 'logs.length', 2),
],
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsLoaded] with filtered parameters',
build: () {
when(mockRepository.searchLogs(
level: 'ERROR',
source: 'API',
searchQuery: 'timeout',
timeRange: 'Dernières 1h',
)).thenAnswer((_) async => [fakeLog(level: 'ERROR')]);
return bloc;
},
act: (b) => b.add(SearchLogs(
level: 'ERROR',
source: 'API',
searchQuery: 'timeout',
timeRange: 'Dernières 1h',
)),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<LogsLoaded>().having(
(s) => s.logs.first.level,
'level',
'ERROR',
),
],
verify: (_) {
verify(mockRepository.searchLogs(
level: 'ERROR',
source: 'API',
searchQuery: 'timeout',
timeRange: 'Dernières 1h',
)).called(1);
},
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsMonitoringError] on failure',
build: () {
when(mockRepository.searchLogs(
level: anyNamed('level'),
source: anyNamed('source'),
searchQuery: anyNamed('searchQuery'),
timeRange: anyNamed('timeRange'),
)).thenThrow(Exception('network error'));
return bloc;
},
act: (b) => b.add(SearchLogs()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<LogsMonitoringError>().having(
(s) => s.error,
'error',
contains('Erreur'),
),
],
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsLoaded] with empty result',
build: () {
when(mockRepository.searchLogs(
level: anyNamed('level'),
source: anyNamed('source'),
searchQuery: anyNamed('searchQuery'),
timeRange: anyNamed('timeRange'),
)).thenAnswer((_) async => []);
return bloc;
},
act: (b) => b.add(SearchLogs()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<LogsLoaded>().having((s) => s.logs, 'logs', isEmpty),
],
);
});
// ── LoadMetrics ───────────────────────────────────────────────────────────
group('LoadMetrics', () {
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, MetricsLoaded] on success',
build: () {
when(mockRepository.getMetrics()).thenAnswer((_) async => fakeMetrics());
return bloc;
},
act: (b) => b.add(LoadMetrics()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<MetricsLoaded>().having(
(s) => s.metrics.cpuUsagePercent,
'cpuUsagePercent',
45.0,
),
],
verify: (_) => verify(mockRepository.getMetrics()).called(1),
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsMonitoringError] on metrics failure',
build: () {
when(mockRepository.getMetrics()).thenThrow(Exception('metrics error'));
return bloc;
},
act: (b) => b.add(LoadMetrics()),
expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
);
});
// ── LoadAlerts ────────────────────────────────────────────────────────────
group('LoadAlerts', () {
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, AlertsLoaded] on success',
build: () {
when(mockRepository.getAlerts())
.thenAnswer((_) async => [fakeAlert(), fakeAlert(id: 'alert-2')]);
return bloc;
},
act: (b) => b.add(LoadAlerts()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<AlertsLoaded>().having((s) => s.alerts.length, 'alerts.length', 2),
],
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsMonitoringError] on alerts failure',
build: () {
when(mockRepository.getAlerts()).thenThrow(Exception('alerts error'));
return bloc;
},
act: (b) => b.add(LoadAlerts()),
expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, AlertsLoaded] with empty list',
build: () {
when(mockRepository.getAlerts()).thenAnswer((_) async => []);
return bloc;
},
act: (b) => b.add(LoadAlerts()),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<AlertsLoaded>().having((s) => s.alerts, 'alerts', isEmpty),
],
);
});
// ── AcknowledgeAlert ──────────────────────────────────────────────────────
group('AcknowledgeAlert', () {
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, AlertsLoaded, LogsMonitoringSuccess] on acknowledge success',
build: () {
when(mockRepository.acknowledgeAlert('alert-1')).thenAnswer((_) async {});
when(mockRepository.getAlerts())
.thenAnswer((_) async => [fakeAlert(acknowledged: true)]);
return bloc;
},
act: (b) => b.add(AcknowledgeAlert('alert-1')),
expect: () => [
isA<LogsMonitoringLoading>(),
isA<AlertsLoaded>(),
isA<LogsMonitoringSuccess>().having(
(s) => s.message,
'message',
'Alerte acquittée',
),
],
verify: (_) {
verify(mockRepository.acknowledgeAlert('alert-1')).called(1);
verify(mockRepository.getAlerts()).called(1);
},
);
blocTest<LogsMonitoringBloc, LogsMonitoringState>(
'emits [Loading, LogsMonitoringError] on acknowledge failure',
build: () {
when(mockRepository.acknowledgeAlert(any)).thenThrow(Exception('ack error'));
return bloc;
},
act: (b) => b.add(AcknowledgeAlert('alert-1')),
expect: () => [isA<LogsMonitoringLoading>(), isA<LogsMonitoringError>()],
);
});
}