feat: WebSocket temps réel + Finance Workflow + corrections

- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics)
  * Backend: KafkaEventProducer, KafkaEventConsumer
  * Mobile: WebSocketService (reconnection, heartbeat, typed events)
  * DashboardBloc: Auto-refresh depuis WebSocket events

- Finance Workflow: approbations + budgets (backend + mobile)
  * Backend: entities, services, resources, migrations Flyway V6
  * Mobile: features finance_workflow complète avec BLoC

- Corrections DI: interfaces IRepository partout
  * IProfileRepository, IOrganizationRepository, IMembreRepository
  * GetIt configuré avec @injectable

- Spec-Kit: constitution + templates mis à jour
  * .specify/memory/constitution.md enrichie
  * Templates agent, plan, spec, tasks, checklist

- Nettoyage: fichiers temporaires supprimés

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -0,0 +1,67 @@
/// Tests unitaires pour ClearCache use case
library clear_cache_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/clear_cache.dart';
@GenerateMocks([ISystemConfigRepository])
import 'clear_cache_test.mocks.dart';
void main() {
late ClearCache useCase;
late MockISystemConfigRepository mockRepository;
setUp(() {
mockRepository = MockISystemConfigRepository();
useCase = ClearCache(mockRepository);
});
group('ClearCache Use Case', () {
test('should clear cache successfully', () async {
// Arrange
when(mockRepository.clearCache())
.thenAnswer((_) async => Future.value());
// Act
await useCase();
// Assert
verify(mockRepository.clearCache());
verifyNoMoreInteractions(mockRepository);
});
test('should complete without error when cache is empty', () async {
// Arrange
when(mockRepository.clearCache())
.thenAnswer((_) async => Future.value());
// Act
await useCase();
// Assert
verify(mockRepository.clearCache()).called(1);
});
test('should throw exception when clear operation fails', () async {
// Arrange
when(mockRepository.clearCache())
.thenThrow(Exception('Clear operation failed'));
// Act & Assert
expect(() => useCase(), throwsA(isA<Exception>()));
verify(mockRepository.clearCache());
});
test('should throw exception when permission denied', () async {
// Arrange
when(mockRepository.clearCache())
.thenThrow(Exception('Permission denied'));
// Act & Assert
expect(() => useCase(), throwsException);
});
});
}

View File

@@ -0,0 +1,96 @@
/// Tests unitaires pour GetCacheStats use case
library get_cache_stats_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/get_cache_stats.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/cache_stats_model.dart';
@GenerateMocks([ISystemConfigRepository])
import 'get_cache_stats_test.mocks.dart';
void main() {
late GetCacheStats useCase;
late MockISystemConfigRepository mockRepository;
setUp(() {
mockRepository = MockISystemConfigRepository();
useCase = GetCacheStats(mockRepository);
});
group('GetCacheStats Use Case', () {
final tCacheStats = CacheStatsModel(
totalEntries: 1000,
hits: 850,
misses: 150,
hitRate: 0.85,
totalSizeBytes: 1024 * 1024 * 50, // 50 MB
);
test('should return cache statistics', () async {
// Arrange
when(mockRepository.getCacheStats()).thenAnswer((_) async => tCacheStats);
// Act
final result = await useCase();
// Assert
expect(result, equals(tCacheStats));
expect(result.totalEntries, equals(1000));
expect(result.hitRate, equals(0.85));
verify(mockRepository.getCacheStats());
verifyNoMoreInteractions(mockRepository);
});
test('should handle empty cache', () async {
// Arrange
final emptyCacheStats = CacheStatsModel(
totalEntries: 0,
hits: 0,
misses: 0,
hitRate: 0.0,
totalSizeBytes: 0,
);
when(mockRepository.getCacheStats())
.thenAnswer((_) async => emptyCacheStats);
// Act
final result = await useCase();
// Assert
expect(result.totalEntries, equals(0));
expect(result.hitRate, equals(0.0));
});
test('should handle low hit rate cache', () async {
// Arrange
final lowHitCacheStats = CacheStatsModel(
totalEntries: 100,
hits: 20,
misses: 80,
hitRate: 0.20,
totalSizeBytes: 1024 * 100,
);
when(mockRepository.getCacheStats())
.thenAnswer((_) async => lowHitCacheStats);
// Act
final result = await useCase();
// Assert
expect(result.hitRate, lessThan(0.5));
expect(result.misses!, greaterThan(result.hits!));
});
test('should throw exception when stats retrieval fails', () async {
// Arrange
when(mockRepository.getCacheStats())
.thenThrow(Exception('Stats unavailable'));
// Act & Assert
expect(() => useCase(), throwsException);
});
});
}

