## 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>
254 lines
9.0 KiB
Dart
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>()],
|
|
);
|
|
});
|
|
}
|