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()); }); // ── SearchLogs ──────────────────────────────────────────────────────────── group('SearchLogs', () { blocTest( '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(), isA().having((s) => s.logs.length, 'logs.length', 2), ], ); blocTest( '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(), isA().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( '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(), isA().having( (s) => s.error, 'error', contains('Erreur'), ), ], ); blocTest( '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(), isA().having((s) => s.logs, 'logs', isEmpty), ], ); }); // ── LoadMetrics ─────────────────────────────────────────────────────────── group('LoadMetrics', () { blocTest( 'emits [Loading, MetricsLoaded] on success', build: () { when(mockRepository.getMetrics()).thenAnswer((_) async => fakeMetrics()); return bloc; }, act: (b) => b.add(LoadMetrics()), expect: () => [ isA(), isA().having( (s) => s.metrics.cpuUsagePercent, 'cpuUsagePercent', 45.0, ), ], verify: (_) => verify(mockRepository.getMetrics()).called(1), ); blocTest( 'emits [Loading, LogsMonitoringError] on metrics failure', build: () { when(mockRepository.getMetrics()).thenThrow(Exception('metrics error')); return bloc; }, act: (b) => b.add(LoadMetrics()), expect: () => [isA(), isA()], ); }); // ── LoadAlerts ──────────────────────────────────────────────────────────── group('LoadAlerts', () { blocTest( '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(), isA().having((s) => s.alerts.length, 'alerts.length', 2), ], ); blocTest( 'emits [Loading, LogsMonitoringError] on alerts failure', build: () { when(mockRepository.getAlerts()).thenThrow(Exception('alerts error')); return bloc; }, act: (b) => b.add(LoadAlerts()), expect: () => [isA(), isA()], ); blocTest( 'emits [Loading, AlertsLoaded] with empty list', build: () { when(mockRepository.getAlerts()).thenAnswer((_) async => []); return bloc; }, act: (b) => b.add(LoadAlerts()), expect: () => [ isA(), isA().having((s) => s.alerts, 'alerts', isEmpty), ], ); }); // ── AcknowledgeAlert ────────────────────────────────────────────────────── group('AcknowledgeAlert', () { blocTest( '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(), isA(), isA().having( (s) => s.message, 'message', 'Alerte acquittée', ), ], verify: (_) { verify(mockRepository.acknowledgeAlert('alert-1')).called(1); verify(mockRepository.getAlerts()).called(1); }, ); blocTest( '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(), isA()], ); }); }