View File

@@ -0,0 +1,94 @@
/// Tests unitaires pour GetSettings use case
library get_settings_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/get_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/system_config_model.dart';
@GenerateMocks([ISystemConfigRepository])
import 'get_settings_test.mocks.dart';
void main() {
late GetSettings useCase;
late MockISystemConfigRepository mockRepository;
setUp(() {
mockRepository = MockISystemConfigRepository();
useCase = GetSettings(mockRepository);
});
group('GetSettings Use Case', () {
final tConfig = SystemConfigModel(
applicationName: 'UnionFlow',
version: '1.0.0',
maintenanceMode: false,
defaultLanguage: 'fr',
timezone: 'Europe/Paris',
sessionTimeoutMinutes: 30,
);
test('should return system configuration', () async {
// Arrange
when(mockRepository.getConfig()).thenAnswer((_) async => tConfig);
// Act
final result = await useCase();
// Assert
expect(result, equals(tConfig));
expect(result.applicationName, equals('UnionFlow'));
expect(result.maintenanceMode, isFalse);
verify(mockRepository.getConfig());
verifyNoMoreInteractions(mockRepository);
});
test('should return config with all optional fields', () async {
// Arrange
final fullConfig = SystemConfigModel(
applicationName: 'UnionFlow',
version: '1.0.0',
maintenanceMode: false,
defaultLanguage: 'fr',
timezone: 'Europe/Paris',
networkTimeout: 30000,
sessionTimeoutMinutes: 60,
twoFactorAuthEnabled: true,
);
when(mockRepository.getConfig()).thenAnswer((_) async => fullConfig);
// Act
final result = await useCase();
// Assert
expect(result.networkTimeout, equals(30000));
expect(result.twoFactorAuthEnabled, isTrue);
});
test('should handle minimal config', () async {
// Arrange
final minimalConfig = SystemConfigModel(
applicationName: 'UnionFlow',
);
when(mockRepository.getConfig()).thenAnswer((_) async => minimalConfig);
// Act
final result = await useCase();
// Assert
expect(result.applicationName, isNotNull);
verify(mockRepository.getConfig());
});
test('should throw exception when config retrieval fails', () async {
// Arrange
when(mockRepository.getConfig())
.thenThrow(Exception('Config not found'));
// Act & Assert
expect(() => useCase(), throwsException);
});
});
}

View File

@@ -0,0 +1,92 @@
/// Tests unitaires pour ResetSettings use case
library reset_settings_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/reset_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/system_config_model.dart';
@GenerateMocks([ISystemConfigRepository])
import 'reset_settings_test.mocks.dart';
void main() {
late ResetSettings useCase;
late MockISystemConfigRepository mockRepository;
setUp(() {
mockRepository = MockISystemConfigRepository();
useCase = ResetSettings(mockRepository);
});
group('ResetSettings Use Case', () {
final tDefaultConfig = SystemConfigModel(
applicationName: 'UnionFlow',
version: '1.0.0',
maintenanceMode: false,
defaultLanguage: 'fr',
timezone: 'Europe/Paris',
);
test('should reset configuration to default values', () async {
// Arrange
when(mockRepository.resetConfig()).thenAnswer((_) async => tDefaultConfig);
// Act
final result = await useCase();
// Assert
expect(result, equals(tDefaultConfig));
expect(result.maintenanceMode, isFalse);
expect(result.applicationName, equals('UnionFlow'));
expect(result.version, equals('1.0.0'));
verify(mockRepository.resetConfig());
verifyNoMoreInteractions(mockRepository);
});
test('should handle fallback when reset endpoint fails', () async {
// Arrange
when(mockRepository.resetConfig()).thenAnswer((_) async => tDefaultConfig);
// Act
final result = await useCase();
// Assert
expect(result, isNotNull);
expect(result.applicationName, equals('UnionFlow'));
verify(mockRepository.resetConfig());
});
test('should throw exception when all reset strategies fail', () async {
// Arrange
when(mockRepository.resetConfig()).thenThrow(Exception('Reset failed'));
// Act & Assert
expect(
() => useCase(),
throwsA(isA<Exception>()),
);
verify(mockRepository.resetConfig());
});
test('should return valid config with minimal required fields', () async {
// Arrange
final tMinimalConfig = SystemConfigModel(
applicationName: 'UnionFlow',
version: '1.0.0',
maintenanceMode: false,
);
when(mockRepository.resetConfig()).thenAnswer((_) async => tMinimalConfig);
// Act
final result = await useCase();
// Assert
expect(result.applicationName, isNotNull);
expect(result.version, isNotNull);
expect(result.maintenanceMode, isNotNull);
verify(mockRepository.resetConfig());
});
});
}

View File

@@ -0,0 +1,91 @@
/// Tests unitaires pour UpdateSettings use case
library update_settings_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/repositories/system_config_repository.dart';
import 'package:unionflow_mobile_apps/features/settings/domain/usecases/update_settings.dart';
import 'package:unionflow_mobile_apps/features/settings/data/models/system_config_model.dart';
@GenerateMocks([ISystemConfigRepository])
import 'update_settings_test.mocks.dart';
void main() {
late UpdateSettings useCase;
late MockISystemConfigRepository mockRepository;
setUp(() {
mockRepository = MockISystemConfigRepository();
useCase = UpdateSettings(mockRepository);
});
group('UpdateSettings Use Case', () {
final tConfigMap = {
'applicationName': 'UnionFlow Updated',
'maintenanceMode': true,
'sessionTimeoutMinutes': 45,
};
final tUpdatedConfig = SystemConfigModel(
applicationName: 'UnionFlow Updated',
maintenanceMode: true,
sessionTimeoutMinutes: 45,
);
test('should update configuration successfully', () async {
// Arrange
when(mockRepository.updateConfig(tConfigMap))
.thenAnswer((_) async => tUpdatedConfig);
// Act
final result = await useCase(tConfigMap);
// Assert
expect(result, equals(tUpdatedConfig));
expect(result.applicationName, equals('UnionFlow Updated'));
expect(result.maintenanceMode, isTrue);
verify(mockRepository.updateConfig(tConfigMap));
verifyNoMoreInteractions(mockRepository);
});
test('should update partial configuration', () async {
// Arrange
final partialConfig = {'maintenanceMode': false};
final expectedResult = SystemConfigModel(maintenanceMode: false);
when(mockRepository.updateConfig(partialConfig))
.thenAnswer((_) async => expectedResult);
// Act
final result = await useCase(partialConfig);
// Assert
expect(result.maintenanceMode, isFalse);
verify(mockRepository.updateConfig(partialConfig));
});
test('should handle empty config map', () async {
// Arrange
final emptyConfig = <String, dynamic>{};
final expectedResult = SystemConfigModel();
when(mockRepository.updateConfig(emptyConfig))
.thenAnswer((_) async => expectedResult);
// Act
final result = await useCase(emptyConfig);
// Assert
expect(result, isNotNull);
verify(mockRepository.updateConfig(emptyConfig));
});
test('should throw exception when update fails', () async {
// Arrange
when(mockRepository.updateConfig(any))
.thenThrow(Exception('Update failed'));
// Act & Assert
expect(() => useCase(tConfigMap), throwsException);
});
});
}