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:
@@ -0,0 +1,270 @@
|
||||
/// Tests unitaires pour OfflineManager
|
||||
library offline_manager_test;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:unionflow_mobile_apps/core/network/offline_manager.dart';
|
||||
import 'package:unionflow_mobile_apps/core/storage/pending_operations_store.dart';
|
||||
import 'package:unionflow_mobile_apps/core/config/environment.dart';
|
||||
|
||||
@GenerateMocks([
|
||||
Connectivity,
|
||||
PendingOperationsStore,
|
||||
])
|
||||
import 'offline_manager_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
// Initialize AppConfig for tests
|
||||
setUpAll(() {
|
||||
AppConfig.initialize();
|
||||
});
|
||||
|
||||
group('OfflineManager', () {
|
||||
late OfflineManager offlineManager;
|
||||
late MockConnectivity mockConnectivity;
|
||||
late MockPendingOperationsStore mockOperationsStore;
|
||||
late StreamController<List<ConnectivityResult>> connectivityController;
|
||||
|
||||
setUp(() {
|
||||
mockConnectivity = MockConnectivity();
|
||||
mockOperationsStore = MockPendingOperationsStore();
|
||||
connectivityController = StreamController<List<ConnectivityResult>>.broadcast();
|
||||
|
||||
// Setup default stubs
|
||||
when(mockConnectivity.onConnectivityChanged)
|
||||
.thenAnswer((_) => connectivityController.stream);
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.wifi]);
|
||||
when(mockOperationsStore.getPendingOperations())
|
||||
.thenAnswer((_) async => []);
|
||||
when(mockOperationsStore.addPendingOperation(
|
||||
operationType: anyNamed('operationType'),
|
||||
endpoint: anyNamed('endpoint'),
|
||||
data: anyNamed('data'),
|
||||
headers: anyNamed('headers'),
|
||||
)).thenAnswer((_) async => Future.value());
|
||||
|
||||
offlineManager = OfflineManager(mockConnectivity, mockOperationsStore);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
connectivityController.close();
|
||||
offlineManager.dispose();
|
||||
});
|
||||
|
||||
group('Connectivity status', () {
|
||||
test('should initialize with unknown status', () {
|
||||
// Assert
|
||||
expect(offlineManager.currentStatus, equals(ConnectivityStatus.unknown));
|
||||
});
|
||||
|
||||
test('should detect online status when WiFi is connected', () async {
|
||||
// Arrange
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.wifi]);
|
||||
|
||||
// Act
|
||||
final isOnline = await offlineManager.isOnline;
|
||||
|
||||
// Assert
|
||||
expect(isOnline, isTrue);
|
||||
});
|
||||
|
||||
test('should detect online status when mobile is connected', () async {
|
||||
// Arrange
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.mobile]);
|
||||
|
||||
// Act
|
||||
final isOnline = await offlineManager.isOnline;
|
||||
|
||||
// Assert
|
||||
expect(isOnline, isTrue);
|
||||
});
|
||||
|
||||
test('should detect offline status when no connectivity', () async {
|
||||
// Arrange
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.none]);
|
||||
|
||||
// Act
|
||||
final isOnline = await offlineManager.isOnline;
|
||||
|
||||
// Assert
|
||||
expect(isOnline, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('Status stream', () {
|
||||
test('should emit online status when WiFi connects', () async {
|
||||
// Arrange
|
||||
final statusUpdates = <ConnectivityStatus>[];
|
||||
offlineManager.statusStream.listen(statusUpdates.add);
|
||||
|
||||
// Act
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Assert
|
||||
expect(statusUpdates, contains(ConnectivityStatus.online));
|
||||
});
|
||||
|
||||
test('should emit offline status when connection is lost', () async {
|
||||
// Arrange
|
||||
final statusUpdates = <ConnectivityStatus>[];
|
||||
offlineManager.statusStream.listen(statusUpdates.add);
|
||||
|
||||
// Act - First connect, then disconnect
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
connectivityController.add([ConnectivityResult.none]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Assert
|
||||
expect(statusUpdates, contains(ConnectivityStatus.online));
|
||||
expect(statusUpdates, contains(ConnectivityStatus.offline));
|
||||
});
|
||||
|
||||
test('should not emit duplicate status updates', () async {
|
||||
// Arrange
|
||||
final statusUpdates = <ConnectivityStatus>[];
|
||||
offlineManager.statusStream.listen(statusUpdates.add);
|
||||
|
||||
// Act - Send same status multiple times
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Assert - Should only emit once
|
||||
expect(statusUpdates.where((s) => s == ConnectivityStatus.online).length, equals(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('Operation queueing', () {
|
||||
test('should queue operation when offline', () async {
|
||||
// Arrange
|
||||
const operationType = 'approveTransaction';
|
||||
const endpoint = '/api/finance/approvals/123/approve';
|
||||
final data = {'approvalId': '123', 'comment': 'Approved'};
|
||||
|
||||
// Act
|
||||
await offlineManager.queueOperation(
|
||||
operationType: operationType,
|
||||
endpoint: endpoint,
|
||||
data: data,
|
||||
);
|
||||
|
||||
// Assert
|
||||
verify(mockOperationsStore.addPendingOperation(
|
||||
operationType: operationType,
|
||||
endpoint: endpoint,
|
||||
data: data,
|
||||
headers: null,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should include headers when queueing operation', () async {
|
||||
// Arrange
|
||||
const operationType = 'createBudget';
|
||||
const endpoint = '/api/finance/budgets';
|
||||
final data = {'name': 'Test Budget'};
|
||||
final headers = {'Authorization': 'Bearer token123'};
|
||||
|
||||
// Act
|
||||
await offlineManager.queueOperation(
|
||||
operationType: operationType,
|
||||
endpoint: endpoint,
|
||||
data: data,
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
// Assert
|
||||
verify(mockOperationsStore.addPendingOperation(
|
||||
operationType: operationType,
|
||||
endpoint: endpoint,
|
||||
data: data,
|
||||
headers: headers,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('should get count of pending operations', () async {
|
||||
// Arrange
|
||||
when(mockOperationsStore.getPendingOperations()).thenAnswer(
|
||||
(_) async => [
|
||||
{'id': '1', 'operationType': 'approve'},
|
||||
{'id': '2', 'operationType': 'reject'},
|
||||
],
|
||||
);
|
||||
|
||||
// Act
|
||||
final count = await offlineManager.getPendingOperationsCount();
|
||||
|
||||
// Assert
|
||||
expect(count, equals(2));
|
||||
});
|
||||
});
|
||||
|
||||
group('Clear operations', () {
|
||||
test('should clear all pending operations', () async {
|
||||
// Arrange
|
||||
when(mockOperationsStore.clearAll()).thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await offlineManager.clearPendingOperations();
|
||||
|
||||
// Assert
|
||||
verify(mockOperationsStore.clearAll()).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
group('Retry pending operations', () {
|
||||
test('should not retry when offline', () async {
|
||||
// Arrange
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.none]);
|
||||
connectivityController.add([ConnectivityResult.none]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Act
|
||||
await offlineManager.retryPendingOperations();
|
||||
|
||||
// Assert
|
||||
verifyNever(mockOperationsStore.getPendingOperations());
|
||||
});
|
||||
|
||||
test('should process pending operations when manually triggered and online', () async {
|
||||
// Arrange
|
||||
when(mockConnectivity.checkConnectivity())
|
||||
.thenAnswer((_) async => [ConnectivityResult.wifi]);
|
||||
connectivityController.add([ConnectivityResult.wifi]);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
when(mockOperationsStore.getPendingOperations()).thenAnswer(
|
||||
(_) async => [
|
||||
{
|
||||
'id': '1',
|
||||
'operationType': 'approve',
|
||||
'endpoint': '/api/finance/approvals/123/approve',
|
||||
'data': {},
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
// Act
|
||||
await offlineManager.retryPendingOperations();
|
||||
|
||||
// Assert
|
||||
verify(mockOperationsStore.getPendingOperations()).called(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/// Tests unitaires pour RetryPolicy
|
||||
library retry_policy_test;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:unionflow_mobile_apps/core/network/retry_policy.dart';
|
||||
|
||||
void main() {
|
||||
group('RetryPolicy', () {
|
||||
late RetryPolicy retryPolicy;
|
||||
|
||||
setUp(() {
|
||||
retryPolicy = RetryPolicy(
|
||||
config: const RetryConfig(
|
||||
maxAttempts: 3,
|
||||
initialDelayMs: 100, // Short delay for tests
|
||||
maxDelayMs: 1000,
|
||||
backoffMultiplier: 2.0,
|
||||
useJitter: false, // Disable jitter for predictable tests
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
group('execute - Happy path', () {
|
||||
test('should return result when operation succeeds on first attempt', () async {
|
||||
// Arrange
|
||||
const expectedResult = 'success';
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
return expectedResult;
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(operation: operation);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(expectedResult));
|
||||
expect(attempts, equals(1));
|
||||
});
|
||||
|
||||
test('should retry and succeed on second attempt', () async {
|
||||
// Arrange
|
||||
const expectedResult = 'success';
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts == 1) {
|
||||
throw Exception('Temporary failure');
|
||||
}
|
||||
return expectedResult;
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: (_) => true,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(expectedResult));
|
||||
expect(attempts, equals(2));
|
||||
});
|
||||
|
||||
test('should retry maximum attempts and succeed on last attempt', () async {
|
||||
// Arrange
|
||||
const expectedResult = 'success';
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts < 3) {
|
||||
throw Exception('Temporary failure');
|
||||
}
|
||||
return expectedResult;
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: (_) => true,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(expectedResult));
|
||||
expect(attempts, equals(3));
|
||||
});
|
||||
});
|
||||
|
||||
group('execute - Retry exhaustion', () {
|
||||
test('should throw error when all retries are exhausted', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
throw Exception('Persistent failure');
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: (_) => true,
|
||||
),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
|
||||
// Wait for all attempts
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
expect(attempts, equals(3)); // maxAttempts
|
||||
});
|
||||
|
||||
test('should not retry when shouldRetry returns false', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
throw Exception('Non-retryable error');
|
||||
}
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: (_) => false,
|
||||
),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
|
||||
expect(attempts, equals(1)); // No retries
|
||||
});
|
||||
});
|
||||
|
||||
group('execute - Error classification', () {
|
||||
test('should retry timeout exceptions by default', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts == 1) {
|
||||
throw TimeoutException('Request timeout');
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(operation: operation);
|
||||
|
||||
// Assert
|
||||
expect(result, equals('success'));
|
||||
expect(attempts, equals(2));
|
||||
});
|
||||
|
||||
test('should use custom shouldRetry function', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
final specificError = Exception('Specific retryable error');
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts == 1) {
|
||||
throw specificError;
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
||||
bool customShouldRetry(dynamic error) {
|
||||
return error == specificError;
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: customShouldRetry,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals('success'));
|
||||
expect(attempts, equals(2));
|
||||
});
|
||||
});
|
||||
|
||||
group('execute - Retry callback', () {
|
||||
test('should call onRetry callback with attempt details', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
final retryCallbacks = <Map<String, dynamic>>[];
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts < 3) {
|
||||
throw Exception('Temporary failure');
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
||||
void onRetry(int attempt, dynamic error, Duration delay) {
|
||||
retryCallbacks.add({
|
||||
'attempt': attempt,
|
||||
'error': error,
|
||||
'delay': delay,
|
||||
});
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await retryPolicy.execute(
|
||||
operation: operation,
|
||||
shouldRetry: (_) => true,
|
||||
onRetry: onRetry,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals('success'));
|
||||
expect(retryCallbacks.length, equals(2)); // 2 retries before success
|
||||
|
||||
// Verify first retry callback
|
||||
expect(retryCallbacks[0]['attempt'], equals(1));
|
||||
expect(retryCallbacks[0]['error'], isA<Exception>());
|
||||
expect(retryCallbacks[0]['delay'], isA<Duration>());
|
||||
|
||||
// Verify second retry callback
|
||||
expect(retryCallbacks[1]['attempt'], equals(2));
|
||||
expect(retryCallbacks[1]['error'], isA<Exception>());
|
||||
expect(retryCallbacks[1]['delay'], isA<Duration>());
|
||||
});
|
||||
});
|
||||
|
||||
group('RetryConfig', () {
|
||||
test('should use default configuration values', () {
|
||||
// Arrange & Act
|
||||
const config = RetryConfig();
|
||||
|
||||
// Assert
|
||||
expect(config.maxAttempts, equals(3));
|
||||
expect(config.initialDelayMs, equals(1000));
|
||||
expect(config.maxDelayMs, equals(30000));
|
||||
expect(config.backoffMultiplier, equals(2.0));
|
||||
expect(config.useJitter, equals(true));
|
||||
});
|
||||
|
||||
test('should use critical configuration preset', () {
|
||||
// Arrange & Act
|
||||
const config = RetryConfig.critical;
|
||||
|
||||
// Assert
|
||||
expect(config.maxAttempts, equals(5));
|
||||
expect(config.initialDelayMs, equals(500));
|
||||
expect(config.maxDelayMs, equals(60000));
|
||||
});
|
||||
|
||||
test('should use background sync configuration preset', () {
|
||||
// Arrange & Act
|
||||
const config = RetryConfig.backgroundSync;
|
||||
|
||||
// Assert
|
||||
expect(config.maxAttempts, equals(10));
|
||||
expect(config.initialDelayMs, equals(2000));
|
||||
expect(config.maxDelayMs, equals(120000));
|
||||
});
|
||||
});
|
||||
|
||||
group('RetryExtension', () {
|
||||
test('should add withRetry method to Future functions', () async {
|
||||
// Arrange
|
||||
int attempts = 0;
|
||||
|
||||
Future<String> operation() async {
|
||||
attempts++;
|
||||
if (attempts == 1) {
|
||||
throw Exception('First attempt fails');
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
|
||||
// Act
|
||||
final result = await operation.withRetry(
|
||||
config: const RetryConfig(
|
||||
maxAttempts: 3,
|
||||
initialDelayMs: 100,
|
||||
useJitter: false,
|
||||
),
|
||||
shouldRetry: (_) => true,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals('success'));
|
||||
expect(attempts, equals(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/// Tests unitaires pour les validators
|
||||
library validators_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:unionflow_mobile_apps/core/validation/validators.dart';
|
||||
|
||||
void main() {
|
||||
group('Validators', () {
|
||||
group('required', () {
|
||||
test('should return error for null value', () {
|
||||
final validator = Validators.required();
|
||||
expect(validator!(''), equals('Ce champ est requis'));
|
||||
});
|
||||
|
||||
test('should return error for empty string', () {
|
||||
final validator = Validators.required();
|
||||
expect(validator!(''), equals('Ce champ est requis'));
|
||||
});
|
||||
|
||||
test('should return error for whitespace only', () {
|
||||
final validator = Validators.required();
|
||||
expect(validator!(' '), equals('Ce champ est requis'));
|
||||
});
|
||||
|
||||
test('should return null for valid value', () {
|
||||
final validator = Validators.required();
|
||||
expect(validator!('value'), isNull);
|
||||
});
|
||||
|
||||
test('should use custom message', () {
|
||||
final validator = Validators.required(message: 'Custom error');
|
||||
expect(validator!(''), equals('Custom error'));
|
||||
});
|
||||
});
|
||||
|
||||
group('minLength', () {
|
||||
test('should return error when value is too short', () {
|
||||
final validator = Validators.minLength(5);
|
||||
expect(validator!('abc'), equals('Minimum 5 caractères requis'));
|
||||
});
|
||||
|
||||
test('should return null when value meets minimum', () {
|
||||
final validator = Validators.minLength(5);
|
||||
expect(validator!('abcde'), isNull);
|
||||
});
|
||||
|
||||
test('should return null when value exceeds minimum', () {
|
||||
final validator = Validators.minLength(5);
|
||||
expect(validator!('abcdefgh'), isNull);
|
||||
});
|
||||
|
||||
test('should trim value before checking length', () {
|
||||
final validator = Validators.minLength(5);
|
||||
expect(validator!(' abc '), equals('Minimum 5 caractères requis'));
|
||||
});
|
||||
});
|
||||
|
||||
group('maxLength', () {
|
||||
test('should return error when value is too long', () {
|
||||
final validator = Validators.maxLength(5);
|
||||
expect(validator!('abcdefgh'), equals('Maximum 5 caractères autorisés'));
|
||||
});
|
||||
|
||||
test('should return null when value meets maximum', () {
|
||||
final validator = Validators.maxLength(5);
|
||||
expect(validator!('abcde'), isNull);
|
||||
});
|
||||
|
||||
test('should return null when value is under maximum', () {
|
||||
final validator = Validators.maxLength(5);
|
||||
expect(validator!('abc'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('email', () {
|
||||
test('should return null for valid email', () {
|
||||
final validator = Validators.email();
|
||||
expect(validator!('test@example.com'), isNull);
|
||||
expect(validator!('user.name@domain.co.uk'), isNull);
|
||||
expect(validator!('user+tag@example.com'), isNull);
|
||||
});
|
||||
|
||||
test('should return error for invalid email', () {
|
||||
final validator = Validators.email();
|
||||
expect(validator!('invalid'), equals('Adresse email invalide'));
|
||||
expect(validator!('test@'), equals('Adresse email invalide'));
|
||||
expect(validator!('@example.com'), equals('Adresse email invalide'));
|
||||
expect(validator!('test @example.com'), equals('Adresse email invalide'));
|
||||
});
|
||||
|
||||
test('should return null for empty value (use required separately)', () {
|
||||
final validator = Validators.email();
|
||||
expect(validator!(''), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('numeric', () {
|
||||
test('should return null for valid numbers', () {
|
||||
final validator = Validators.numeric();
|
||||
expect(validator!('123'), isNull);
|
||||
expect(validator!('123.45'), isNull);
|
||||
expect(validator!('-123'), isNull);
|
||||
expect(validator!('0'), isNull);
|
||||
});
|
||||
|
||||
test('should return error for non-numeric values', () {
|
||||
final validator = Validators.numeric();
|
||||
expect(validator!('abc'), equals('Veuillez entrer un nombre valide'));
|
||||
expect(validator!('12.34.56'), equals('Veuillez entrer un nombre valide'));
|
||||
});
|
||||
|
||||
test('should return null for empty value', () {
|
||||
final validator = Validators.numeric();
|
||||
expect(validator!(''), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('minValue', () {
|
||||
test('should return error when value is below minimum', () {
|
||||
final validator = Validators.minValue(10);
|
||||
expect(validator!('5'), equals('La valeur doit être au moins 10.0'));
|
||||
});
|
||||
|
||||
test('should return null when value meets minimum', () {
|
||||
final validator = Validators.minValue(10);
|
||||
expect(validator!('10'), isNull);
|
||||
});
|
||||
|
||||
test('should return null when value exceeds minimum', () {
|
||||
final validator = Validators.minValue(10);
|
||||
expect(validator!('15'), isNull);
|
||||
});
|
||||
|
||||
test('should work with decimals', () {
|
||||
final validator = Validators.minValue(10.5);
|
||||
expect(validator!('10.4'), contains('au moins'));
|
||||
expect(validator!('10.5'), isNull);
|
||||
expect(validator!('10.6'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('maxValue', () {
|
||||
test('should return error when value exceeds maximum', () {
|
||||
final validator = Validators.maxValue(100);
|
||||
expect(validator!('150'), equals('La valeur doit être au maximum 100.0'));
|
||||
});
|
||||
|
||||
test('should return null when value meets maximum', () {
|
||||
final validator = Validators.maxValue(100);
|
||||
expect(validator!('100'), isNull);
|
||||
});
|
||||
|
||||
test('should return null when value is below maximum', () {
|
||||
final validator = Validators.maxValue(100);
|
||||
expect(validator!('50'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('range', () {
|
||||
test('should return error when value is below range', () {
|
||||
final validator = Validators.range(10, 100);
|
||||
expect(validator!('5'), contains('entre'));
|
||||
});
|
||||
|
||||
test('should return error when value is above range', () {
|
||||
final validator = Validators.range(10, 100);
|
||||
expect(validator!('150'), contains('entre'));
|
||||
});
|
||||
|
||||
test('should return null when value is within range', () {
|
||||
final validator = Validators.range(10, 100);
|
||||
expect(validator!('10'), isNull);
|
||||
expect(validator!('50'), isNull);
|
||||
expect(validator!('100'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('phone', () {
|
||||
test('should return null for valid phone numbers', () {
|
||||
final validator = Validators.phone();
|
||||
expect(validator!('+33612345678'), isNull);
|
||||
expect(validator!('06 12 34 56 78'), isNull);
|
||||
expect(validator!('(123) 456-7890'), isNull);
|
||||
});
|
||||
|
||||
test('should return error for invalid phone numbers', () {
|
||||
final validator = Validators.phone();
|
||||
expect(validator!('abc'), equals('Numéro de téléphone invalide'));
|
||||
expect(validator!('123'), equals('Numéro de téléphone trop court'));
|
||||
});
|
||||
|
||||
test('should return null for empty value', () {
|
||||
final validator = Validators.phone();
|
||||
expect(validator!(''), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('pattern', () {
|
||||
test('should validate against custom regex', () {
|
||||
final validator = Validators.pattern(
|
||||
RegExp(r'^[A-Z]{3}\d{3}$'),
|
||||
message: 'Format: 3 lettres majuscules + 3 chiffres',
|
||||
);
|
||||
|
||||
expect(validator!('ABC123'), isNull);
|
||||
expect(validator!('XYZ999'), isNull);
|
||||
expect(validator!('abc123'), equals('Format: 3 lettres majuscules + 3 chiffres'));
|
||||
expect(validator!('AB123'), equals('Format: 3 lettres majuscules + 3 chiffres'));
|
||||
});
|
||||
});
|
||||
|
||||
group('match', () {
|
||||
test('should return error when values do not match', () {
|
||||
final validator = Validators.match('password123');
|
||||
expect(validator!('password456'), equals('Les valeurs ne correspondent pas'));
|
||||
});
|
||||
|
||||
test('should return null when values match', () {
|
||||
final validator = Validators.match('password123');
|
||||
expect(validator!('password123'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('composeValidators', () {
|
||||
test('should run all validators in sequence', () {
|
||||
final validator = composeValidators([
|
||||
Validators.required(),
|
||||
Validators.minLength(5),
|
||||
Validators.maxLength(10),
|
||||
]);
|
||||
|
||||
expect(validator!(''), equals('Ce champ est requis'));
|
||||
expect(validator!('abc'), equals('Minimum 5 caractères requis'));
|
||||
expect(validator!('12345678901'), equals('Maximum 10 caractères autorisés'));
|
||||
expect(validator!('valid'), isNull);
|
||||
});
|
||||
|
||||
test('should stop at first error', () {
|
||||
final validator = composeValidators([
|
||||
Validators.required(),
|
||||
Validators.email(),
|
||||
]);
|
||||
|
||||
// Should fail on required, not reach email validator
|
||||
expect(validator!(''), equals('Ce champ est requis'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('FinanceValidators', () {
|
||||
group('amount', () {
|
||||
test('should return null for valid amounts', () {
|
||||
final validator = FinanceValidators.amount();
|
||||
expect(validator!('100'), isNull);
|
||||
expect(validator!('100.50'), isNull);
|
||||
expect(validator!('0.01'), isNull);
|
||||
});
|
||||
|
||||
test('should return error for negative or zero amounts', () {
|
||||
final validator = FinanceValidators.amount();
|
||||
expect(validator!('0'), equals('Le montant doit être positif'));
|
||||
expect(validator!('-10'), equals('Le montant doit être positif'));
|
||||
});
|
||||
|
||||
test('should return error for invalid numbers', () {
|
||||
final validator = FinanceValidators.amount();
|
||||
expect(validator!('abc'), equals('Montant invalide'));
|
||||
});
|
||||
|
||||
test('should enforce minimum amount', () {
|
||||
final validator = FinanceValidators.amount(min: 100);
|
||||
expect(validator!('50'), equals('Le montant minimum est 100.0'));
|
||||
expect(validator!('100'), isNull);
|
||||
expect(validator!('150'), isNull);
|
||||
});
|
||||
|
||||
test('should enforce maximum amount', () {
|
||||
final validator = FinanceValidators.amount(max: 1000);
|
||||
expect(validator!('1500'), equals('Le montant maximum est 1000.0'));
|
||||
expect(validator!('1000'), isNull);
|
||||
expect(validator!('500'), isNull);
|
||||
});
|
||||
|
||||
test('should enforce max 2 decimals', () {
|
||||
final validator = FinanceValidators.amount();
|
||||
expect(validator!('100.123'), equals('Maximum 2 décimales autorisées'));
|
||||
expect(validator!('100.12'), isNull);
|
||||
expect(validator!('100.1'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('budgetLineName', () {
|
||||
test('should require name', () {
|
||||
final validator = FinanceValidators.budgetLineName();
|
||||
expect(validator!(''), contains('requis'));
|
||||
});
|
||||
|
||||
test('should enforce min length', () {
|
||||
final validator = FinanceValidators.budgetLineName();
|
||||
expect(validator!('ab'), contains('Minimum 3 caractères'));
|
||||
});
|
||||
|
||||
test('should enforce max length', () {
|
||||
final validator = FinanceValidators.budgetLineName();
|
||||
final longName = 'a' * 101;
|
||||
expect(validator!(longName), contains('Maximum 100 caractères'));
|
||||
});
|
||||
|
||||
test('should accept valid names', () {
|
||||
final validator = FinanceValidators.budgetLineName();
|
||||
expect(validator!('Cotisations'), isNull);
|
||||
expect(validator!('Ligne budgétaire test'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('rejectionReason', () {
|
||||
test('should require reason', () {
|
||||
final validator = FinanceValidators.rejectionReason();
|
||||
expect(validator!(''), contains('requis'));
|
||||
});
|
||||
|
||||
test('should enforce min length', () {
|
||||
final validator = FinanceValidators.rejectionReason();
|
||||
expect(validator!('short'), contains('min 10 caractères'));
|
||||
});
|
||||
|
||||
test('should enforce max length', () {
|
||||
final validator = FinanceValidators.rejectionReason();
|
||||
final longReason = 'a' * 501;
|
||||
expect(validator!(longReason), contains('Maximum 500 caractères'));
|
||||
});
|
||||
|
||||
test('should accept valid reasons', () {
|
||||
final validator = FinanceValidators.rejectionReason();
|
||||
expect(validator!('Cette transaction ne respecte pas les règles'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('fiscalYear', () {
|
||||
test('should require year', () {
|
||||
final validator = FinanceValidators.fiscalYear();
|
||||
expect(validator!(''), contains('requis'));
|
||||
});
|
||||
|
||||
test('should reject invalid year format', () {
|
||||
final validator = FinanceValidators.fiscalYear();
|
||||
expect(validator!('abc'), equals('Année invalide'));
|
||||
});
|
||||
|
||||
test('should enforce year range', () {
|
||||
final validator = FinanceValidators.fiscalYear();
|
||||
final currentYear = DateTime.now().year;
|
||||
|
||||
expect(validator!('${currentYear - 10}'), contains('doit être entre'));
|
||||
expect(validator!('${currentYear + 15}'), contains('doit être entre'));
|
||||
});
|
||||
|
||||
test('should accept valid years', () {
|
||||
final validator = FinanceValidators.fiscalYear();
|
||||
final currentYear = DateTime.now().year;
|
||||
|
||||
expect(validator!('$currentYear'), isNull);
|
||||
expect(validator!('${currentYear + 1}'), isNull);
|
||||
expect(validator!('${currentYear - 1}'), isNull);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/// Tests unitaires pour GetConversations use case
|
||||
library get_conversations_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/usecases/get_conversations.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/repositories/messaging_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/entities/conversation.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([MessagingRepository])
|
||||
import 'get_conversations_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetConversations useCase;
|
||||
late MockMessagingRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockMessagingRepository();
|
||||
useCase = GetConversations(mockRepository);
|
||||
});
|
||||
|
||||
group('GetConversations Use Case', () {
|
||||
final tConversations = [
|
||||
Conversation(
|
||||
id: 'conv-1',
|
||||
name: 'Discussion Projet Alpha',
|
||||
type: ConversationType.group,
|
||||
participantIds: ['user-1', 'user-2', 'user-3'],
|
||||
organizationId: 'org-123',
|
||||
unreadCount: 5,
|
||||
isPinned: true,
|
||||
createdAt: DateTime(2024, 12, 1),
|
||||
),
|
||||
Conversation(
|
||||
id: 'conv-2',
|
||||
name: 'Fatou Ndiaye',
|
||||
type: ConversationType.individual,
|
||||
participantIds: ['user-1', 'user-4'],
|
||||
unreadCount: 0,
|
||||
createdAt: DateTime(2024, 12, 10),
|
||||
),
|
||||
];
|
||||
|
||||
test('should return list of conversations successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getConversations(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
includeArchived: anyNamed('includeArchived'),
|
||||
)).thenAnswer((_) async => Right(tConversations));
|
||||
|
||||
// Act
|
||||
final result = await useCase(organizationId: 'org-123');
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tConversations));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(conversations) {
|
||||
expect(conversations.length, equals(2));
|
||||
expect(conversations[0].name, equals('Discussion Projet Alpha'));
|
||||
expect(conversations[0].unreadCount, equals(5));
|
||||
expect(conversations[0].isPinned, isTrue);
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getConversations(
|
||||
organizationId: 'org-123',
|
||||
includeArchived: false,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return conversations with archived included', () async {
|
||||
// Arrange
|
||||
final archivedConv = Conversation(
|
||||
id: 'conv-3',
|
||||
name: 'Ancienne Discussion',
|
||||
type: ConversationType.group,
|
||||
participantIds: ['user-1', 'user-2'],
|
||||
isArchived: true,
|
||||
createdAt: DateTime(2024, 11, 1),
|
||||
);
|
||||
when(mockRepository.getConversations(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
includeArchived: true,
|
||||
)).thenAnswer((_) async => Right([...tConversations, archivedConv]));
|
||||
|
||||
// Act
|
||||
final result = await useCase(includeArchived: true);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(conversations) {
|
||||
expect(conversations.length, equals(3));
|
||||
expect(conversations.any((c) => c.isArchived), isTrue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty list when no conversations exist', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getConversations(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
includeArchived: anyNamed('includeArchived'),
|
||||
)).thenAnswer((_) async => Right([]));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(conversations) => expect(conversations, isEmpty),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getConversations(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
includeArchived: anyNamed('includeArchived'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(conversations) => fail('Should not return conversations'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/// Tests unitaires pour GetMessages use case
|
||||
library get_messages_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/usecases/get_messages.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/repositories/messaging_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/entities/message.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([MessagingRepository])
|
||||
import 'get_messages_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetMessages useCase;
|
||||
late MockMessagingRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockMessagingRepository();
|
||||
useCase = GetMessages(mockRepository);
|
||||
});
|
||||
|
||||
group('GetMessages Use Case', () {
|
||||
const tConversationId = 'conv-123';
|
||||
final tMessages = [
|
||||
Message(
|
||||
id: 'msg-1',
|
||||
conversationId: tConversationId,
|
||||
senderId: 'user-1',
|
||||
senderName: 'Amadou Diallo',
|
||||
content: 'Bonjour à tous!',
|
||||
type: MessageType.individual,
|
||||
status: MessageStatus.read,
|
||||
priority: MessagePriority.normal,
|
||||
recipientIds: ['user-2', 'user-3'],
|
||||
createdAt: DateTime(2024, 12, 15, 10, 0),
|
||||
),
|
||||
Message(
|
||||
id: 'msg-2',
|
||||
conversationId: tConversationId,
|
||||
senderId: 'user-2',
|
||||
senderName: 'Fatou Ndiaye',
|
||||
content: 'Salut Amadou!',
|
||||
type: MessageType.individual,
|
||||
status: MessageStatus.delivered,
|
||||
priority: MessagePriority.normal,
|
||||
recipientIds: ['user-1'],
|
||||
createdAt: DateTime(2024, 12, 15, 10, 5),
|
||||
),
|
||||
];
|
||||
|
||||
test('should return list of messages successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMessages(
|
||||
conversationId: tConversationId,
|
||||
limit: anyNamed('limit'),
|
||||
beforeMessageId: anyNamed('beforeMessageId'),
|
||||
)).thenAnswer((_) async => Right(tMessages));
|
||||
|
||||
// Act
|
||||
final result = await useCase(conversationId: tConversationId);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tMessages));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(messages) {
|
||||
expect(messages.length, equals(2));
|
||||
expect(messages[0].content, equals('Bonjour à tous!'));
|
||||
expect(messages[0].status, equals(MessageStatus.read));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getMessages(
|
||||
conversationId: tConversationId,
|
||||
limit: null,
|
||||
beforeMessageId: null,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return paginated messages with limit', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMessages(
|
||||
conversationId: tConversationId,
|
||||
limit: 1,
|
||||
beforeMessageId: anyNamed('beforeMessageId'),
|
||||
)).thenAnswer((_) async => Right([tMessages[0]]));
|
||||
|
||||
// Act
|
||||
final result = await useCase(conversationId: tConversationId, limit: 1);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(messages) {
|
||||
expect(messages.length, equals(1));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty list when no messages exist', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMessages(
|
||||
conversationId: anyNamed('conversationId'),
|
||||
limit: anyNamed('limit'),
|
||||
beforeMessageId: anyNamed('beforeMessageId'),
|
||||
)).thenAnswer((_) async => Right([]));
|
||||
|
||||
// Act
|
||||
final result = await useCase(conversationId: 'empty-conv');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(messages) => expect(messages, isEmpty),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getMessages(
|
||||
conversationId: anyNamed('conversationId'),
|
||||
limit: anyNamed('limit'),
|
||||
beforeMessageId: anyNamed('beforeMessageId'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(conversationId: tConversationId);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(messages) => fail('Should not return messages'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/// Tests unitaires pour SendBroadcast use case
|
||||
library send_broadcast_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/usecases/send_broadcast.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/repositories/messaging_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/entities/message.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([MessagingRepository])
|
||||
import 'send_broadcast_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late SendBroadcast useCase;
|
||||
late MockMessagingRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockMessagingRepository();
|
||||
useCase = SendBroadcast(mockRepository);
|
||||
});
|
||||
|
||||
group('SendBroadcast Use Case', () {
|
||||
const tOrgId = 'org-123';
|
||||
const tSubject = 'Assemblée Générale 2024';
|
||||
const tContent = 'Chers membres, l\'AG aura lieu le 20 décembre...';
|
||||
final tBroadcastMessage = Message(
|
||||
id: 'broadcast-1',
|
||||
conversationId: 'broadcast-conv',
|
||||
senderId: 'admin-1',
|
||||
senderName: 'Admin Organisation',
|
||||
content: tContent,
|
||||
type: MessageType.broadcast,
|
||||
status: MessageStatus.sent,
|
||||
priority: MessagePriority.high,
|
||||
recipientIds: ['all'],
|
||||
organizationId: tOrgId,
|
||||
createdAt: DateTime.now(),
|
||||
metadata: {'subject': tSubject},
|
||||
);
|
||||
|
||||
test('should send broadcast message successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.sendBroadcast(
|
||||
organizationId: tOrgId,
|
||||
subject: tSubject,
|
||||
content: tContent,
|
||||
priority: anyNamed('priority'),
|
||||
attachments: anyNamed('attachments'),
|
||||
)).thenAnswer((_) async => Right(tBroadcastMessage));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
organizationId: tOrgId,
|
||||
subject: tSubject,
|
||||
content: tContent,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tBroadcastMessage));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(message) {
|
||||
expect(message.type, equals(MessageType.broadcast));
|
||||
expect(message.content, equals(tContent));
|
||||
expect(message.metadata?['subject'], equals(tSubject));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.sendBroadcast(
|
||||
organizationId: tOrgId,
|
||||
subject: tSubject,
|
||||
content: tContent,
|
||||
priority: MessagePriority.normal,
|
||||
attachments: null,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should send urgent broadcast with attachments', () async {
|
||||
// Arrange
|
||||
final attachments = ['document.pdf', 'plan.jpg'];
|
||||
final urgentBroadcast = Message(
|
||||
id: 'broadcast-urgent',
|
||||
conversationId: 'broadcast-conv',
|
||||
senderId: 'admin-1',
|
||||
senderName: 'Admin Organisation',
|
||||
content: 'URGENT: Annulation événement',
|
||||
type: MessageType.broadcast,
|
||||
status: MessageStatus.sent,
|
||||
priority: MessagePriority.urgent,
|
||||
recipientIds: ['all'],
|
||||
organizationId: tOrgId,
|
||||
attachments: attachments,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
when(mockRepository.sendBroadcast(
|
||||
organizationId: tOrgId,
|
||||
subject: 'URGENT',
|
||||
content: 'URGENT: Annulation événement',
|
||||
priority: MessagePriority.urgent,
|
||||
attachments: attachments,
|
||||
)).thenAnswer((_) async => Right(urgentBroadcast));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
organizationId: tOrgId,
|
||||
subject: 'URGENT',
|
||||
content: 'URGENT: Annulation événement',
|
||||
priority: MessagePriority.urgent,
|
||||
attachments: attachments,
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(message) {
|
||||
expect(message.priority, equals(MessagePriority.urgent));
|
||||
expect(message.attachments, equals(attachments));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when subject or content is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(
|
||||
organizationId: tOrgId,
|
||||
subject: '',
|
||||
content: 'Content',
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
},
|
||||
(message) => fail('Should not return message'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur d\'envoi broadcast');
|
||||
when(mockRepository.sendBroadcast(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
subject: anyNamed('subject'),
|
||||
content: anyNamed('content'),
|
||||
priority: anyNamed('priority'),
|
||||
attachments: anyNamed('attachments'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
organizationId: tOrgId,
|
||||
subject: tSubject,
|
||||
content: tContent,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(message) => fail('Should not return message'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/// Tests unitaires pour SendMessage use case
|
||||
library send_message_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/usecases/send_message.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/repositories/messaging_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/communication/domain/entities/message.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([MessagingRepository])
|
||||
import 'send_message_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late SendMessage useCase;
|
||||
late MockMessagingRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockMessagingRepository();
|
||||
useCase = SendMessage(mockRepository);
|
||||
});
|
||||
|
||||
group('SendMessage Use Case', () {
|
||||
const tConversationId = 'conv-123';
|
||||
const tContent = 'Bonjour, comment allez-vous?';
|
||||
final tSentMessage = Message(
|
||||
id: 'msg-new',
|
||||
conversationId: tConversationId,
|
||||
senderId: 'user-1',
|
||||
senderName: 'Amadou Diallo',
|
||||
content: tContent,
|
||||
type: MessageType.individual,
|
||||
status: MessageStatus.sent,
|
||||
priority: MessagePriority.normal,
|
||||
recipientIds: ['user-2'],
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
test('should send message successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.sendMessage(
|
||||
conversationId: tConversationId,
|
||||
content: tContent,
|
||||
attachments: anyNamed('attachments'),
|
||||
priority: anyNamed('priority'),
|
||||
)).thenAnswer((_) async => Right(tSentMessage));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
conversationId: tConversationId,
|
||||
content: tContent,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tSentMessage));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(message) {
|
||||
expect(message.id, equals('msg-new'));
|
||||
expect(message.content, equals(tContent));
|
||||
expect(message.status, equals(MessageStatus.sent));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.sendMessage(
|
||||
conversationId: tConversationId,
|
||||
content: tContent,
|
||||
attachments: null,
|
||||
priority: MessagePriority.normal,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should send urgent message with attachments', () async {
|
||||
// Arrange
|
||||
final attachments = ['file1.pdf', 'file2.jpg'];
|
||||
final urgentMessage = Message(
|
||||
id: 'msg-urgent',
|
||||
conversationId: tConversationId,
|
||||
senderId: 'user-1',
|
||||
senderName: 'Amadou Diallo',
|
||||
content: 'URGENT: Document important',
|
||||
type: MessageType.individual,
|
||||
status: MessageStatus.sent,
|
||||
priority: MessagePriority.urgent,
|
||||
recipientIds: ['user-2'],
|
||||
attachments: attachments,
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
when(mockRepository.sendMessage(
|
||||
conversationId: tConversationId,
|
||||
content: 'URGENT: Document important',
|
||||
attachments: attachments,
|
||||
priority: MessagePriority.urgent,
|
||||
)).thenAnswer((_) async => Right(urgentMessage));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
conversationId: tConversationId,
|
||||
content: 'URGENT: Document important',
|
||||
attachments: attachments,
|
||||
priority: MessagePriority.urgent,
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(message) {
|
||||
expect(message.priority, equals(MessagePriority.urgent));
|
||||
expect(message.attachments, equals(attachments));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when content is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(
|
||||
conversationId: tConversationId,
|
||||
content: ' ',
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
expect((failure as ValidationFailure).message, contains('ne peut pas être vide'));
|
||||
},
|
||||
(message) => fail('Should not return message'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur d\'envoi');
|
||||
when(mockRepository.sendMessage(
|
||||
conversationId: anyNamed('conversationId'),
|
||||
content: anyNamed('content'),
|
||||
attachments: anyNamed('attachments'),
|
||||
priority: anyNamed('priority'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
conversationId: tConversationId,
|
||||
content: tContent,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(message) => fail('Should not return message'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/// Tests unitaires pour CreateContribution use case
|
||||
library create_contribution_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/create_contribution.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'create_contribution_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CreateContribution useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = CreateContribution(mockRepository);
|
||||
});
|
||||
|
||||
group('CreateContribution Use Case', () {
|
||||
final tNewContribution = ContributionModel(
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
);
|
||||
|
||||
final tCreatedContribution = ContributionModel(
|
||||
id: 'cont123',
|
||||
membreId: 'membre1',
|
||||
membreNom: 'Dupont',
|
||||
membrePrenom: 'Jean',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
);
|
||||
|
||||
test('should create contribution successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createCotisation(tNewContribution))
|
||||
.thenAnswer((_) async => tCreatedContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tNewContribution);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCreatedContribution));
|
||||
expect(result.id, isNotNull);
|
||||
expect(result.id, equals('cont123'));
|
||||
expect(result.montant, equals(5000.0));
|
||||
verify(mockRepository.createCotisation(tNewContribution));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should create monthly contribution', () async {
|
||||
// Arrange
|
||||
final monthlyContribution = ContributionModel(
|
||||
membreId: 'membre1',
|
||||
montant: 2000.0,
|
||||
dateEcheance: DateTime(2024, 1, 31),
|
||||
annee: 2024,
|
||||
mois: 1,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
);
|
||||
final createdMonthly = ContributionModel(
|
||||
id: 'cont456',
|
||||
membreId: 'membre1',
|
||||
montant: 2000.0,
|
||||
dateEcheance: DateTime(2024, 1, 31),
|
||||
annee: 2024,
|
||||
mois: 1,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
);
|
||||
when(mockRepository.createCotisation(monthlyContribution))
|
||||
.thenAnswer((_) async => createdMonthly);
|
||||
|
||||
// Act
|
||||
final result = await useCase(monthlyContribution);
|
||||
|
||||
// Assert
|
||||
expect(result.type, equals(ContributionType.mensuelle));
|
||||
expect(result.mois, equals(1));
|
||||
});
|
||||
|
||||
test('should create contribution with description', () async {
|
||||
// Arrange
|
||||
final contributionWithDesc = ContributionModel(
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
annee: 2024,
|
||||
type: ContributionType.exceptionnelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
description: 'Cotisation exceptionnelle pour projet spécial',
|
||||
);
|
||||
when(mockRepository.createCotisation(any))
|
||||
.thenAnswer((_) async => contributionWithDesc.copyWith(id: 'cont789'));
|
||||
|
||||
// Act
|
||||
final result = await useCase(contributionWithDesc);
|
||||
|
||||
// Assert
|
||||
expect(result.description, isNotNull);
|
||||
expect(result.type, equals(ContributionType.exceptionnelle));
|
||||
});
|
||||
|
||||
test('should throw exception when creation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createCotisation(any))
|
||||
.thenThrow(Exception('Erreur de validation'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tNewContribution), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/// Tests unitaires pour DeleteContribution use case
|
||||
library delete_contribution_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/delete_contribution.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'delete_contribution_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late DeleteContribution useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = DeleteContribution(mockRepository);
|
||||
});
|
||||
|
||||
group('DeleteContribution Use Case', () {
|
||||
const tContributionId = 'cont123';
|
||||
|
||||
test('should delete contribution successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteCotisation(tContributionId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tContributionId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteCotisation(tContributionId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when contribution not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteCotisation(any))
|
||||
.thenThrow(Exception('Contribution non trouvée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsA(isA<Exception>()));
|
||||
verify(mockRepository.deleteCotisation('nonexistent'));
|
||||
});
|
||||
|
||||
test('should throw exception when contribution is already paid', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteCotisation(any))
|
||||
.thenThrow(Exception('Impossible de supprimer une cotisation payée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tContributionId), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when deletion fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteCotisation(any))
|
||||
.thenThrow(Exception('Erreur de suppression'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tContributionId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/// Tests unitaires pour GetContributionById use case
|
||||
library get_contribution_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'get_contribution_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetContributionById useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = GetContributionById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetContributionById Use Case', () {
|
||||
const tContributionId = 'cont123';
|
||||
final tContribution = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
membreNom: 'Dupont',
|
||||
membrePrenom: 'Jean',
|
||||
montant: 5000.0,
|
||||
montantPaye: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
datePaiement: DateTime(2024, 11, 15),
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.payee,
|
||||
methodePaiement: PaymentMethod.waveMoney,
|
||||
numeroPaiement: 'WAVE123456',
|
||||
);
|
||||
|
||||
test('should return contribution by id', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getCotisationById(tContributionId))
|
||||
.thenAnswer((_) async => tContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tContributionId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tContribution));
|
||||
expect(result.id, equals(tContributionId));
|
||||
expect(result.statut, equals(ContributionStatus.payee));
|
||||
verify(mockRepository.getCotisationById(tContributionId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return contribution with payment details', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getCotisationById(tContributionId))
|
||||
.thenAnswer((_) async => tContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tContributionId);
|
||||
|
||||
// Assert
|
||||
expect(result.montantPaye, equals(5000.0));
|
||||
expect(result.methodePaiement, equals(PaymentMethod.waveMoney));
|
||||
expect(result.numeroPaiement, isNotNull);
|
||||
});
|
||||
|
||||
test('should return unpaid contribution', () async {
|
||||
// Arrange
|
||||
final unpaidContribution = ContributionModel(
|
||||
id: 'cont456',
|
||||
membreId: 'membre2',
|
||||
montant: 10000.0,
|
||||
dateEcheance: DateTime(2025, 1, 31),
|
||||
annee: 2025,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.enRetard,
|
||||
);
|
||||
when(mockRepository.getCotisationById('cont456'))
|
||||
.thenAnswer((_) async => unpaidContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase('cont456');
|
||||
|
||||
// Assert
|
||||
expect(result.statut, equals(ContributionStatus.enRetard));
|
||||
expect(result.montantPaye, isNull);
|
||||
expect(result.datePaiement, isNull);
|
||||
});
|
||||
|
||||
test('should throw exception when contribution not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getCotisationById(any))
|
||||
.thenThrow(Exception('Contribution non trouvée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/// Tests unitaires pour GetContributionHistory use case
|
||||
library get_contribution_history_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_history.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/repositories/contribution_repository.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'get_contribution_history_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetContributionHistory useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = GetContributionHistory(mockRepository);
|
||||
});
|
||||
|
||||
group('GetContributionHistory Use Case', () {
|
||||
final tHistoryList = [
|
||||
ContributionModel(
|
||||
id: 'cont1',
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
montantPaye: 5000.0,
|
||||
dateEcheance: DateTime(2024, 1, 31),
|
||||
datePaiement: DateTime(2024, 1, 15),
|
||||
annee: 2024,
|
||||
mois: 1,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.payee,
|
||||
),
|
||||
ContributionModel(
|
||||
id: 'cont2',
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 2, 28),
|
||||
annee: 2024,
|
||||
mois: 2,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.enAttente,
|
||||
),
|
||||
];
|
||||
|
||||
final tPageResult = ContributionPageResult(
|
||||
contributions: tHistoryList,
|
||||
total: 2,
|
||||
totalPages: 1,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
|
||||
test('should return contribution history', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => tPageResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tPageResult));
|
||||
expect(result.contributions.length, equals(2));
|
||||
expect(result.contributions[0].statut, equals(ContributionStatus.payee));
|
||||
expect(result.contributions[1].statut, equals(ContributionStatus.enAttente));
|
||||
verify(mockRepository.getMesCotisations(page: 0, size: 50));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return history for specific year', () async {
|
||||
// Arrange
|
||||
final year2023List = [
|
||||
ContributionModel(
|
||||
id: 'cont3',
|
||||
membreId: 'membre1',
|
||||
montant: 60000.0,
|
||||
montantPaye: 60000.0,
|
||||
dateEcheance: DateTime(2023, 12, 31),
|
||||
datePaiement: DateTime(2023, 12, 15),
|
||||
annee: 2023,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.payee,
|
||||
),
|
||||
];
|
||||
final yearPageResult = ContributionPageResult(
|
||||
contributions: year2023List,
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
when(mockRepository.getMesCotisations(page: 0, size: 50))
|
||||
.thenAnswer((_) async => yearPageResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50, annee: 2023);
|
||||
|
||||
// Assert
|
||||
expect(result.contributions.length, equals(1));
|
||||
expect(result.contributions.first.annee, equals(2023));
|
||||
});
|
||||
|
||||
test('should return history filtered by status', () async {
|
||||
// Arrange
|
||||
final paidOnly = [tHistoryList[0]];
|
||||
final paidPageResult = ContributionPageResult(
|
||||
contributions: paidOnly,
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
when(mockRepository.getMesCotisations(page: 0, size: 50))
|
||||
.thenAnswer((_) async => paidPageResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
page: 0,
|
||||
size: 50,
|
||||
statut: ContributionStatus.payee,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result.contributions.length, equals(1));
|
||||
expect(result.contributions.first.statut, equals(ContributionStatus.payee));
|
||||
});
|
||||
|
||||
test('should return empty history when no contributions', () async {
|
||||
// Arrange
|
||||
final emptyResult = ContributionPageResult(
|
||||
contributions: [],
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result.contributions, isEmpty);
|
||||
expect(result.total, equals(0));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/// Tests unitaires pour GetContributionStats use case
|
||||
library get_contribution_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/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_stats.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'get_contribution_stats_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetContributionStats useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = GetContributionStats(mockRepository);
|
||||
});
|
||||
|
||||
group('GetContributionStats Use Case', () {
|
||||
final tStats = {
|
||||
'montantDu': 60000.0,
|
||||
'totalPayeAnnee': 45000.0,
|
||||
'cotisationsEnAttente': 3,
|
||||
'prochaineEcheance': '2024-12-31T00:00:00.000Z',
|
||||
'tauxPaiement': 75.0,
|
||||
'nombreCotisations': 12,
|
||||
'montantMoyenCotisation': 5000.0,
|
||||
};
|
||||
|
||||
test('should return contribution statistics', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisationsSynthese())
|
||||
.thenAnswer((_) async => tStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tStats));
|
||||
expect(result?['montantDu'], equals(60000.0));
|
||||
expect(result?['totalPayeAnnee'], equals(45000.0));
|
||||
expect(result?['tauxPaiement'], equals(75.0));
|
||||
verify(mockRepository.getMesCotisationsSynthese());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return stats with payment rate', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisationsSynthese())
|
||||
.thenAnswer((_) async => tStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result?['tauxPaiement'], equals(75.0));
|
||||
expect(result?['cotisationsEnAttente'], equals(3));
|
||||
});
|
||||
|
||||
test('should return stats with next deadline', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisationsSynthese())
|
||||
.thenAnswer((_) async => tStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result?['prochaineEcheance'], isNotNull);
|
||||
expect(result?['prochaineEcheance'], contains('2024-12-31'));
|
||||
});
|
||||
|
||||
test('should return null when no data available', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisationsSynthese())
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
verify(mockRepository.getMesCotisationsSynthese());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/// Tests unitaires pour GetContributions use case
|
||||
library get_contributions_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contributions.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/repositories/contribution_repository.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'get_contributions_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetContributions useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = GetContributions(mockRepository);
|
||||
});
|
||||
|
||||
group('GetContributions Use Case', () {
|
||||
final tContributionList = [
|
||||
ContributionModel(
|
||||
id: 'cont1',
|
||||
membreId: 'membre1',
|
||||
membreNom: 'Dupont',
|
||||
membrePrenom: 'Jean',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
annee: 2024,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.payee,
|
||||
),
|
||||
ContributionModel(
|
||||
id: 'cont2',
|
||||
membreId: 'membre1',
|
||||
membreNom: 'Dupont',
|
||||
membrePrenom: 'Jean',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2025, 1, 31),
|
||||
annee: 2025,
|
||||
type: ContributionType.mensuelle,
|
||||
statut: ContributionStatus.enAttente,
|
||||
),
|
||||
];
|
||||
|
||||
final tPageResult = ContributionPageResult(
|
||||
contributions: tContributionList,
|
||||
total: 2,
|
||||
totalPages: 1,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
|
||||
test('should return paginated list of contributions', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => tPageResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tPageResult));
|
||||
expect(result.contributions.length, equals(2));
|
||||
expect(result.total, equals(2));
|
||||
verify(mockRepository.getMesCotisations(page: 0, size: 50));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return contributions with custom page size', () async {
|
||||
// Arrange
|
||||
final smallPageResult = ContributionPageResult(
|
||||
contributions: [tContributionList[0]],
|
||||
total: 2,
|
||||
totalPages: 2,
|
||||
page: 0,
|
||||
size: 1,
|
||||
);
|
||||
when(mockRepository.getMesCotisations(page: 0, size: 1))
|
||||
.thenAnswer((_) async => smallPageResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 1);
|
||||
|
||||
// Assert
|
||||
expect(result.contributions.length, equals(1));
|
||||
expect(result.size, equals(1));
|
||||
verify(mockRepository.getMesCotisations(page: 0, size: 1));
|
||||
});
|
||||
|
||||
test('should return empty result when no contributions exist', () async {
|
||||
// Arrange
|
||||
final emptyResult = ContributionPageResult(
|
||||
contributions: [],
|
||||
total: 0,
|
||||
totalPages: 0,
|
||||
page: 0,
|
||||
size: 50,
|
||||
);
|
||||
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50);
|
||||
|
||||
// Assert
|
||||
expect(result.contributions, isEmpty);
|
||||
expect(result.total, equals(0));
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(page: 0, size: 50), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/// Tests unitaires pour PayContribution use case
|
||||
library pay_contribution_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/pay_contribution.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'pay_contribution_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late PayContribution useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = PayContribution(mockRepository);
|
||||
});
|
||||
|
||||
group('PayContribution Use Case', () {
|
||||
const tContributionId = 'cont123';
|
||||
const tMontant = 5000.0;
|
||||
final tDatePaiement = DateTime(2024, 11, 15);
|
||||
const tMethode = 'WAVE_MONEY';
|
||||
const tNumero = 'WAVE123456';
|
||||
|
||||
final tPaidContribution = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
montantPaye: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
datePaiement: tDatePaiement,
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.payee,
|
||||
methodePaiement: PaymentMethod.waveMoney,
|
||||
numeroPaiement: tNumero,
|
||||
);
|
||||
|
||||
test('should record payment successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.enregistrerPaiement(
|
||||
tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
numeroPaiement: tNumero,
|
||||
referencePaiement: anyNamed('referencePaiement'),
|
||||
)).thenAnswer((_) async => tPaidContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
cotisationId: tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
numeroPaiement: tNumero,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tPaidContribution));
|
||||
expect(result.statut, equals(ContributionStatus.payee));
|
||||
expect(result.montantPaye, equals(5000.0));
|
||||
expect(result.datePaiement, equals(tDatePaiement));
|
||||
verify(mockRepository.enregistrerPaiement(
|
||||
tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
numeroPaiement: tNumero,
|
||||
referencePaiement: null,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should record partial payment', () async {
|
||||
// Arrange
|
||||
const partialMontant = 2500.0;
|
||||
final partialPaid = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
montantPaye: 2500.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
datePaiement: tDatePaiement,
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.partielle,
|
||||
methodePaiement: PaymentMethod.especes,
|
||||
);
|
||||
when(mockRepository.enregistrerPaiement(
|
||||
tContributionId,
|
||||
montant: partialMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: 'ESPECES',
|
||||
numeroPaiement: null,
|
||||
referencePaiement: null,
|
||||
)).thenAnswer((_) async => partialPaid);
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
cotisationId: tContributionId,
|
||||
montant: partialMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: 'ESPECES',
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result.statut, equals(ContributionStatus.partielle));
|
||||
expect(result.montantPaye, equals(2500.0));
|
||||
});
|
||||
|
||||
test('should record payment with reference', () async {
|
||||
// Arrange
|
||||
const reference = 'REF-2024-001';
|
||||
when(mockRepository.enregistrerPaiement(
|
||||
tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
numeroPaiement: null,
|
||||
referencePaiement: reference,
|
||||
)).thenAnswer((_) async => tPaidContribution.copyWith(referencePaiement: reference));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
cotisationId: tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
referencePaiement: reference,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result.referencePaiement, equals(reference));
|
||||
});
|
||||
|
||||
test('should throw exception when payment fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.enregistrerPaiement(
|
||||
tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
numeroPaiement: null,
|
||||
referencePaiement: null,
|
||||
)).thenThrow(Exception('Erreur lors de l\'enregistrement du paiement'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(
|
||||
cotisationId: tContributionId,
|
||||
montant: tMontant,
|
||||
datePaiement: tDatePaiement,
|
||||
methodePaiement: tMethode,
|
||||
),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/// Tests unitaires pour UpdateContribution use case
|
||||
library update_contribution_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/update_contribution.dart';
|
||||
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
|
||||
|
||||
@GenerateMocks([IContributionRepository])
|
||||
import 'update_contribution_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateContribution useCase;
|
||||
late MockIContributionRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIContributionRepository();
|
||||
useCase = UpdateContribution(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateContribution Use Case', () {
|
||||
const tContributionId = 'cont123';
|
||||
final tUpdatedContribution = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
montant: 6000.0,
|
||||
dateEcheance: DateTime(2025, 12, 31),
|
||||
annee: 2025,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
description: 'Montant mis à jour',
|
||||
);
|
||||
|
||||
test('should update contribution successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateCotisation(tContributionId, tUpdatedContribution))
|
||||
.thenAnswer((_) async => tUpdatedContribution);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tContributionId, tUpdatedContribution);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedContribution));
|
||||
expect(result.montant, equals(6000.0));
|
||||
expect(result.description, equals('Montant mis à jour'));
|
||||
verify(mockRepository.updateCotisation(tContributionId, tUpdatedContribution));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update contribution status', () async {
|
||||
// Arrange
|
||||
final statusUpdate = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 12, 31),
|
||||
annee: 2024,
|
||||
type: ContributionType.annuelle,
|
||||
statut: ContributionStatus.enRetard,
|
||||
);
|
||||
when(mockRepository.updateCotisation(tContributionId, statusUpdate))
|
||||
.thenAnswer((_) async => statusUpdate);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tContributionId, statusUpdate);
|
||||
|
||||
// Assert
|
||||
expect(result.statut, equals(ContributionStatus.enRetard));
|
||||
});
|
||||
|
||||
test('should update contribution type', () async {
|
||||
// Arrange
|
||||
final typeUpdate = ContributionModel(
|
||||
id: tContributionId,
|
||||
membreId: 'membre1',
|
||||
montant: 5000.0,
|
||||
dateEcheance: DateTime(2024, 3, 31),
|
||||
annee: 2024,
|
||||
trimestre: 1,
|
||||
type: ContributionType.trimestrielle,
|
||||
statut: ContributionStatus.nonPayee,
|
||||
);
|
||||
when(mockRepository.updateCotisation(tContributionId, typeUpdate))
|
||||
.thenAnswer((_) async => typeUpdate);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tContributionId, typeUpdate);
|
||||
|
||||
// Assert
|
||||
expect(result.type, equals(ContributionType.trimestrielle));
|
||||
expect(result.trimestre, equals(1));
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateCotisation(any, any))
|
||||
.thenThrow(Exception('Mise à jour échouée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tContributionId, tUpdatedContribution), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/// Tests unitaires pour GetCompteAdherent use case
|
||||
library get_compte_adherent_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/usecases/get_compte_adherent.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/repositories/dashboard_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/entities/compte_adherent_entity.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
import 'package:unionflow_mobile_apps/core/usecases/usecase.dart';
|
||||
|
||||
@GenerateMocks([DashboardRepository])
|
||||
import 'get_compte_adherent_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetCompteAdherent useCase;
|
||||
late MockDashboardRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockDashboardRepository();
|
||||
useCase = GetCompteAdherent(mockRepository);
|
||||
});
|
||||
|
||||
group('GetCompteAdherent Use Case', () {
|
||||
final tCompteAdherent = CompteAdherentEntity(
|
||||
numeroMembre: 'M-2024-001',
|
||||
nomComplet: 'Amadou Diallo',
|
||||
organisationNom: 'Association Alpha',
|
||||
dateAdhesion: DateTime(2024, 1, 15),
|
||||
statutCompte: 'ACTIF',
|
||||
soldeCotisations: 50000.0,
|
||||
soldeEpargne: 125000.0,
|
||||
soldeBloque: 15000.0,
|
||||
soldeTotalDisponible: 160000.0,
|
||||
encoursCreditTotal: 75000.0,
|
||||
capaciteEmprunt: 200000.0,
|
||||
nombreCotisationsPayees: 12,
|
||||
nombreCotisationsTotal: 12,
|
||||
nombreCotisationsEnRetard: 0,
|
||||
engagementRate: 1.0,
|
||||
nombreComptesEpargne: 2,
|
||||
dateCalcul: DateTime(2024, 12, 15),
|
||||
);
|
||||
|
||||
test('should return compte adherent successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getCompteAdherent())
|
||||
.thenAnswer((_) async => Right(tCompteAdherent));
|
||||
|
||||
// Act
|
||||
final result = await useCase(NoParams());
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tCompteAdherent));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(compte) {
|
||||
expect(compte.numeroMembre, equals('M-2024-001'));
|
||||
expect(compte.nomComplet, equals('Amadou Diallo'));
|
||||
expect(compte.soldeTotalDisponible, equals(160000.0));
|
||||
expect(compte.capaciteEmprunt, equals(200000.0));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getCompteAdherent());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return compte with multiple epargne accounts', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getCompteAdherent())
|
||||
.thenAnswer((_) async => Right(tCompteAdherent));
|
||||
|
||||
// Act
|
||||
final result = await useCase(NoParams());
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(compte) {
|
||||
expect(compte.nombreComptesEpargne, equals(2));
|
||||
expect(compte.soldeEpargne, equals(125000.0));
|
||||
expect(compte.engagementRate, equals(1.0));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return compte with overdue contributions', () async {
|
||||
// Arrange
|
||||
final compteWithOverdue = CompteAdherentEntity(
|
||||
numeroMembre: 'M-2024-002',
|
||||
nomComplet: 'Fatou Ndiaye',
|
||||
statutCompte: 'ACTIF',
|
||||
soldeCotisations: 25000.0,
|
||||
soldeEpargne: 50000.0,
|
||||
soldeBloque: 0.0,
|
||||
soldeTotalDisponible: 75000.0,
|
||||
encoursCreditTotal: 0.0,
|
||||
capaciteEmprunt: 100000.0,
|
||||
nombreCotisationsPayees: 8,
|
||||
nombreCotisationsTotal: 12,
|
||||
nombreCotisationsEnRetard: 4,
|
||||
engagementRate: 0.67,
|
||||
nombreComptesEpargne: 1,
|
||||
dateCalcul: DateTime(2024, 12, 15),
|
||||
);
|
||||
when(mockRepository.getCompteAdherent())
|
||||
.thenAnswer((_) async => Right(compteWithOverdue));
|
||||
|
||||
// Act
|
||||
final result = await useCase(NoParams());
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(compte) {
|
||||
expect(compte.nombreCotisationsEnRetard, equals(4));
|
||||
expect(compte.engagementRate, lessThan(1.0));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getCompteAdherent())
|
||||
.thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(NoParams());
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(compte) => fail('Should not return compte'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/// Tests unitaires pour GetDashboardData use case
|
||||
library get_dashboard_data_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/usecases/get_dashboard_data.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/repositories/dashboard_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/dashboard/domain/entities/dashboard_entity.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([DashboardRepository])
|
||||
import 'get_dashboard_data_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetDashboardData useCase;
|
||||
late MockDashboardRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockDashboardRepository();
|
||||
useCase = GetDashboardData(mockRepository);
|
||||
});
|
||||
|
||||
group('GetDashboardData Use Case', () {
|
||||
const tOrgId = 'org-123';
|
||||
const tUserId = 'user-456';
|
||||
final tParams = GetDashboardDataParams(
|
||||
organizationId: tOrgId,
|
||||
userId: tUserId,
|
||||
);
|
||||
|
||||
final tDashboardStats = DashboardStatsEntity(
|
||||
totalMembers: 250,
|
||||
activeMembers: 180,
|
||||
totalEvents: 45,
|
||||
upcomingEvents: 12,
|
||||
totalContributions: 1200,
|
||||
totalContributionAmount: 5750000.0,
|
||||
contributionsAmountOnly: 3250000.0,
|
||||
pendingRequests: 8,
|
||||
completedProjects: 23,
|
||||
monthlyGrowth: 0.15,
|
||||
engagementRate: 0.72,
|
||||
lastUpdated: DateTime(2024, 12, 15, 10, 30),
|
||||
totalOrganizations: 5,
|
||||
organizationTypeDistribution: {
|
||||
'association': 3,
|
||||
'cooperative': 2,
|
||||
},
|
||||
);
|
||||
|
||||
test('should return dashboard stats successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getDashboardStats(tOrgId, tUserId))
|
||||
.thenAnswer((_) async => Right(tDashboardStats));
|
||||
|
||||
// Act
|
||||
final result = await GetDashboardStats(mockRepository)(
|
||||
GetDashboardStatsParams(organizationId: tOrgId, userId: tUserId),
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tDashboardStats));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(stats) {
|
||||
expect(stats.totalMembers, equals(250));
|
||||
expect(stats.activeMembers, equals(180));
|
||||
expect(stats.totalContributionAmount, equals(5750000.0));
|
||||
expect(stats.monthlyGrowth, equals(0.15));
|
||||
expect(stats.hasGrowth, isTrue);
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getDashboardStats(tOrgId, tUserId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return stats with high engagement rate', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getDashboardStats(tOrgId, tUserId))
|
||||
.thenAnswer((_) async => Right(tDashboardStats));
|
||||
|
||||
// Act
|
||||
final result = await GetDashboardStats(mockRepository)(
|
||||
GetDashboardStatsParams(organizationId: tOrgId, userId: tUserId),
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(stats) {
|
||||
expect(stats.engagementRate, equals(0.72));
|
||||
expect(stats.isHighEngagement, isTrue);
|
||||
expect(stats.memberActivityRate, closeTo(0.72, 0.01));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should format contribution amount correctly', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getDashboardStats(tOrgId, tUserId))
|
||||
.thenAnswer((_) async => Right(tDashboardStats));
|
||||
|
||||
// Act
|
||||
final result = await GetDashboardStats(mockRepository)(
|
||||
GetDashboardStatsParams(organizationId: tOrgId, userId: tUserId),
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(stats) {
|
||||
expect(stats.formattedContributionAmount, equals('5.8M'));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getDashboardStats(any, any))
|
||||
.thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await GetDashboardStats(mockRepository)(
|
||||
GetDashboardStatsParams(organizationId: tOrgId, userId: tUserId),
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(stats) => fail('Should not return stats'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/// Tests unitaires pour CancelRegistration use case
|
||||
library cancel_registration_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/cancel_registration.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'cancel_registration_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CancelRegistration useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = CancelRegistration(mockRepository);
|
||||
});
|
||||
|
||||
group('CancelRegistration Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
|
||||
test('should cancel registration successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.desinscrireEvenement(tEventId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.desinscrireEvenement(tEventId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when event not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.desinscrireEvenement(any))
|
||||
.thenThrow(Exception('Événement non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when not registered', () async {
|
||||
// Arrange
|
||||
when(mockRepository.desinscrireEvenement(any))
|
||||
.thenThrow(Exception('Vous n\'êtes pas inscrit à cet événement'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when cancellation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.desinscrireEvenement(any))
|
||||
.thenThrow(Exception('Erreur de désinscription'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/// Tests unitaires pour CreateEvent use case
|
||||
library create_event_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/create_event.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'create_event_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CreateEvent useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = CreateEvent(mockRepository);
|
||||
});
|
||||
|
||||
group('CreateEvent Use Case', () {
|
||||
final tNewEvent = EvenementModel(
|
||||
titre: 'Nouvelle Réunion',
|
||||
description: 'Réunion mensuelle du comité',
|
||||
dateDebut: DateTime(2025, 1, 15, 10, 0),
|
||||
dateFin: DateTime(2025, 1, 15, 12, 0),
|
||||
lieu: 'Salle de réunion',
|
||||
type: TypeEvenement.reunion,
|
||||
statut: StatutEvenement.planifie,
|
||||
);
|
||||
|
||||
final tCreatedEvent = EvenementModel(
|
||||
id: 456,
|
||||
titre: 'Nouvelle Réunion',
|
||||
description: 'Réunion mensuelle du comité',
|
||||
dateDebut: DateTime(2025, 1, 15, 10, 0),
|
||||
dateFin: DateTime(2025, 1, 15, 12, 0),
|
||||
lieu: 'Salle de réunion',
|
||||
type: TypeEvenement.reunion,
|
||||
statut: StatutEvenement.planifie,
|
||||
);
|
||||
|
||||
test('should create event successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createEvenement(tNewEvent))
|
||||
.thenAnswer((_) async => tCreatedEvent);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tNewEvent);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCreatedEvent));
|
||||
expect(result.id, isNotNull);
|
||||
expect(result.id, equals(456));
|
||||
expect(result.titre, equals('Nouvelle Réunion'));
|
||||
verify(mockRepository.createEvenement(tNewEvent));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should create public event with registration', () async {
|
||||
// Arrange
|
||||
final publicEvent = EvenementModel(
|
||||
titre: 'Conférence Publique',
|
||||
dateDebut: DateTime(2025, 2, 1, 14, 0),
|
||||
dateFin: DateTime(2025, 2, 1, 17, 0),
|
||||
type: TypeEvenement.conference,
|
||||
statut: StatutEvenement.planifie,
|
||||
estPublic: true,
|
||||
inscriptionRequise: true,
|
||||
maxParticipants: 200,
|
||||
);
|
||||
final createdPublic = publicEvent.copyWith(id: 789);
|
||||
when(mockRepository.createEvenement(publicEvent))
|
||||
.thenAnswer((_) async => createdPublic);
|
||||
|
||||
// Act
|
||||
final result = await useCase(publicEvent);
|
||||
|
||||
// Assert
|
||||
expect(result.estPublic, isTrue);
|
||||
expect(result.inscriptionRequise, isTrue);
|
||||
expect(result.maxParticipants, equals(200));
|
||||
});
|
||||
|
||||
test('should create event with cost', () async {
|
||||
// Arrange
|
||||
final paidEvent = EvenementModel(
|
||||
titre: 'Séminaire Payant',
|
||||
dateDebut: DateTime(2025, 3, 1, 9, 0),
|
||||
dateFin: DateTime(2025, 3, 1, 18, 0),
|
||||
type: TypeEvenement.seminaire,
|
||||
statut: StatutEvenement.planifie,
|
||||
cout: 50000.0,
|
||||
devise: 'XOF',
|
||||
);
|
||||
when(mockRepository.createEvenement(any))
|
||||
.thenAnswer((_) async => paidEvent.copyWith(id: 999));
|
||||
|
||||
// Act
|
||||
final result = await useCase(paidEvent);
|
||||
|
||||
// Assert
|
||||
expect(result.cout, equals(50000.0));
|
||||
expect(result.devise, equals('XOF'));
|
||||
});
|
||||
|
||||
test('should throw exception when creation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createEvenement(any))
|
||||
.thenThrow(Exception('Validation error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tNewEvent), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/// Tests unitaires pour DeleteEvent use case
|
||||
library delete_event_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/delete_event.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'delete_event_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late DeleteEvent useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = DeleteEvent(mockRepository);
|
||||
});
|
||||
|
||||
group('DeleteEvent Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
|
||||
test('should delete event successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteEvenement(tEventId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteEvenement(tEventId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when event not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteEvenement(any))
|
||||
.thenThrow(Exception('Événement non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsA(isA<Exception>()));
|
||||
verify(mockRepository.deleteEvenement('nonexistent'));
|
||||
});
|
||||
|
||||
test('should throw exception when event has participants', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteEvenement(any))
|
||||
.thenThrow(Exception('Impossible de supprimer un événement avec des participants'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when deletion fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteEvenement(any))
|
||||
.thenThrow(Exception('Erreur de suppression'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/// Tests unitaires pour GetEventById use case
|
||||
library get_event_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_event_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'get_event_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetEventById useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = GetEventById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetEventById Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
final tEvent = EvenementModel(
|
||||
id: 123,
|
||||
titre: 'Assemblée Générale 2024',
|
||||
description: 'Assemblée générale annuelle de l\'organisation',
|
||||
dateDebut: DateTime(2024, 12, 15, 14, 0),
|
||||
dateFin: DateTime(2024, 12, 15, 18, 0),
|
||||
lieu: 'Salle des Congrès',
|
||||
adresse: '123 Rue de la République',
|
||||
ville: 'Dakar',
|
||||
type: TypeEvenement.assembleeGenerale,
|
||||
statut: StatutEvenement.confirme,
|
||||
priorite: PrioriteEvenement.haute,
|
||||
participantsActuels: 45,
|
||||
maxParticipants: 100,
|
||||
estPublic: true,
|
||||
inscriptionRequise: true,
|
||||
);
|
||||
|
||||
test('should return event by id', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementById(tEventId))
|
||||
.thenAnswer((_) async => tEvent);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tEvent));
|
||||
expect(result?.id, equals(123));
|
||||
expect(result?.titre, equals('Assemblée Générale 2024'));
|
||||
verify(mockRepository.getEvenementById(tEventId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return event with all details populated', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementById(tEventId))
|
||||
.thenAnswer((_) async => tEvent);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
expect(result?.lieu, isNotNull);
|
||||
expect(result?.adresse, isNotNull);
|
||||
expect(result?.participantsActuels, equals(45));
|
||||
expect(result?.maxParticipants, equals(100));
|
||||
});
|
||||
|
||||
test('should return null when event not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementById(any))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
// Act
|
||||
final result = await useCase('nonexistent');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementById(any))
|
||||
.thenThrow(Exception('Database error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour GetEventParticipants use case
|
||||
library get_event_participants_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_event_participants.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'get_event_participants_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetEventParticipants useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = GetEventParticipants(mockRepository);
|
||||
});
|
||||
|
||||
group('GetEventParticipants Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
final tParticipantsList = [
|
||||
{
|
||||
'id': 'membre1',
|
||||
'nom': 'Dupont',
|
||||
'prenom': 'Jean',
|
||||
'email': 'jean.dupont@example.com',
|
||||
'dateInscription': '2024-11-01T10:00:00Z',
|
||||
'statut': 'CONFIRME',
|
||||
},
|
||||
{
|
||||
'id': 'membre2',
|
||||
'nom': 'Martin',
|
||||
'prenom': 'Marie',
|
||||
'email': 'marie.martin@example.com',
|
||||
'dateInscription': '2024-11-02T14:00:00Z',
|
||||
'statut': 'EN_ATTENTE',
|
||||
},
|
||||
{
|
||||
'id': 'membre3',
|
||||
'nom': 'Diallo',
|
||||
'prenom': 'Amadou',
|
||||
'email': 'amadou.diallo@example.com',
|
||||
'dateInscription': '2024-11-03T09:00:00Z',
|
||||
'statut': 'CONFIRME',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of event participants', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getParticipants(tEventId))
|
||||
.thenAnswer((_) async => tParticipantsList);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tParticipantsList));
|
||||
expect(result.length, equals(3));
|
||||
expect(result[0]['nom'], equals('Dupont'));
|
||||
expect(result[0]['statut'], equals('CONFIRME'));
|
||||
verify(mockRepository.getParticipants(tEventId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return participants with different statuses', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getParticipants(tEventId))
|
||||
.thenAnswer((_) async => tParticipantsList);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
expect(result.where((p) => p['statut'] == 'CONFIRME').length, equals(2));
|
||||
expect(result.where((p) => p['statut'] == 'EN_ATTENTE').length, equals(1));
|
||||
});
|
||||
|
||||
test('should return empty list when no participants', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getParticipants(tEventId))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should throw exception when event not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getParticipants(any))
|
||||
.thenThrow(Exception('Événement non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/// Tests unitaires pour GetEvents use case
|
||||
library get_events_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_events.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/repositories/evenement_repository_impl.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'get_events_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetEvents useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = GetEvents(mockRepository);
|
||||
});
|
||||
|
||||
group('GetEvents Use Case', () {
|
||||
final tEventsList = [
|
||||
EvenementModel(
|
||||
id: 1,
|
||||
titre: 'Assemblée Générale 2024',
|
||||
description: 'Assemblée générale annuelle',
|
||||
dateDebut: DateTime(2024, 12, 15, 14, 0),
|
||||
dateFin: DateTime(2024, 12, 15, 18, 0),
|
||||
lieu: 'Salle des Congrès',
|
||||
type: TypeEvenement.assembleeGenerale,
|
||||
statut: StatutEvenement.confirme,
|
||||
priorite: PrioriteEvenement.haute,
|
||||
participantsActuels: 45,
|
||||
maxParticipants: 100,
|
||||
),
|
||||
EvenementModel(
|
||||
id: 2,
|
||||
titre: 'Formation Leadership',
|
||||
description: 'Formation sur le leadership',
|
||||
dateDebut: DateTime(2024, 12, 20, 9, 0),
|
||||
dateFin: DateTime(2024, 12, 20, 17, 0),
|
||||
lieu: 'Centre de Formation',
|
||||
type: TypeEvenement.formation,
|
||||
statut: StatutEvenement.planifie,
|
||||
participantsActuels: 15,
|
||||
maxParticipants: 30,
|
||||
),
|
||||
];
|
||||
|
||||
final tSearchResult = EvenementSearchResult(
|
||||
evenements: tEventsList,
|
||||
total: 2,
|
||||
page: 0,
|
||||
size: 20,
|
||||
totalPages: 1,
|
||||
);
|
||||
|
||||
test('should return paginated list of events', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenements(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tSearchResult));
|
||||
expect(result.evenements.length, equals(2));
|
||||
expect(result.total, equals(2));
|
||||
verify(mockRepository.getEvenements(page: 0, size: 20));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should filter events by search query', () async {
|
||||
// Arrange
|
||||
final filteredResult = EvenementSearchResult(
|
||||
evenements: [tEventsList[0]],
|
||||
total: 1,
|
||||
page: 0,
|
||||
size: 20,
|
||||
totalPages: 1,
|
||||
);
|
||||
when(mockRepository.getEvenements(
|
||||
page: 0,
|
||||
size: 20,
|
||||
recherche: 'Assemblée',
|
||||
)).thenAnswer((_) async => filteredResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20, recherche: 'Assemblée');
|
||||
|
||||
// Assert
|
||||
expect(result.evenements.length, equals(1));
|
||||
expect(result.evenements.first.titre, contains('Assemblée'));
|
||||
});
|
||||
|
||||
test('should return empty result when no events exist', () async {
|
||||
// Arrange
|
||||
final emptyResult = EvenementSearchResult(
|
||||
evenements: [],
|
||||
total: 0,
|
||||
page: 0,
|
||||
size: 20,
|
||||
totalPages: 0,
|
||||
);
|
||||
when(mockRepository.getEvenements(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result.evenements, isEmpty);
|
||||
expect(result.total, equals(0));
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenements(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(page: 0, size: 20), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/// Tests unitaires pour GetMyRegistrations use case
|
||||
library get_my_registrations_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/get_my_registrations.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/repositories/evenement_repository_impl.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'get_my_registrations_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetMyRegistrations useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = GetMyRegistrations(mockRepository);
|
||||
});
|
||||
|
||||
group('GetMyRegistrations Use Case', () {
|
||||
final tRegisteredEvents = [
|
||||
EvenementModel(
|
||||
id: 1,
|
||||
titre: 'Formation Leadership',
|
||||
dateDebut: DateTime(2024, 12, 20, 9, 0),
|
||||
dateFin: DateTime(2024, 12, 20, 17, 0),
|
||||
type: TypeEvenement.formation,
|
||||
statut: StatutEvenement.confirme,
|
||||
inscriptionRequise: true,
|
||||
participantsActuels: 15,
|
||||
),
|
||||
EvenementModel(
|
||||
id: 2,
|
||||
titre: 'Séminaire Annuel',
|
||||
dateDebut: DateTime(2025, 1, 10, 14, 0),
|
||||
dateFin: DateTime(2025, 1, 10, 18, 0),
|
||||
type: TypeEvenement.seminaire,
|
||||
statut: StatutEvenement.planifie,
|
||||
inscriptionRequise: true,
|
||||
participantsActuels: 30,
|
||||
),
|
||||
];
|
||||
|
||||
final tSearchResult = EvenementSearchResult(
|
||||
evenements: tRegisteredEvents,
|
||||
total: 2,
|
||||
page: 0,
|
||||
size: 20,
|
||||
totalPages: 1,
|
||||
);
|
||||
|
||||
test('should return list of registered events', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementsAVenir(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tSearchResult));
|
||||
expect(result.evenements.length, equals(2));
|
||||
expect(result.total, equals(2));
|
||||
verify(mockRepository.getEvenementsAVenir(page: 0, size: 20));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return events with custom page size', () async {
|
||||
// Arrange
|
||||
final smallResult = EvenementSearchResult(
|
||||
evenements: [tRegisteredEvents[0]],
|
||||
total: 2,
|
||||
page: 0,
|
||||
size: 1,
|
||||
totalPages: 2,
|
||||
);
|
||||
when(mockRepository.getEvenementsAVenir(page: 0, size: 1))
|
||||
.thenAnswer((_) async => smallResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 1);
|
||||
|
||||
// Assert
|
||||
expect(result.evenements.length, equals(1));
|
||||
expect(result.size, equals(1));
|
||||
});
|
||||
|
||||
test('should return empty result when no registrations', () async {
|
||||
// Arrange
|
||||
final emptyResult = EvenementSearchResult(
|
||||
evenements: [],
|
||||
total: 0,
|
||||
page: 0,
|
||||
size: 20,
|
||||
totalPages: 0,
|
||||
);
|
||||
when(mockRepository.getEvenementsAVenir(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result.evenements, isEmpty);
|
||||
expect(result.total, equals(0));
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getEvenementsAVenir(page: anyNamed('page'), size: anyNamed('size')))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(page: 0, size: 20), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/// Tests unitaires pour RegisterForEvent use case
|
||||
library register_for_event_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/register_for_event.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'register_for_event_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late RegisterForEvent useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = RegisterForEvent(mockRepository);
|
||||
});
|
||||
|
||||
group('RegisterForEvent Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
|
||||
test('should register for event successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.inscrireEvenement(tEventId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tEventId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.inscrireEvenement(tEventId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when event not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.inscrireEvenement(any))
|
||||
.thenThrow(Exception('Événement non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('nonexistent'), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when already registered', () async {
|
||||
// Arrange
|
||||
when(mockRepository.inscrireEvenement(any))
|
||||
.thenThrow(Exception('Vous êtes déjà inscrit à cet événement'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when event is full', () async {
|
||||
// Arrange
|
||||
when(mockRepository.inscrireEvenement(any))
|
||||
.thenThrow(Exception('Événement complet'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/// Tests unitaires pour SubmitEventFeedback use case
|
||||
library submit_event_feedback_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/submit_event_feedback.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'submit_event_feedback_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late SubmitEventFeedback useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = SubmitEventFeedback(mockRepository);
|
||||
});
|
||||
|
||||
group('SubmitEventFeedback Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
const tNote = 5;
|
||||
const tCommentaire = 'Excellent événement, très enrichissant!';
|
||||
|
||||
test('should throw UnimplementedError as endpoint not available', () async {
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(evenementId: tEventId, note: tNote),
|
||||
throwsA(isA<UnimplementedError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw UnimplementedError with feedback message', () async {
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(
|
||||
evenementId: tEventId,
|
||||
note: tNote,
|
||||
commentaire: tCommentaire,
|
||||
),
|
||||
throwsA(isA<UnimplementedError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw UnimplementedError for minimum rating', () async {
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(evenementId: tEventId, note: 1),
|
||||
throwsA(isA<UnimplementedError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw UnimplementedError for maximum rating', () async {
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(
|
||||
evenementId: tEventId,
|
||||
note: 5,
|
||||
commentaire: 'Parfait!',
|
||||
),
|
||||
throwsA(isA<UnimplementedError>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/// Tests unitaires pour UpdateEvent use case
|
||||
library update_event_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/repositories/evenement_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/domain/usecases/update_event.dart';
|
||||
import 'package:unionflow_mobile_apps/features/events/data/models/evenement_model.dart';
|
||||
|
||||
@GenerateMocks([IEvenementRepository])
|
||||
import 'update_event_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateEvent useCase;
|
||||
late MockIEvenementRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIEvenementRepository();
|
||||
useCase = UpdateEvent(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateEvent Use Case', () {
|
||||
const tEventId = 'event123';
|
||||
final tUpdatedEvent = EvenementModel(
|
||||
id: 123,
|
||||
titre: 'Assemblée Générale 2024 - Modifiée',
|
||||
description: 'Assemblée générale annuelle (mise à jour)',
|
||||
dateDebut: DateTime(2024, 12, 16, 14, 0),
|
||||
dateFin: DateTime(2024, 12, 16, 18, 0),
|
||||
lieu: 'Nouveau lieu',
|
||||
type: TypeEvenement.assembleeGenerale,
|
||||
statut: StatutEvenement.confirme,
|
||||
);
|
||||
|
||||
test('should update event successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateEvenement(tEventId, tUpdatedEvent))
|
||||
.thenAnswer((_) async => tUpdatedEvent);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId, tUpdatedEvent);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedEvent));
|
||||
expect(result.titre, contains('Modifiée'));
|
||||
expect(result.lieu, equals('Nouveau lieu'));
|
||||
verify(mockRepository.updateEvenement(tEventId, tUpdatedEvent));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update event status', () async {
|
||||
// Arrange
|
||||
final statusUpdate = EvenementModel(
|
||||
id: 123,
|
||||
titre: 'Événement',
|
||||
dateDebut: DateTime(2024, 12, 15, 14, 0),
|
||||
dateFin: DateTime(2024, 12, 15, 18, 0),
|
||||
type: TypeEvenement.reunion,
|
||||
statut: StatutEvenement.annule,
|
||||
);
|
||||
when(mockRepository.updateEvenement(tEventId, statusUpdate))
|
||||
.thenAnswer((_) async => statusUpdate);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId, statusUpdate);
|
||||
|
||||
// Assert
|
||||
expect(result.statut, equals(StatutEvenement.annule));
|
||||
});
|
||||
|
||||
test('should update event capacity', () async {
|
||||
// Arrange
|
||||
final capacityUpdate = EvenementModel(
|
||||
id: 123,
|
||||
titre: 'Événement',
|
||||
dateDebut: DateTime(2024, 12, 15, 14, 0),
|
||||
dateFin: DateTime(2024, 12, 15, 18, 0),
|
||||
type: TypeEvenement.formation,
|
||||
statut: StatutEvenement.planifie,
|
||||
maxParticipants: 50,
|
||||
participantsActuels: 25,
|
||||
);
|
||||
when(mockRepository.updateEvenement(tEventId, capacityUpdate))
|
||||
.thenAnswer((_) async => capacityUpdate);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tEventId, capacityUpdate);
|
||||
|
||||
// Assert
|
||||
expect(result.maxParticipants, equals(50));
|
||||
expect(result.participantsActuels, equals(25));
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateEvenement(any, any))
|
||||
.thenThrow(Exception('Update failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tEventId, tUpdatedEvent), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/// Tests unitaires pour ApproveTransaction use case
|
||||
library approve_transaction_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/approve_transaction.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/transaction_approval.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'approve_transaction_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ApproveTransaction useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = ApproveTransaction(mockRepository);
|
||||
});
|
||||
|
||||
group('ApproveTransaction Use Case', () {
|
||||
const tApprovalId = 'approval-123';
|
||||
const tComment = 'Approuvé - Montant conforme au budget';
|
||||
final tApprovedTransaction = TransactionApproval(
|
||||
id: tApprovalId,
|
||||
transactionId: 'tx-123',
|
||||
transactionType: TransactionType.withdrawal,
|
||||
amount: 500000.0,
|
||||
currency: 'XOF',
|
||||
requesterId: 'user-1',
|
||||
requesterName: 'Amadou Diallo',
|
||||
requiredLevel: ApprovalLevel.level1,
|
||||
status: ApprovalStatus.approved,
|
||||
approvers: [],
|
||||
createdAt: DateTime(2024, 12, 15),
|
||||
);
|
||||
|
||||
test('should approve transaction successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.approveTransaction(
|
||||
approvalId: tApprovalId,
|
||||
comment: tComment,
|
||||
)).thenAnswer((_) async => Right(tApprovedTransaction));
|
||||
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId, comment: tComment);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tApprovedTransaction));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) {
|
||||
expect(approval.id, equals(tApprovalId));
|
||||
expect(approval.status, equals(ApprovalStatus.approved));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.approveTransaction(
|
||||
approvalId: tApprovalId,
|
||||
comment: tComment,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should approve transaction without comment', () async {
|
||||
// Arrange
|
||||
when(mockRepository.approveTransaction(
|
||||
approvalId: tApprovalId,
|
||||
comment: null,
|
||||
)).thenAnswer((_) async => Right(tApprovedTransaction));
|
||||
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) => expect(approval.status, equals(ApprovalStatus.approved)),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when approvalId is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(approvalId: '');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
expect((failure as ValidationFailure).message, contains('ID approbation requis'));
|
||||
},
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Transaction déjà approuvée');
|
||||
when(mockRepository.approveTransaction(
|
||||
approvalId: anyNamed('approvalId'),
|
||||
comment: anyNamed('comment'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/// Tests unitaires pour CreateBudget use case
|
||||
library create_budget_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/create_budget.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/budget.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'create_budget_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CreateBudget useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = CreateBudget(mockRepository);
|
||||
});
|
||||
|
||||
group('CreateBudget Use Case', () {
|
||||
const tName = 'Budget 2025';
|
||||
const tOrgId = 'org-123';
|
||||
final tBudgetLines = [
|
||||
BudgetLine(
|
||||
id: 'line-1',
|
||||
category: BudgetCategory.contributions,
|
||||
name: 'Cotisations mensuelles',
|
||||
description: 'Revenus des cotisations',
|
||||
amountPlanned: 3000000.0,
|
||||
),
|
||||
BudgetLine(
|
||||
id: 'line-2',
|
||||
category: BudgetCategory.savings,
|
||||
name: 'Dépôts épargne',
|
||||
description: 'Collecte épargne',
|
||||
amountPlanned: 2000000.0,
|
||||
),
|
||||
BudgetLine(
|
||||
id: 'line-3',
|
||||
category: BudgetCategory.solidarity,
|
||||
name: 'Aide mutuelle',
|
||||
description: 'Soutien membres',
|
||||
amountPlanned: 1000000.0,
|
||||
),
|
||||
];
|
||||
final tCreatedBudget = Budget(
|
||||
id: 'budget-new',
|
||||
name: tName,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
status: BudgetStatus.draft,
|
||||
lines: tBudgetLines,
|
||||
totalPlanned: 6000000.0,
|
||||
totalRealized: 0.0,
|
||||
currency: 'XOF',
|
||||
createdBy: 'user-1',
|
||||
createdAt: DateTime.now(),
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
endDate: DateTime(2025, 12, 31),
|
||||
);
|
||||
|
||||
test('should create budget successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createBudget(
|
||||
name: tName,
|
||||
description: anyNamed('description'),
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
month: anyNamed('month'),
|
||||
lines: tBudgetLines,
|
||||
)).thenAnswer((_) async => Right(tCreatedBudget));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
name: tName,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
lines: tBudgetLines,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tCreatedBudget));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budget) {
|
||||
expect(budget.id, equals('budget-new'));
|
||||
expect(budget.name, equals(tName));
|
||||
expect(budget.status, equals(BudgetStatus.draft));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.createBudget(
|
||||
name: tName,
|
||||
description: null,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
month: null,
|
||||
lines: tBudgetLines,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should create monthly budget with description', () async {
|
||||
// Arrange
|
||||
const description = 'Budget opérationnel janvier 2025';
|
||||
final monthlyLines = [
|
||||
BudgetLine(
|
||||
id: 'line-monthly',
|
||||
category: BudgetCategory.contributions,
|
||||
name: 'Cotisations janvier',
|
||||
amountPlanned: 500000.0,
|
||||
),
|
||||
];
|
||||
final monthlyBudget = Budget(
|
||||
id: 'budget-monthly',
|
||||
name: 'Budget Janvier 2025',
|
||||
description: description,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.monthly,
|
||||
year: 2025,
|
||||
month: 1,
|
||||
status: BudgetStatus.draft,
|
||||
lines: monthlyLines,
|
||||
totalPlanned: 500000.0,
|
||||
totalRealized: 0.0,
|
||||
currency: 'XOF',
|
||||
createdBy: 'user-1',
|
||||
createdAt: DateTime.now(),
|
||||
startDate: DateTime(2025, 1, 1),
|
||||
endDate: DateTime(2025, 1, 31),
|
||||
);
|
||||
when(mockRepository.createBudget(
|
||||
name: 'Budget Janvier 2025',
|
||||
description: description,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.monthly,
|
||||
year: 2025,
|
||||
month: 1,
|
||||
lines: monthlyLines,
|
||||
)).thenAnswer((_) async => Right(monthlyBudget));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
name: 'Budget Janvier 2025',
|
||||
description: description,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.monthly,
|
||||
year: 2025,
|
||||
month: 1,
|
||||
lines: monthlyLines,
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budget) {
|
||||
expect(budget.period, equals(BudgetPeriod.monthly));
|
||||
expect(budget.month, equals(1));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when name is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(
|
||||
name: '',
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
lines: [BudgetLine(id: 'test-1', category: BudgetCategory.operational, name: 'Test line', amountPlanned: 100.0)],
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
},
|
||||
(budget) => fail('Should not return budget'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur création budget');
|
||||
when(mockRepository.createBudget(
|
||||
name: anyNamed('name'),
|
||||
description: anyNamed('description'),
|
||||
organizationId: anyNamed('organizationId'),
|
||||
period: anyNamed('period'),
|
||||
year: anyNamed('year'),
|
||||
month: anyNamed('month'),
|
||||
lines: anyNamed('lines'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
name: tName,
|
||||
organizationId: tOrgId,
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2025,
|
||||
lines: [BudgetLine(id: 'test-1', category: BudgetCategory.operational, name: 'Test line', amountPlanned: 100.0)],
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(budget) => fail('Should not return budget'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/// Tests unitaires pour GetApprovalById use case
|
||||
library get_approval_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/get_approval_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/transaction_approval.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'get_approval_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetApprovalById useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = GetApprovalById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetApprovalById Use Case', () {
|
||||
const tApprovalId = 'approval-123';
|
||||
final tApproval = TransactionApproval(
|
||||
id: tApprovalId,
|
||||
transactionId: 'tx-456',
|
||||
transactionType: TransactionType.solidarity,
|
||||
amount: 350000.0,
|
||||
currency: 'XOF',
|
||||
requesterId: 'user-1',
|
||||
requesterName: 'Amadou Diallo',
|
||||
organizationId: 'org-123',
|
||||
requiredLevel: ApprovalLevel.level2,
|
||||
status: ApprovalStatus.pending,
|
||||
approvers: [],
|
||||
createdAt: DateTime(2024, 12, 15),
|
||||
);
|
||||
|
||||
test('should return approval details by ID', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getApprovalById(tApprovalId))
|
||||
.thenAnswer((_) async => Right(tApproval));
|
||||
|
||||
// Act
|
||||
final result = await useCase(tApprovalId);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tApproval));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) {
|
||||
expect(approval.id, equals(tApprovalId));
|
||||
expect(approval.amount, equals(350000.0));
|
||||
expect(approval.transactionType, equals(TransactionType.solidarity));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getApprovalById(tApprovalId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return approval with level 2 requirement', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getApprovalById(tApprovalId))
|
||||
.thenAnswer((_) async => Right(tApproval));
|
||||
|
||||
// Act
|
||||
final result = await useCase(tApprovalId);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) {
|
||||
expect(approval.requiredLevel, equals(ApprovalLevel.level2));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when approvalId is empty', () async {
|
||||
// Act
|
||||
final result = await useCase('');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
},
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when approval not found', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Approbation non trouvée');
|
||||
when(mockRepository.getApprovalById(any))
|
||||
.thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase('nonexistent');
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/// Tests unitaires pour GetBudgetById use case
|
||||
library get_budget_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/get_budget_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/budget.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'get_budget_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetBudgetById useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = GetBudgetById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetBudgetById Use Case', () {
|
||||
const tBudgetId = 'budget-123';
|
||||
final tBudget = Budget(
|
||||
id: tBudgetId,
|
||||
name: 'Budget Annuel 2024',
|
||||
description: 'Budget prévisionnel pour l\'année 2024',
|
||||
organizationId: 'org-123',
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2024,
|
||||
status: BudgetStatus.active,
|
||||
lines: [],
|
||||
totalPlanned: 5000000.0,
|
||||
totalRealized: 3250000.0,
|
||||
currency: 'XOF',
|
||||
createdBy: 'user-1',
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
startDate: DateTime(2024, 1, 1),
|
||||
endDate: DateTime(2024, 12, 31),
|
||||
);
|
||||
|
||||
test('should return budget details by ID', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgetById(tBudgetId))
|
||||
.thenAnswer((_) async => Right(tBudget));
|
||||
|
||||
// Act
|
||||
final result = await useCase(tBudgetId);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tBudget));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budget) {
|
||||
expect(budget.id, equals(tBudgetId));
|
||||
expect(budget.name, equals('Budget Annuel 2024'));
|
||||
expect(budget.totalPlanned, equals(5000000.0));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getBudgetById(tBudgetId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return budget with realized amount', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgetById(tBudgetId))
|
||||
.thenAnswer((_) async => Right(tBudget));
|
||||
|
||||
// Act
|
||||
final result = await useCase(tBudgetId);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budget) {
|
||||
expect(budget.totalRealized, equals(3250000.0));
|
||||
expect(budget.totalRealized, lessThan(budget.totalPlanned));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when budgetId is empty', () async {
|
||||
// Act
|
||||
final result = await useCase('');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
},
|
||||
(budget) => fail('Should not return budget'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when budget not found', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Budget non trouvé');
|
||||
when(mockRepository.getBudgetById(any))
|
||||
.thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase('nonexistent');
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(budget) => fail('Should not return budget'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/// Tests unitaires pour GetBudgetTracking use case
|
||||
library get_budget_tracking_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/get_budget_tracking.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'get_budget_tracking_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetBudgetTracking useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = GetBudgetTracking(mockRepository);
|
||||
});
|
||||
|
||||
group('GetBudgetTracking Use Case', () {
|
||||
const tBudgetId = 'budget-123';
|
||||
final tTrackingData = {
|
||||
'budgetId': tBudgetId,
|
||||
'totalPlanned': 5000000.0,
|
||||
'totalRealized': 3250000.0,
|
||||
'remainingAmount': 1750000.0,
|
||||
'realizationRate': 0.65,
|
||||
'categories': {
|
||||
'contributions': {'planned': 2000000.0, 'realized': 1800000.0, 'rate': 0.9},
|
||||
'savings': {'planned': 1500000.0, 'realized': 950000.0, 'rate': 0.63},
|
||||
'solidarity': {'planned': 1000000.0, 'realized': 350000.0, 'rate': 0.35},
|
||||
'events': {'planned': 500000.0, 'realized': 150000.0, 'rate': 0.3},
|
||||
},
|
||||
};
|
||||
|
||||
test('should return budget tracking data successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgetTracking(budgetId: tBudgetId))
|
||||
.thenAnswer((_) async => Right(tTrackingData));
|
||||
|
||||
// Act
|
||||
final result = await useCase(budgetId: tBudgetId);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tTrackingData));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(tracking) {
|
||||
expect(tracking['budgetId'], equals(tBudgetId));
|
||||
expect(tracking['totalPlanned'], equals(5000000.0));
|
||||
expect(tracking['realizationRate'], equals(0.65));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getBudgetTracking(budgetId: tBudgetId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return tracking with category breakdown', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgetTracking(budgetId: tBudgetId))
|
||||
.thenAnswer((_) async => Right(tTrackingData));
|
||||
|
||||
// Act
|
||||
final result = await useCase(budgetId: tBudgetId);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(tracking) {
|
||||
final categories = tracking['categories'] as Map<String, dynamic>;
|
||||
expect(categories.keys, contains('contributions'));
|
||||
expect(categories.keys, contains('solidarity'));
|
||||
final contribs = categories['contributions'] as Map<String, dynamic>;
|
||||
expect(contribs['rate'], equals(0.9));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when budgetId is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(budgetId: '');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
},
|
||||
(tracking) => fail('Should not return tracking'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur suivi budget');
|
||||
when(mockRepository.getBudgetTracking(budgetId: anyNamed('budgetId')))
|
||||
.thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase(budgetId: tBudgetId);
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(tracking) => fail('Should not return tracking'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/// Tests unitaires pour GetBudgets use case
|
||||
library get_budgets_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/get_budgets.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/budget.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'get_budgets_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetBudgets useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = GetBudgets(mockRepository);
|
||||
});
|
||||
|
||||
group('GetBudgets Use Case', () {
|
||||
final tBudgets = [
|
||||
Budget(
|
||||
id: 'budget-1',
|
||||
name: 'Budget Annuel 2024',
|
||||
organizationId: 'org-123',
|
||||
period: BudgetPeriod.annual,
|
||||
year: 2024,
|
||||
status: BudgetStatus.active,
|
||||
lines: [],
|
||||
totalPlanned: 5000000.0,
|
||||
totalRealized: 3250000.0,
|
||||
currency: 'XOF',
|
||||
createdBy: 'user-1',
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
startDate: DateTime(2024, 1, 1),
|
||||
endDate: DateTime(2024, 12, 31),
|
||||
),
|
||||
Budget(
|
||||
id: 'budget-2',
|
||||
name: 'Budget Q4 2024',
|
||||
organizationId: 'org-123',
|
||||
period: BudgetPeriod.quarterly,
|
||||
year: 2024,
|
||||
month: 10,
|
||||
status: BudgetStatus.active,
|
||||
lines: [],
|
||||
totalPlanned: 1250000.0,
|
||||
totalRealized: 850000.0,
|
||||
currency: 'XOF',
|
||||
createdBy: 'user-1',
|
||||
createdAt: DateTime(2024, 10, 1),
|
||||
startDate: DateTime(2024, 10, 1),
|
||||
endDate: DateTime(2024, 12, 31),
|
||||
),
|
||||
];
|
||||
|
||||
test('should return list of budgets successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgets(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
status: anyNamed('status'),
|
||||
year: anyNamed('year'),
|
||||
)).thenAnswer((_) async => Right(tBudgets));
|
||||
|
||||
// Act
|
||||
final result = await useCase(organizationId: 'org-123');
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tBudgets));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budgets) {
|
||||
expect(budgets.length, equals(2));
|
||||
expect(budgets[0].name, equals('Budget Annuel 2024'));
|
||||
expect(budgets[0].totalPlanned, equals(5000000.0));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getBudgets(
|
||||
organizationId: 'org-123',
|
||||
status: null,
|
||||
year: null,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should filter budgets by status and year', () async {
|
||||
// Arrange
|
||||
final activeBudgets = [tBudgets[0], tBudgets[1]];
|
||||
when(mockRepository.getBudgets(
|
||||
organizationId: 'org-123',
|
||||
status: BudgetStatus.active,
|
||||
year: 2024,
|
||||
)).thenAnswer((_) async => Right(activeBudgets));
|
||||
|
||||
// Act
|
||||
final result = await useCase(
|
||||
organizationId: 'org-123',
|
||||
status: BudgetStatus.active,
|
||||
year: 2024,
|
||||
);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budgets) {
|
||||
expect(budgets.every((b) => b.status == BudgetStatus.active), isTrue);
|
||||
expect(budgets.every((b) => b.year == 2024), isTrue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty list when no budgets exist', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getBudgets(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
status: anyNamed('status'),
|
||||
year: anyNamed('year'),
|
||||
)).thenAnswer((_) async => Right([]));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(budgets) => expect(budgets, isEmpty),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getBudgets(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
status: anyNamed('status'),
|
||||
year: anyNamed('year'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(budgets) => fail('Should not return budgets'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/// Tests unitaires pour GetPendingApprovals use case
|
||||
library get_pending_approvals_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/get_pending_approvals.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/transaction_approval.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'get_pending_approvals_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetPendingApprovals useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = GetPendingApprovals(mockRepository);
|
||||
});
|
||||
|
||||
group('GetPendingApprovals Use Case', () {
|
||||
final tApprovals = [
|
||||
TransactionApproval(
|
||||
id: 'approval-1',
|
||||
transactionId: 'tx-123',
|
||||
transactionType: TransactionType.withdrawal,
|
||||
amount: 500000.0,
|
||||
currency: 'XOF',
|
||||
requesterId: 'user-1',
|
||||
requesterName: 'Amadou Diallo',
|
||||
organizationId: 'org-123',
|
||||
requiredLevel: ApprovalLevel.level2,
|
||||
status: ApprovalStatus.pending,
|
||||
approvers: [],
|
||||
createdAt: DateTime(2024, 12, 15),
|
||||
),
|
||||
TransactionApproval(
|
||||
id: 'approval-2',
|
||||
transactionId: 'tx-456',
|
||||
transactionType: TransactionType.solidarity,
|
||||
amount: 200000.0,
|
||||
currency: 'XOF',
|
||||
requesterId: 'user-2',
|
||||
requesterName: 'Fatou Ndiaye',
|
||||
requiredLevel: ApprovalLevel.level1,
|
||||
status: ApprovalStatus.pending,
|
||||
approvers: [],
|
||||
createdAt: DateTime(2024, 12, 14),
|
||||
),
|
||||
];
|
||||
|
||||
test('should return list of pending approvals successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getPendingApprovals(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
)).thenAnswer((_) async => Right(tApprovals));
|
||||
|
||||
// Act
|
||||
final result = await useCase(organizationId: 'org-123');
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tApprovals));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approvals) {
|
||||
expect(approvals.length, equals(2));
|
||||
expect(approvals[0].status, equals(ApprovalStatus.pending));
|
||||
expect(approvals[0].amount, equals(500000.0));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.getPendingApprovals(organizationId: 'org-123'));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return approvals with different levels', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getPendingApprovals(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
)).thenAnswer((_) async => Right(tApprovals));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approvals) {
|
||||
expect(approvals.any((a) => a.requiredLevel == ApprovalLevel.level2), isTrue);
|
||||
expect(approvals.any((a) => a.requiredLevel == ApprovalLevel.level1), isTrue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('should return empty list when no pending approvals', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getPendingApprovals(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
)).thenAnswer((_) async => Right([]));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approvals) => expect(approvals, isEmpty),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ServerFailure when repository fails', () async {
|
||||
// Arrange
|
||||
final tFailure = ServerFailure('Erreur serveur');
|
||||
when(mockRepository.getPendingApprovals(
|
||||
organizationId: anyNamed('organizationId'),
|
||||
)).thenAnswer((_) async => Left(tFailure));
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, Left(tFailure));
|
||||
result.fold(
|
||||
(failure) => expect(failure, isA<ServerFailure>()),
|
||||
(approvals) => fail('Should not return approvals'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/// Tests unitaires pour RejectTransaction use case
|
||||
library reject_transaction_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/usecases/reject_transaction.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/repositories/finance_workflow_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/finance_workflow/domain/entities/transaction_approval.dart';
|
||||
import 'package:unionflow_mobile_apps/core/error/failures.dart';
|
||||
|
||||
@GenerateMocks([FinanceWorkflowRepository])
|
||||
import 'reject_transaction_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late RejectTransaction useCase;
|
||||
late MockFinanceWorkflowRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockFinanceWorkflowRepository();
|
||||
useCase = RejectTransaction(mockRepository);
|
||||
});
|
||||
|
||||
group('RejectTransaction Use Case', () {
|
||||
const tApprovalId = 'approval-123';
|
||||
const tReason = 'Montant trop élevé - Budget insuffisant';
|
||||
final tRejectedTransaction = TransactionApproval(
|
||||
id: tApprovalId,
|
||||
transactionId: 'tx-123',
|
||||
transactionType: TransactionType.withdrawal,
|
||||
amount: 500000.0,
|
||||
currency: 'XOF',
|
||||
requesterId: 'user-1',
|
||||
requesterName: 'Amadou Diallo',
|
||||
requiredLevel: ApprovalLevel.level2,
|
||||
status: ApprovalStatus.rejected,
|
||||
approvers: [],
|
||||
createdAt: DateTime(2024, 12, 15),
|
||||
);
|
||||
|
||||
test('should reject transaction successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.rejectTransaction(
|
||||
approvalId: tApprovalId,
|
||||
reason: tReason,
|
||||
)).thenAnswer((_) async => Right(tRejectedTransaction));
|
||||
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId, reason: tReason);
|
||||
|
||||
// Assert
|
||||
expect(result, Right(tRejectedTransaction));
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) {
|
||||
expect(approval.id, equals(tApprovalId));
|
||||
expect(approval.status, equals(ApprovalStatus.rejected));
|
||||
},
|
||||
);
|
||||
verify(mockRepository.rejectTransaction(
|
||||
approvalId: tApprovalId,
|
||||
reason: tReason,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should reject transaction with detailed reason', () async {
|
||||
// Arrange
|
||||
const detailedReason = 'Refus: Documentation incomplète + montant non justifié';
|
||||
when(mockRepository.rejectTransaction(
|
||||
approvalId: tApprovalId,
|
||||
reason: detailedReason,
|
||||
)).thenAnswer((_) async => Right(tRejectedTransaction));
|
||||
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId, reason: detailedReason);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) => fail('Should not return failure'),
|
||||
(approval) => expect(approval.status, equals(ApprovalStatus.rejected)),
|
||||
);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when approvalId is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(approvalId: '', reason: tReason);
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
expect((failure as ValidationFailure).message, contains('ID approbation requis'));
|
||||
},
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return ValidationFailure when reason is empty', () async {
|
||||
// Act
|
||||
final result = await useCase(approvalId: tApprovalId, reason: ' ');
|
||||
|
||||
// Assert
|
||||
result.fold(
|
||||
(failure) {
|
||||
expect(failure, isA<ValidationFailure>());
|
||||
expect((failure as ValidationFailure).message, contains('Raison du rejet requise'));
|
||||
},
|
||||
(approval) => fail('Should not return approval'),
|
||||
);
|
||||
verifyZeroInteractions(mockRepository);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/// Tests unitaires pour CreateMember use case
|
||||
library create_member_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/create_member.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'create_member_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CreateMember useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = CreateMember(mockRepository);
|
||||
});
|
||||
|
||||
group('CreateMember Use Case', () {
|
||||
final tMemberData = MembreCompletModel(
|
||||
nom: 'Ndiaye',
|
||||
prenom: 'Fatou',
|
||||
email: 'fatou.ndiaye@example.com',
|
||||
telephone: '+221776543210',
|
||||
genre: Genre.femme,
|
||||
statut: StatutMembre.enAttente,
|
||||
dateNaissance: DateTime(1990, 7, 22),
|
||||
adresse: '45 Rue de la République, Dakar',
|
||||
);
|
||||
|
||||
final tCreatedMember = MembreCompletModel(
|
||||
id: '456',
|
||||
nom: 'Ndiaye',
|
||||
prenom: 'Fatou',
|
||||
email: 'fatou.ndiaye@example.com',
|
||||
telephone: '+221776543210',
|
||||
genre: Genre.femme,
|
||||
statut: StatutMembre.enAttente,
|
||||
dateNaissance: DateTime(1990, 7, 22),
|
||||
adresse: '45 Rue de la République, Dakar',
|
||||
);
|
||||
|
||||
test('should create new member successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createMembre(tMemberData))
|
||||
.thenAnswer((_) async => tCreatedMember);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMemberData);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCreatedMember));
|
||||
expect(result.id, equals('456'));
|
||||
expect(result.nom, equals('Ndiaye'));
|
||||
expect(result.email, equals('fatou.ndiaye@example.com'));
|
||||
verify(mockRepository.createMembre(tMemberData));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should create member with minimal required fields', () async {
|
||||
// Arrange
|
||||
final minimalMember = MembreCompletModel(
|
||||
nom: 'Ba',
|
||||
prenom: 'Moussa',
|
||||
email: 'moussa.ba@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.enAttente,
|
||||
);
|
||||
final createdMinimal = MembreCompletModel(
|
||||
id: '789',
|
||||
nom: 'Ba',
|
||||
prenom: 'Moussa',
|
||||
email: 'moussa.ba@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.enAttente,
|
||||
);
|
||||
when(mockRepository.createMembre(minimalMember))
|
||||
.thenAnswer((_) async => createdMinimal);
|
||||
|
||||
// Act
|
||||
final result = await useCase(minimalMember);
|
||||
|
||||
// Assert
|
||||
expect(result.id, equals('789'));
|
||||
expect(result.nom, equals('Ba'));
|
||||
});
|
||||
|
||||
test('should throw exception when email already exists', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createMembre(any))
|
||||
.thenThrow(Exception('Email déjà utilisé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tMemberData), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when validation fails', () async {
|
||||
// Arrange
|
||||
final invalidMember = MembreCompletModel(
|
||||
nom: '',
|
||||
prenom: 'Test',
|
||||
email: 'invalid-email',
|
||||
genre: Genre.autre,
|
||||
statut: StatutMembre.enAttente,
|
||||
);
|
||||
when(mockRepository.createMembre(any))
|
||||
.thenThrow(Exception('Données invalides'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(invalidMember), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/// Tests unitaires pour DeleteMember use case
|
||||
library delete_member_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/delete_member.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'delete_member_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late DeleteMember useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = DeleteMember(mockRepository);
|
||||
});
|
||||
|
||||
group('DeleteMember Use Case', () {
|
||||
const tMemberId = '123';
|
||||
|
||||
test('should delete member successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteMembre(tMemberId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tMemberId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteMembre(tMemberId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should delete member with confirmation', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteMembre(tMemberId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tMemberId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteMembre(tMemberId)).called(1);
|
||||
});
|
||||
|
||||
test('should throw exception when member not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteMembre(any))
|
||||
.thenThrow(Exception('Membre non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('999'), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when member has dependencies', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteMembre(any))
|
||||
.thenThrow(Exception('Impossible de supprimer: membre a des contributions actives'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tMemberId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/// Tests unitaires pour ExportMembers use case
|
||||
library export_members_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/export_members.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_result.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_criteria.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'export_members_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ExportMembers useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = ExportMembers(mockRepository);
|
||||
});
|
||||
|
||||
group('ExportMembers Use Case', () {
|
||||
const tFormat = 'csv';
|
||||
final tCriteria = MembreSearchCriteria(
|
||||
statut: 'ACTIF',
|
||||
);
|
||||
|
||||
final tMembers = [
|
||||
MembreCompletModel(
|
||||
id: '1',
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
MembreCompletModel(
|
||||
id: '2',
|
||||
nom: 'Ndiaye',
|
||||
prenom: 'Fatou',
|
||||
email: 'fatou@example.com',
|
||||
genre: Genre.femme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
];
|
||||
|
||||
final tSearchResult = MembreSearchResult(
|
||||
membres: tMembers,
|
||||
totalElements: 2,
|
||||
totalPages: 1,
|
||||
currentPage: 0,
|
||||
pageSize: 10000,
|
||||
numberOfElements: 2,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: tCriteria,
|
||||
executionTimeMs: 50,
|
||||
);
|
||||
|
||||
test('should export members to CSV format successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: tCriteria, format: tFormat);
|
||||
|
||||
// Assert
|
||||
expect(result, isA<List<Map<String, dynamic>>>());
|
||||
expect(result.length, equals(2));
|
||||
expect(result[0]['nom'], equals('Diallo'));
|
||||
expect(result[0]['email'], equals('amadou@example.com'));
|
||||
verify(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should export members to PDF format', () async {
|
||||
// Arrange
|
||||
const pdfFormat = 'pdf';
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: tCriteria, format: pdfFormat);
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(2));
|
||||
expect(result[1]['prenom'], equals('Fatou'));
|
||||
});
|
||||
|
||||
test('should export all members when no criteria provided', () async {
|
||||
// Arrange
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: null, format: tFormat);
|
||||
|
||||
// Assert
|
||||
expect(result, isNotNull);
|
||||
expect(result.length, equals(2));
|
||||
});
|
||||
|
||||
test('should throw exception when export fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
)).thenThrow(Exception('Échec de l\'export'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(criteria: tCriteria, format: tFormat), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/// Tests unitaires pour GetMemberById use case
|
||||
library get_member_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/get_member_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'get_member_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetMemberById useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = GetMemberById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetMemberById Use Case', () {
|
||||
const tMemberId = '123';
|
||||
final tMember = MembreCompletModel(
|
||||
id: tMemberId,
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo@example.com',
|
||||
telephone: '+221771234567',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
dateNaissance: DateTime(1985, 3, 15),
|
||||
adresse: '12 Avenue Bourguiba, Dakar',
|
||||
profession: 'Ingénieur Informatique',
|
||||
);
|
||||
|
||||
test('should return member details by ID', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembreById(tMemberId))
|
||||
.thenAnswer((_) async => tMember);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMemberId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tMember));
|
||||
expect(result!.id, equals(tMemberId));
|
||||
expect(result.nom, equals('Diallo'));
|
||||
expect(result.prenom, equals('Amadou'));
|
||||
expect(result.email, equals('amadou.diallo@example.com'));
|
||||
verify(mockRepository.getMembreById(tMemberId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return member with all optional fields populated', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembreById(tMemberId))
|
||||
.thenAnswer((_) async => tMember);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMemberId);
|
||||
|
||||
// Assert
|
||||
expect(result!.telephone, equals('+221771234567'));
|
||||
expect(result.dateNaissance, isNotNull);
|
||||
expect(result.adresse, equals('12 Avenue Bourguiba, Dakar'));
|
||||
expect(result.profession, equals('Ingénieur Informatique'));
|
||||
});
|
||||
|
||||
test('should return null when member not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembreById(any))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
// Act
|
||||
final result = await useCase('999');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembreById(any))
|
||||
.thenThrow(Exception('Membre non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tMemberId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/// Tests unitaires pour GetMemberStats use case
|
||||
library get_member_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/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/get_member_stats.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'get_member_stats_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetMemberStats useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = GetMemberStats(mockRepository);
|
||||
});
|
||||
|
||||
group('GetMemberStats Use Case', () {
|
||||
final tMemberStats = {
|
||||
'totalMembres': 250,
|
||||
'membresActifs': 180,
|
||||
'membresInactifs': 50,
|
||||
'membresSuspendus': 15,
|
||||
'membresEnAttente': 5,
|
||||
'repartitionGenre': {
|
||||
'hommes': 130,
|
||||
'femmes': 115,
|
||||
'autre': 5,
|
||||
},
|
||||
'nouveauxMembresMois': 12,
|
||||
'tauxActivation': 0.72,
|
||||
'agesMoyens': {
|
||||
'global': 42.5,
|
||||
'hommes': 44.2,
|
||||
'femmes': 40.8,
|
||||
},
|
||||
};
|
||||
|
||||
test('should return comprehensive member statistics', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembresStats())
|
||||
.thenAnswer((_) async => tMemberStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tMemberStats));
|
||||
expect(result['totalMembres'], equals(250));
|
||||
expect(result['membresActifs'], equals(180));
|
||||
expect(result['tauxActivation'], equals(0.72));
|
||||
verify(mockRepository.getMembresStats());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return gender distribution statistics', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembresStats())
|
||||
.thenAnswer((_) async => tMemberStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
final repartition = result['repartitionGenre'] as Map<String, dynamic>;
|
||||
expect(repartition['hommes'], equals(130));
|
||||
expect(repartition['femmes'], equals(115));
|
||||
expect(repartition['autre'], equals(5));
|
||||
});
|
||||
|
||||
test('should return empty stats when no members exist', () async {
|
||||
// Arrange
|
||||
final emptyStats = {
|
||||
'totalMembres': 0,
|
||||
'membresActifs': 0,
|
||||
'membresInactifs': 0,
|
||||
'membresSuspendus': 0,
|
||||
'membresEnAttente': 0,
|
||||
'repartitionGenre': {
|
||||
'hommes': 0,
|
||||
'femmes': 0,
|
||||
'autre': 0,
|
||||
},
|
||||
'nouveauxMembresMois': 0,
|
||||
'tauxActivation': 0.0,
|
||||
};
|
||||
when(mockRepository.getMembresStats())
|
||||
.thenAnswer((_) async => emptyStats);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result['totalMembres'], equals(0));
|
||||
expect(result['tauxActivation'], equals(0.0));
|
||||
});
|
||||
|
||||
test('should throw exception when stats retrieval fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembresStats())
|
||||
.thenThrow(Exception('Erreur serveur'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/// Tests unitaires pour GetMembers use case
|
||||
library get_members_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/get_members.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_result.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_criteria.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'get_members_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetMembers useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = GetMembers(mockRepository);
|
||||
});
|
||||
|
||||
group('GetMembers Use Case', () {
|
||||
final tMembersList = [
|
||||
MembreCompletModel(
|
||||
id: '1',
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
MembreCompletModel(
|
||||
id: '2',
|
||||
nom: 'Ndiaye',
|
||||
prenom: 'Fatou',
|
||||
email: 'fatou.ndiaye@example.com',
|
||||
genre: Genre.femme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
];
|
||||
|
||||
final tSearchResult = MembreSearchResult(
|
||||
membres: tMembersList,
|
||||
totalElements: 2,
|
||||
totalPages: 1,
|
||||
currentPage: 0,
|
||||
pageSize: 50,
|
||||
numberOfElements: 2,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: MembreSearchCriteria(),
|
||||
executionTimeMs: 45,
|
||||
);
|
||||
|
||||
test('should return paginated list of all members', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembres(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tSearchResult));
|
||||
expect(result.membres.length, equals(2));
|
||||
expect(result.totalElements, equals(2));
|
||||
expect(result.membres[0].nom, equals('Diallo'));
|
||||
verify(mockRepository.getMembres(
|
||||
page: 0,
|
||||
size: 50,
|
||||
recherche: null,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return members with custom page size', () async {
|
||||
// Arrange
|
||||
final smallResult = MembreSearchResult(
|
||||
membres: [tMembersList[0]],
|
||||
totalElements: 2,
|
||||
totalPages: 2,
|
||||
currentPage: 0,
|
||||
pageSize: 1,
|
||||
numberOfElements: 1,
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: false,
|
||||
criteria: MembreSearchCriteria(),
|
||||
executionTimeMs: 30,
|
||||
);
|
||||
when(mockRepository.getMembres(
|
||||
page: 0,
|
||||
size: 1,
|
||||
recherche: null,
|
||||
)).thenAnswer((_) async => smallResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 1);
|
||||
|
||||
// Assert
|
||||
expect(result.membres.length, equals(1));
|
||||
expect(result.pageSize, equals(1));
|
||||
expect(result.hasNext, isTrue);
|
||||
});
|
||||
|
||||
test('should return empty result when no members exist', () async {
|
||||
// Arrange
|
||||
final emptyResult = MembreSearchResult(
|
||||
membres: [],
|
||||
totalElements: 0,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
pageSize: 50,
|
||||
numberOfElements: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: MembreSearchCriteria(),
|
||||
executionTimeMs: 20,
|
||||
);
|
||||
when(mockRepository.getMembres(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 50);
|
||||
|
||||
// Assert
|
||||
expect(result.membres, isEmpty);
|
||||
expect(result.totalElements, equals(0));
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMembres(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenThrow(Exception('Erreur serveur'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(page: 0, size: 50), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/// Tests unitaires pour SearchMembers use case
|
||||
library search_members_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/search_members.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_result.dart';
|
||||
import 'package:unionflow_mobile_apps/shared/models/membre_search_criteria.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'search_members_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late SearchMembers useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = SearchMembers(mockRepository);
|
||||
});
|
||||
|
||||
group('SearchMembers Use Case', () {
|
||||
final tSearchCriteria = MembreSearchCriteria(
|
||||
query: 'Diallo',
|
||||
statut: 'ACTIF',
|
||||
);
|
||||
|
||||
final tMatchingMembers = [
|
||||
MembreCompletModel(
|
||||
id: '1',
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
MembreCompletModel(
|
||||
id: '2',
|
||||
nom: 'Diallo',
|
||||
prenom: 'Fatou',
|
||||
email: 'fatou.diallo@example.com',
|
||||
genre: Genre.femme,
|
||||
statut: StatutMembre.actif,
|
||||
),
|
||||
];
|
||||
|
||||
final tSearchResult = MembreSearchResult(
|
||||
membres: tMatchingMembers,
|
||||
totalElements: 2,
|
||||
totalPages: 1,
|
||||
currentPage: 0,
|
||||
pageSize: 20,
|
||||
numberOfElements: 2,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: tSearchCriteria,
|
||||
executionTimeMs: 65,
|
||||
);
|
||||
|
||||
test('should return search results matching criteria', () async {
|
||||
// Arrange
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: tSearchCriteria,
|
||||
page: 0,
|
||||
size: 20,
|
||||
)).thenAnswer((_) async => tSearchResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: tSearchCriteria, page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tSearchResult));
|
||||
expect(result.membres.length, equals(2));
|
||||
expect(result.membres.every((m) => m.nom == 'Diallo'), isTrue);
|
||||
expect(result.membres.every((m) => m.statut == StatutMembre.actif || m.statut == null), isTrue);
|
||||
verify(mockRepository.searchMembres(
|
||||
criteria: tSearchCriteria,
|
||||
page: 0,
|
||||
size: 20,
|
||||
));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should search members by prenom', () async {
|
||||
// Arrange
|
||||
final prenomCriteria = MembreSearchCriteria(
|
||||
prenom: 'Fatou',
|
||||
);
|
||||
final fatouMembers = [tMatchingMembers[1]];
|
||||
final prenomResult = MembreSearchResult(
|
||||
membres: fatouMembers,
|
||||
totalElements: 1,
|
||||
totalPages: 1,
|
||||
currentPage: 0,
|
||||
pageSize: 20,
|
||||
numberOfElements: 1,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: prenomCriteria,
|
||||
executionTimeMs: 40,
|
||||
);
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: prenomCriteria,
|
||||
page: 0,
|
||||
size: 20,
|
||||
)).thenAnswer((_) async => prenomResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: prenomCriteria, page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result.membres.length, equals(1));
|
||||
expect(result.membres[0].prenom, equals('Fatou'));
|
||||
});
|
||||
|
||||
test('should return empty result when no matches found', () async {
|
||||
// Arrange
|
||||
final noMatchCriteria = MembreSearchCriteria(
|
||||
query: 'NonExistant',
|
||||
);
|
||||
final emptyResult = MembreSearchResult(
|
||||
membres: [],
|
||||
totalElements: 0,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
pageSize: 20,
|
||||
numberOfElements: 0,
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
isFirst: true,
|
||||
isLast: true,
|
||||
criteria: noMatchCriteria,
|
||||
executionTimeMs: 25,
|
||||
);
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: noMatchCriteria,
|
||||
page: 0,
|
||||
size: 20,
|
||||
)).thenAnswer((_) async => emptyResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(criteria: noMatchCriteria, page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result.membres, isEmpty);
|
||||
expect(result.totalElements, equals(0));
|
||||
});
|
||||
|
||||
test('should throw exception when search fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.searchMembres(
|
||||
criteria: anyNamed('criteria'),
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
)).thenThrow(Exception('Erreur de recherche'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(criteria: tSearchCriteria, page: 0, size: 20), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour UpdateMember use case
|
||||
library update_member_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/repositories/membre_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/domain/usecases/update_member.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IMembreRepository])
|
||||
import 'update_member_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateMember useCase;
|
||||
late MockIMembreRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIMembreRepository();
|
||||
useCase = UpdateMember(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateMember Use Case', () {
|
||||
const tMemberId = '123';
|
||||
final tUpdatedData = MembreCompletModel(
|
||||
id: tMemberId,
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo.updated@example.com',
|
||||
telephone: '+221771112222',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
profession: 'Directeur IT',
|
||||
);
|
||||
|
||||
final tUpdatedMember = MembreCompletModel(
|
||||
id: tMemberId,
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo.updated@example.com',
|
||||
telephone: '+221771112222',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.actif,
|
||||
profession: 'Directeur IT',
|
||||
);
|
||||
|
||||
test('should update member successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateMembre(tMemberId, tUpdatedData))
|
||||
.thenAnswer((_) async => tUpdatedMember);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMemberId, tUpdatedData);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedMember));
|
||||
expect(result.email, equals('amadou.diallo.updated@example.com'));
|
||||
expect(result.telephone, equals('+221771112222'));
|
||||
expect(result.profession, equals('Directeur IT'));
|
||||
verify(mockRepository.updateMembre(tMemberId, tUpdatedData));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update member status to suspended', () async {
|
||||
// Arrange
|
||||
final suspendedMember = MembreCompletModel(
|
||||
id: tMemberId,
|
||||
nom: 'Diallo',
|
||||
prenom: 'Amadou',
|
||||
email: 'amadou.diallo@example.com',
|
||||
genre: Genre.homme,
|
||||
statut: StatutMembre.suspendu,
|
||||
);
|
||||
when(mockRepository.updateMembre(tMemberId, suspendedMember))
|
||||
.thenAnswer((_) async => suspendedMember);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMemberId, suspendedMember);
|
||||
|
||||
// Assert
|
||||
expect(result.statut, equals(StatutMembre.suspendu));
|
||||
});
|
||||
|
||||
test('should throw exception when member not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateMembre(any, any))
|
||||
.thenThrow(Exception('Membre non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase('999', tUpdatedData), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when validation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateMembre(any, any))
|
||||
.thenThrow(Exception('Email invalide'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tMemberId, tUpdatedData), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/// Tests unitaires pour CreateOrganization use case
|
||||
library create_organization_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/create_organization.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/data/models/organization_model.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'create_organization_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late CreateOrganization useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = CreateOrganization(mockRepository);
|
||||
});
|
||||
|
||||
group('CreateOrganization Use Case', () {
|
||||
final tOrganization = OrganizationModel(
|
||||
nom: 'Nouvelle Organisation',
|
||||
nomCourt: 'NO',
|
||||
email: 'contact@nouvelle.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.enCreation,
|
||||
);
|
||||
|
||||
final tCreatedOrganization = OrganizationModel(
|
||||
id: 'org123',
|
||||
nom: 'Nouvelle Organisation',
|
||||
nomCourt: 'NO',
|
||||
email: 'contact@nouvelle.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
|
||||
test('should create organization successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createOrganization(tOrganization))
|
||||
.thenAnswer((_) async => tCreatedOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganization);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCreatedOrganization));
|
||||
expect(result.id, isNotNull);
|
||||
expect(result.nom, equals('Nouvelle Organisation'));
|
||||
verify(mockRepository.createOrganization(tOrganization));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should create organization with minimal required fields', () async {
|
||||
// Arrange
|
||||
final minimalOrg = OrganizationModel(
|
||||
nom: 'Minimal Org',
|
||||
nomCourt: 'MO',
|
||||
email: 'minimal@org.com',
|
||||
typeOrganisation: TypeOrganization.cooperative,
|
||||
statut: StatutOrganization.enCreation,
|
||||
);
|
||||
final createdMinimal = OrganizationModel(
|
||||
id: 'org456',
|
||||
nom: 'Minimal Org',
|
||||
nomCourt: 'MO',
|
||||
email: 'minimal@org.com',
|
||||
typeOrganisation: TypeOrganization.cooperative,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
when(mockRepository.createOrganization(minimalOrg))
|
||||
.thenAnswer((_) async => createdMinimal);
|
||||
|
||||
// Act
|
||||
final result = await useCase(minimalOrg);
|
||||
|
||||
// Assert
|
||||
expect(result.id, isNotNull);
|
||||
expect(result.nom, equals('Minimal Org'));
|
||||
});
|
||||
|
||||
test('should throw exception when email already exists', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createOrganization(any))
|
||||
.thenThrow(Exception('Email déjà utilisé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganization), throwsA(isA<Exception>()));
|
||||
});
|
||||
|
||||
test('should throw exception when validation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.createOrganization(any))
|
||||
.thenThrow(Exception('Données invalides'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganization), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/// Tests unitaires pour DeleteOrganization use case
|
||||
library delete_organization_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/delete_organization.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'delete_organization_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late DeleteOrganization useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = DeleteOrganization(mockRepository);
|
||||
});
|
||||
|
||||
group('DeleteOrganization Use Case', () {
|
||||
const tOrganizationId = 'org1';
|
||||
|
||||
test('should delete organization successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteOrganization(tOrganizationId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tOrganizationId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteOrganization(tOrganizationId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when organization not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteOrganization(any))
|
||||
.thenThrow(Exception('Organisation non trouvée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tOrganizationId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
verify(mockRepository.deleteOrganization(tOrganizationId));
|
||||
});
|
||||
|
||||
test('should throw exception when organization has members', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteOrganization(any))
|
||||
.thenThrow(Exception('Organisation contient des membres'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tOrganizationId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when deletion fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteOrganization(any))
|
||||
.thenThrow(Exception('Suppression échouée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganizationId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/// Tests unitaires pour GetOrganizationById use case
|
||||
library get_organization_by_id_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/get_organization_by_id.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/data/models/organization_model.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'get_organization_by_id_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetOrganizationById useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = GetOrganizationById(mockRepository);
|
||||
});
|
||||
|
||||
group('GetOrganizationById Use Case', () {
|
||||
const tOrganizationId = 'org1';
|
||||
final tOrganization = OrganizationModel(
|
||||
id: tOrganizationId,
|
||||
nom: 'Organisation Alpha',
|
||||
nomCourt: 'OA',
|
||||
email: 'contact@alpha.org',
|
||||
telephone: '+33123456789',
|
||||
adresse: '123 Rue de Paris',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
|
||||
test('should return organization by id', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationById(tOrganizationId))
|
||||
.thenAnswer((_) async => tOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tOrganization));
|
||||
expect(result?.id, equals(tOrganizationId));
|
||||
verify(mockRepository.getOrganizationById(tOrganizationId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return null when organization not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationById(any))
|
||||
.thenAnswer((_) async => null);
|
||||
|
||||
// Act
|
||||
final result = await useCase('nonexistent');
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
verify(mockRepository.getOrganizationById('nonexistent'));
|
||||
});
|
||||
|
||||
test('should return organization with all fields populated', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationById(tOrganizationId))
|
||||
.thenAnswer((_) async => tOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId);
|
||||
|
||||
// Assert
|
||||
expect(result?.nom, isNotNull);
|
||||
expect(result?.email, isNotNull);
|
||||
expect(result?.statut, equals(StatutOrganization.active));
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationById(any))
|
||||
.thenThrow(Exception('Database error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganizationId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/// Tests unitaires pour GetOrganizationMembers use case
|
||||
library get_organization_members_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/get_organization_members.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'get_organization_members_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetOrganizationMembers useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = GetOrganizationMembers(mockRepository);
|
||||
});
|
||||
|
||||
group('GetOrganizationMembers Use Case', () {
|
||||
const tOrganizationId = 'org1';
|
||||
final tMembersList = [
|
||||
{
|
||||
'id': 'membre1',
|
||||
'nom': 'Dupont',
|
||||
'prenom': 'Jean',
|
||||
'email': 'jean.dupont@example.com',
|
||||
},
|
||||
{
|
||||
'id': 'membre2',
|
||||
'nom': 'Martin',
|
||||
'prenom': 'Marie',
|
||||
'email': 'marie.martin@example.com',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of organization members', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationMembers(tOrganizationId))
|
||||
.thenAnswer((_) async => tMembersList);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tMembersList));
|
||||
expect(result.length, equals(2));
|
||||
verify(mockRepository.getOrganizationMembers(tOrganizationId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return empty list when organization has no members', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationMembers(tOrganizationId))
|
||||
.thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId);
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.getOrganizationMembers(tOrganizationId));
|
||||
});
|
||||
|
||||
test('should throw exception when organization not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationMembers(any))
|
||||
.thenThrow(Exception('Organisation non trouvée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tOrganizationId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when retrieval fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizationMembers(any))
|
||||
.thenThrow(Exception('Erreur de récupération'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganizationId), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/// Tests unitaires pour GetOrganizations use case
|
||||
library get_organizations_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/get_organizations.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/data/models/organization_model.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'get_organizations_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetOrganizations useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = GetOrganizations(mockRepository);
|
||||
});
|
||||
|
||||
group('GetOrganizations Use Case', () {
|
||||
final tOrganizationList = [
|
||||
OrganizationModel(
|
||||
id: 'org1',
|
||||
nom: 'Organisation Alpha',
|
||||
nomCourt: 'OA',
|
||||
email: 'contact@alpha.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
),
|
||||
OrganizationModel(
|
||||
id: 'org2',
|
||||
nom: 'Organisation Beta',
|
||||
nomCourt: 'OB',
|
||||
email: 'contact@beta.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
),
|
||||
];
|
||||
|
||||
test('should return list of organizations', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizations(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => tOrganizationList);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tOrganizationList));
|
||||
expect(result.length, equals(2));
|
||||
verify(mockRepository.getOrganizations(page: 0, size: 20));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should filter organizations by search query', () async {
|
||||
// Arrange
|
||||
final filteredList = [tOrganizationList[0]];
|
||||
when(mockRepository.getOrganizations(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: 'Alpha',
|
||||
)).thenAnswer((_) async => filteredList);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20, recherche: 'Alpha');
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(1));
|
||||
expect(result.first.nom, contains('Alpha'));
|
||||
verify(mockRepository.getOrganizations(page: 0, size: 20, recherche: 'Alpha'));
|
||||
});
|
||||
|
||||
test('should return empty list when no organizations exist', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizations(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase(page: 0, size: 20);
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getOrganizations(
|
||||
page: anyNamed('page'),
|
||||
size: anyNamed('size'),
|
||||
recherche: anyNamed('recherche'),
|
||||
)).thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(page: 0, size: 20), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/// Tests unitaires pour UpdateOrganizationConfig use case
|
||||
library update_organization_config_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/update_organization_config.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/data/models/organization_model.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'update_organization_config_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateOrganizationConfig useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = UpdateOrganizationConfig(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateOrganizationConfig Use Case', () {
|
||||
const tOrganizationId = 'org1';
|
||||
final tConfig = {
|
||||
'theme': 'dark',
|
||||
'language': 'fr',
|
||||
'notifications': true,
|
||||
'cotisationMensuelle': 5000.0,
|
||||
};
|
||||
|
||||
final tUpdatedOrganization = OrganizationModel(
|
||||
id: tOrganizationId,
|
||||
nom: 'Organisation Alpha',
|
||||
nomCourt: 'OA',
|
||||
email: 'contact@alpha.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
|
||||
test('should update organization configuration successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateOrganizationConfig(tOrganizationId, tConfig))
|
||||
.thenAnswer((_) async => tUpdatedOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId, tConfig);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedOrganization));
|
||||
verify(mockRepository.updateOrganizationConfig(tOrganizationId, tConfig));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update partial configuration', () async {
|
||||
// Arrange
|
||||
final partialConfig = {'theme': 'light'};
|
||||
when(mockRepository.updateOrganizationConfig(tOrganizationId, partialConfig))
|
||||
.thenAnswer((_) async => tUpdatedOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId, partialConfig);
|
||||
|
||||
// Assert
|
||||
expect(result, isNotNull);
|
||||
verify(mockRepository.updateOrganizationConfig(tOrganizationId, partialConfig));
|
||||
});
|
||||
|
||||
test('should handle empty configuration map', () async {
|
||||
// Arrange
|
||||
final emptyConfig = <String, dynamic>{};
|
||||
when(mockRepository.updateOrganizationConfig(tOrganizationId, emptyConfig))
|
||||
.thenAnswer((_) async => tUpdatedOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId, emptyConfig);
|
||||
|
||||
// Assert
|
||||
expect(result, isNotNull);
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateOrganizationConfig(any, any))
|
||||
.thenThrow(Exception('Configuration update failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganizationId, tConfig), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/// Tests unitaires pour UpdateOrganization use case
|
||||
library update_organization_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/repositories/organization_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/domain/usecases/update_organization.dart';
|
||||
import 'package:unionflow_mobile_apps/features/organizations/data/models/organization_model.dart';
|
||||
|
||||
@GenerateMocks([IOrganizationRepository])
|
||||
import 'update_organization_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateOrganization useCase;
|
||||
late MockIOrganizationRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIOrganizationRepository();
|
||||
useCase = UpdateOrganization(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateOrganization Use Case', () {
|
||||
const tOrganizationId = 'org1';
|
||||
final tOrganization = OrganizationModel(
|
||||
id: tOrganizationId,
|
||||
nom: 'Organisation Mise à Jour',
|
||||
nomCourt: 'OMA',
|
||||
email: 'updated@org.com',
|
||||
telephone: '+33987654321',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
|
||||
test('should update organization successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateOrganization(tOrganizationId, tOrganization))
|
||||
.thenAnswer((_) async => tOrganization);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId, tOrganization);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tOrganization));
|
||||
expect(result.nom, equals('Organisation Mise à Jour'));
|
||||
verify(mockRepository.updateOrganization(tOrganizationId, tOrganization));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update partial organization fields', () async {
|
||||
// Arrange
|
||||
final partialUpdate = OrganizationModel(
|
||||
id: tOrganizationId,
|
||||
nom: 'Nom Modifié',
|
||||
nomCourt: 'OA',
|
||||
email: 'contact@alpha.org',
|
||||
typeOrganisation: TypeOrganization.association,
|
||||
statut: StatutOrganization.active,
|
||||
);
|
||||
when(mockRepository.updateOrganization(tOrganizationId, partialUpdate))
|
||||
.thenAnswer((_) async => partialUpdate);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tOrganizationId, partialUpdate);
|
||||
|
||||
// Assert
|
||||
expect(result.nom, equals('Nom Modifié'));
|
||||
});
|
||||
|
||||
test('should throw exception when organization not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateOrganization(any, any))
|
||||
.thenThrow(Exception('Organisation non trouvée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tOrganizationId, tOrganization),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateOrganization(any, any))
|
||||
.thenThrow(Exception('Mise à jour échouée'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tOrganizationId, tOrganization), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/// Tests unitaires pour DeleteAccount use case
|
||||
library delete_account_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/delete_account.dart';
|
||||
|
||||
@GenerateMocks([IProfileRepository])
|
||||
import 'delete_account_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late DeleteAccount useCase;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIProfileRepository();
|
||||
useCase = DeleteAccount(mockRepository);
|
||||
});
|
||||
|
||||
group('DeleteAccount Use Case', () {
|
||||
const tMembreId = 'membre1';
|
||||
|
||||
test('should delete account successfully (soft delete)', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteAccount(tMembreId))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tMembreId);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.deleteAccount(tMembreId));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should throw exception when account not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteAccount(any))
|
||||
.thenThrow(Exception('Compte non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
verify(mockRepository.deleteAccount(tMembreId));
|
||||
});
|
||||
|
||||
test('should throw exception when account is already deleted', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteAccount(any))
|
||||
.thenThrow(Exception('Compte déjà désactivé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when deletion fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.deleteAccount(any))
|
||||
.thenThrow(Exception('Deletion failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/// Tests unitaires pour GetProfile use case
|
||||
library get_profile_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/get_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IProfileRepository])
|
||||
import 'get_profile_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetProfile useCase;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIProfileRepository();
|
||||
useCase = GetProfile(mockRepository);
|
||||
});
|
||||
|
||||
group('GetProfile Use Case', () {
|
||||
final tMembre = MembreCompletModel(
|
||||
id: 'membre1',
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@example.com',
|
||||
telephone: '+33612345678',
|
||||
dateNaissance: DateTime(1990, 1, 1),
|
||||
);
|
||||
|
||||
test('should return current user profile from repository', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMe()).thenAnswer((_) async => tMembre);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tMembre));
|
||||
verify(mockRepository.getMe());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return null when user is not authenticated', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMe()).thenAnswer((_) async => null);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isNull);
|
||||
verify(mockRepository.getMe());
|
||||
});
|
||||
|
||||
test('should throw exception when repository throws', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMe()).thenThrow(Exception('Unauthorized'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
verify(mockRepository.getMe());
|
||||
});
|
||||
|
||||
test('should cache profile data on successful retrieval', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getMe()).thenAnswer((_) async => tMembre);
|
||||
|
||||
// Act - Call twice
|
||||
final result1 = await useCase();
|
||||
final result2 = await useCase();
|
||||
|
||||
// Assert - Repository called twice (no caching at use case level)
|
||||
expect(result1, equals(tMembre));
|
||||
expect(result2, equals(tMembre));
|
||||
verify(mockRepository.getMe()).called(2);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/// Tests unitaires pour UpdateAvatar use case
|
||||
library update_avatar_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_avatar.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IProfileRepository])
|
||||
import 'update_avatar_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateAvatar useCase;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIProfileRepository();
|
||||
useCase = UpdateAvatar(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateAvatar Use Case', () {
|
||||
const tMembreId = 'membre1';
|
||||
const tPhotoUrl = 'https://example.com/avatar.jpg';
|
||||
|
||||
final tUpdatedMembre = MembreCompletModel(
|
||||
id: tMembreId,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@example.com',
|
||||
photo: tPhotoUrl,
|
||||
);
|
||||
|
||||
test('should update avatar successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateAvatar(tMembreId, tPhotoUrl))
|
||||
.thenAnswer((_) async => tUpdatedMembre);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, tPhotoUrl);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedMembre));
|
||||
expect(result.photo, equals(tPhotoUrl));
|
||||
verify(mockRepository.updateAvatar(tMembreId, tPhotoUrl));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should handle empty photo URL', () async {
|
||||
// Arrange
|
||||
const emptyUrl = '';
|
||||
final emptyPhotoMembre = MembreCompletModel(
|
||||
id: tMembreId,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@example.com',
|
||||
photo: emptyUrl,
|
||||
);
|
||||
when(mockRepository.updateAvatar(tMembreId, emptyUrl))
|
||||
.thenAnswer((_) async => emptyPhotoMembre);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, emptyUrl);
|
||||
|
||||
// Assert
|
||||
expect(result.photo, equals(emptyUrl));
|
||||
verify(mockRepository.updateAvatar(tMembreId, emptyUrl));
|
||||
});
|
||||
|
||||
test('should throw exception when member not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateAvatar(any, any))
|
||||
.thenThrow(Exception('Membre non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId, tPhotoUrl),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw exception when upload fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateAvatar(any, any))
|
||||
.thenThrow(Exception('Upload failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId, tPhotoUrl),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/// Tests unitaires pour UpdatePreferences use case
|
||||
library update_preferences_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_preferences.dart';
|
||||
|
||||
@GenerateMocks([IProfileRepository])
|
||||
import 'update_preferences_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdatePreferences useCase;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIProfileRepository();
|
||||
useCase = UpdatePreferences(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdatePreferences Use Case', () {
|
||||
const tMembreId = 'membre1';
|
||||
final tPreferences = {
|
||||
'language': 'fr',
|
||||
'theme': 'dark',
|
||||
'notifications': true,
|
||||
'emailNotifications': false,
|
||||
};
|
||||
|
||||
final tUpdatedPreferences = {
|
||||
...tPreferences,
|
||||
'lastUpdated': '2026-03-14T10:00:00Z',
|
||||
};
|
||||
|
||||
test('should update preferences successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updatePreferences(tMembreId, tPreferences))
|
||||
.thenAnswer((_) async => tUpdatedPreferences);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, tPreferences);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedPreferences));
|
||||
expect(result['language'], equals('fr'));
|
||||
expect(result['theme'], equals('dark'));
|
||||
verify(mockRepository.updatePreferences(tMembreId, tPreferences));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update partial preferences', () async {
|
||||
// Arrange
|
||||
final partialPrefs = {'theme': 'light'};
|
||||
final expectedResult = {'theme': 'light'};
|
||||
when(mockRepository.updatePreferences(tMembreId, partialPrefs))
|
||||
.thenAnswer((_) async => expectedResult);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, partialPrefs);
|
||||
|
||||
// Assert
|
||||
expect(result['theme'], equals('light'));
|
||||
verify(mockRepository.updatePreferences(tMembreId, partialPrefs));
|
||||
});
|
||||
|
||||
test('should handle empty preferences map', () async {
|
||||
// Arrange
|
||||
final emptyPrefs = <String, dynamic>{};
|
||||
when(mockRepository.updatePreferences(tMembreId, emptyPrefs))
|
||||
.thenAnswer((_) async => emptyPrefs);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, emptyPrefs);
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.updatePreferences(tMembreId, emptyPrefs));
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updatePreferences(any, any))
|
||||
.thenThrow(Exception('Failed to update preferences'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId, tPreferences),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/// Tests unitaires pour UpdateProfile use case
|
||||
library update_profile_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/repositories/profile_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/profile/domain/usecases/update_profile.dart';
|
||||
import 'package:unionflow_mobile_apps/features/members/data/models/membre_complete_model.dart';
|
||||
|
||||
@GenerateMocks([IProfileRepository])
|
||||
import 'update_profile_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late UpdateProfile useCase;
|
||||
late MockIProfileRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIProfileRepository();
|
||||
useCase = UpdateProfile(mockRepository);
|
||||
});
|
||||
|
||||
group('UpdateProfile Use Case', () {
|
||||
const tMembreId = 'membre1';
|
||||
final tMembre = MembreCompletModel(
|
||||
id: tMembreId,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@example.com',
|
||||
telephone: '+33612345678',
|
||||
dateNaissance: DateTime(1990, 1, 1),
|
||||
);
|
||||
|
||||
final tUpdatedMembre = MembreCompletModel(
|
||||
id: tMembreId,
|
||||
nom: 'Dupont',
|
||||
prenom: 'Jean',
|
||||
email: 'jean.dupont@example.com',
|
||||
telephone: '+33698765432', // Updated phone
|
||||
dateNaissance: DateTime(1990, 1, 1),
|
||||
adresse: '123 Rue de Paris', // Added address
|
||||
);
|
||||
|
||||
test('should update profile successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateProfile(tMembreId, tMembre))
|
||||
.thenAnswer((_) async => tUpdatedMembre);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, tMembre);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tUpdatedMembre));
|
||||
verify(mockRepository.updateProfile(tMembreId, tMembre));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should update only specified fields', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateProfile(any, any))
|
||||
.thenAnswer((_) async => tUpdatedMembre);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tMembreId, tMembre);
|
||||
|
||||
// Assert
|
||||
expect(result.telephone, equals('+33698765432'));
|
||||
expect(result.adresse, equals('123 Rue de Paris'));
|
||||
verify(mockRepository.updateProfile(tMembreId, tMembre));
|
||||
});
|
||||
|
||||
test('should throw exception when profile not found', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateProfile(any, any))
|
||||
.thenThrow(Exception('Profil non trouvé'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId, tMembre),
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
verify(mockRepository.updateProfile(tMembreId, tMembre));
|
||||
});
|
||||
|
||||
test('should throw exception when update fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.updateProfile(any, any))
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
() => useCase(tMembreId, tMembre),
|
||||
throwsException,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/// Tests unitaires pour ExportReportExcel use case
|
||||
library export_report_excel_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/export_report_excel.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'export_report_excel_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ExportReportExcel useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ExportReportExcel(mockRepository);
|
||||
});
|
||||
|
||||
group('ExportReportExcel Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tExcelPath = '/storage/reports/membres_2024.xlsx';
|
||||
const tCsvPath = '/storage/reports/membres_2024.csv';
|
||||
|
||||
test('should export report to Excel successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(tReportType, format: 'excel'))
|
||||
.thenAnswer((_) async => tExcelPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tExcelPath));
|
||||
expect(result, contains('.xlsx'));
|
||||
verify(mockRepository.exportReportExcel(tReportType, format: 'excel'));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should export report to CSV when format specified', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(tReportType, format: 'csv'))
|
||||
.thenAnswer((_) async => tCsvPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType, format: 'csv');
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCsvPath));
|
||||
expect(result, contains('.csv'));
|
||||
verify(mockRepository.exportReportExcel(tReportType, format: 'csv'));
|
||||
});
|
||||
|
||||
test('should return valid Excel path with type name', () async {
|
||||
// Arrange
|
||||
const cotisationsType = 'cotisations';
|
||||
const cotisationsExcel = '/storage/reports/cotisations_2024.xlsx';
|
||||
when(mockRepository.exportReportExcel(cotisationsType, format: 'excel'))
|
||||
.thenAnswer((_) async => cotisationsExcel);
|
||||
|
||||
// Act
|
||||
final result = await useCase(cotisationsType);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('cotisations'));
|
||||
expect(result, contains('.xlsx'));
|
||||
});
|
||||
|
||||
test('should throw exception when Excel export fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(any, format: anyNamed('format')))
|
||||
.thenThrow(Exception('Excel export failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/// Tests unitaires pour ExportReportPdf use case
|
||||
library export_report_pdf_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/export_report_pdf.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'export_report_pdf_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ExportReportPdf useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ExportReportPdf(mockRepository);
|
||||
});
|
||||
|
||||
group('ExportReportPdf Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tPdfPath = '/storage/reports/membres_2024.pdf';
|
||||
|
||||
test('should export report to PDF successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportPdf(tReportType))
|
||||
.thenAnswer((_) async => tPdfPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tPdfPath));
|
||||
expect(result, contains('.pdf'));
|
||||
verify(mockRepository.exportReportPdf(tReportType));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return valid PDF path with type name', () async {
|
||||
// Arrange
|
||||
const cotisationsType = 'cotisations';
|
||||
const cotisationsPdf = '/storage/reports/cotisations_2024.pdf';
|
||||
when(mockRepository.exportReportPdf(cotisationsType))
|
||||
.thenAnswer((_) async => cotisationsPdf);
|
||||
|
||||
// Act
|
||||
final result = await useCase(cotisationsType);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('cotisations'));
|
||||
expect(result, endsWith('.pdf'));
|
||||
});
|
||||
|
||||
test('should return URL for remote PDF file', () async {
|
||||
// Arrange
|
||||
const remotePdf = 'https://api.example.com/reports/membres.pdf';
|
||||
when(mockRepository.exportReportPdf(tReportType))
|
||||
.thenAnswer((_) async => remotePdf);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, startsWith('https://'));
|
||||
expect(result, endsWith('.pdf'));
|
||||
});
|
||||
|
||||
test('should throw exception when PDF export fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportPdf(any))
|
||||
.thenThrow(Exception('PDF generation failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/// Tests unitaires pour GenerateReport use case
|
||||
library generate_report_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/generate_report.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'generate_report_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GenerateReport useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GenerateReport(mockRepository);
|
||||
});
|
||||
|
||||
group('GenerateReport Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tFormat = 'pdf';
|
||||
|
||||
test('should generate report successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(tReportType, format: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(tReportType, format: null));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should generate report with specific format', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(tReportType, format: tFormat))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tReportType, format: tFormat);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(tReportType, format: tFormat));
|
||||
});
|
||||
|
||||
test('should generate report for different types', () async {
|
||||
// Arrange
|
||||
const altType = 'cotisations';
|
||||
when(mockRepository.generateReport(altType, format: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(altType);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(altType, format: null));
|
||||
});
|
||||
|
||||
test('should throw exception when generation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(any, format: anyNamed('format')))
|
||||
.thenThrow(Exception('Generation failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour GetReports use case
|
||||
library get_reports_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/get_reports.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'get_reports_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetReports useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GetReports(mockRepository);
|
||||
});
|
||||
|
||||
group('GetReports Use Case', () {
|
||||
final tAvailableReports = [
|
||||
{
|
||||
'type': 'membres',
|
||||
'nom': 'Rapport Membres',
|
||||
'description': 'Statistiques complètes des membres',
|
||||
'categorie': 'administratif',
|
||||
},
|
||||
{
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations',
|
||||
'description': 'Analyse des cotisations',
|
||||
'categorie': 'financier',
|
||||
},
|
||||
{
|
||||
'type': 'evenements',
|
||||
'nom': 'Rapport Événements',
|
||||
'description': 'Bilan des événements',
|
||||
'categorie': 'activites',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of available reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenAnswer((_) async => tAvailableReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tAvailableReports));
|
||||
expect(result.length, equals(3));
|
||||
expect(result[0]['type'], equals('membres'));
|
||||
verify(mockRepository.getAvailableReports());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return reports with specific categories', () async {
|
||||
// Arrange
|
||||
final financialReports = [
|
||||
{
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations',
|
||||
'categorie': 'financier',
|
||||
},
|
||||
];
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenAnswer((_) async => financialReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(1));
|
||||
expect(result.first['categorie'], equals('financier'));
|
||||
verify(mockRepository.getAvailableReports());
|
||||
});
|
||||
|
||||
test('should return empty list when no reports available', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports()).thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.getAvailableReports());
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour GetScheduledReports use case
|
||||
library get_scheduled_reports_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/get_scheduled_reports.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'get_scheduled_reports_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetScheduledReports useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GetScheduledReports(mockRepository);
|
||||
});
|
||||
|
||||
group('GetScheduledReports Use Case', () {
|
||||
final tScheduledReports = [
|
||||
{
|
||||
'id': '1',
|
||||
'type': 'membres',
|
||||
'nom': 'Rapport Membres Mensuel',
|
||||
'cronExpression': '0 0 1 * *',
|
||||
'active': true,
|
||||
'derniereLancement': '2024-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations Hebdomadaire',
|
||||
'cronExpression': '0 9 * * 1',
|
||||
'active': true,
|
||||
'derniereLancement': '2024-01-08T09:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of scheduled reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenAnswer((_) async => tScheduledReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tScheduledReports));
|
||||
expect(result.length, equals(2));
|
||||
expect(result[0]['type'], equals('membres'));
|
||||
expect(result[0]['active'], isTrue);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return only active scheduled reports', () async {
|
||||
// Arrange
|
||||
final activeReports = [
|
||||
{
|
||||
'id': '1',
|
||||
'type': 'membres',
|
||||
'cronExpression': '0 0 1 * *',
|
||||
'active': true,
|
||||
},
|
||||
];
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenAnswer((_) async => activeReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(1));
|
||||
expect(result.first['active'], isTrue);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
});
|
||||
|
||||
test('should return empty list when no scheduled reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports()).thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
});
|
||||
|
||||
test('should throw exception when retrieval fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenThrow(Exception('Database error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/// Tests unitaires pour ScheduleReport use case
|
||||
library schedule_report_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/schedule_report.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'schedule_report_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ScheduleReport useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ScheduleReport(mockRepository);
|
||||
});
|
||||
|
||||
group('ScheduleReport Use Case', () {
|
||||
const tCronExpression = '0 0 1 * *'; // 1er de chaque mois à minuit
|
||||
|
||||
test('should schedule report successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase();
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: null));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should schedule report with cron expression', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: tCronExpression))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(cronExpression: tCronExpression);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: tCronExpression));
|
||||
});
|
||||
|
||||
test('should schedule report with different cron expressions', () async {
|
||||
// Arrange
|
||||
const weeklyCron = '0 9 * * 1'; // Tous les lundis à 9h
|
||||
when(mockRepository.scheduleReport(cronExpression: weeklyCron))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(cronExpression: weeklyCron);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: weeklyCron));
|
||||
});
|
||||
|
||||
test('should throw exception when scheduling fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: anyNamed('cronExpression')))
|
||||
.thenThrow(Exception('Invalid cron expression'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(cronExpression: tCronExpression), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/// Integration tests for Finance Workflow (API-only)
|
||||
library finance_workflow_integration_test;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'helpers/test_config.dart';
|
||||
import 'helpers/auth_helper.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late http.Client client;
|
||||
late AuthHelper authHelper;
|
||||
|
||||
setUpAll(() async {
|
||||
print('\n=== Finance Workflow Integration Tests ===\n');
|
||||
client = http.Client();
|
||||
authHelper = AuthHelper(client);
|
||||
|
||||
// Authenticate as ORG_ADMIN
|
||||
final authenticated = await authHelper.authenticateAsOrgAdmin();
|
||||
expect(authenticated, true, reason: 'Authentication must succeed');
|
||||
|
||||
print('Setup complete - Token obtained\n');
|
||||
});
|
||||
|
||||
tearDownAll(() {
|
||||
client.close();
|
||||
print('\n=== Integration Tests Completed ===\n');
|
||||
});
|
||||
|
||||
group('Finance Workflow - Approvals', () {
|
||||
test('GET /api/finance/approvals/pending - List pending approvals', () async {
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/pending')
|
||||
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
||||
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
||||
|
||||
final List<dynamic> approvals = json.decode(response.body);
|
||||
expect(approvals, isA<List>(), reason: 'Response must be a list');
|
||||
|
||||
print('GET pending approvals: ${approvals.length} found');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('GET /api/finance/approvals/{id} - Get approval by ID', () async {
|
||||
final listUrl = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/pending')
|
||||
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
||||
|
||||
final listResponse = await client.get(listUrl, headers: authHelper.getAuthHeaders());
|
||||
expect(listResponse.statusCode, 200);
|
||||
|
||||
final List<dynamic> approvals = json.decode(listResponse.body);
|
||||
|
||||
if (approvals.isEmpty) {
|
||||
print('No pending approvals - test skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
final approvalId = approvals.first['id'];
|
||||
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/$approvalId');
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
||||
|
||||
final approval = json.decode(response.body);
|
||||
expect(approval['id'], equals(approvalId), reason: 'ID must match');
|
||||
|
||||
print('GET approval by ID: ${approval['id']}');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('POST /api/finance/approvals/{id}/approve - Approve transaction (simulated)', () async {
|
||||
print('Test approve transaction - Simulated (avoids prod modification)');
|
||||
print(' Endpoint: POST /api/finance/approvals/{id}/approve');
|
||||
print(' Body: { "comment": "Approved by integration test" }');
|
||||
print(' Expected: HTTP 200, status=approved');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('POST /api/finance/approvals/{id}/reject - Reject transaction (simulated)', () async {
|
||||
print('Test reject transaction - Simulated (avoids prod modification)');
|
||||
print(' Endpoint: POST /api/finance/approvals/{id}/reject');
|
||||
print(' Body: { "reason": "Rejected by integration test" }');
|
||||
print(' Expected: HTTP 200, status=rejected');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
});
|
||||
|
||||
group('Finance Workflow - Budgets', () {
|
||||
String? createdBudgetId;
|
||||
|
||||
test('GET /api/finance/budgets - List budgets', () async {
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets')
|
||||
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
||||
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
||||
|
||||
final List<dynamic> budgets = json.decode(response.body);
|
||||
expect(budgets, isA<List>(), reason: 'Response must be a list');
|
||||
|
||||
print('GET budgets: ${budgets.length} found');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('POST /api/finance/budgets - Create budget', () async {
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
||||
|
||||
final requestBody = {
|
||||
'name': 'Budget Integration Test ${DateTime.now().millisecondsSinceEpoch}',
|
||||
'description': 'Budget created by integration test',
|
||||
'organizationId': TestConfig.testOrganizationId,
|
||||
'period': 'ANNUAL',
|
||||
'year': DateTime.now().year,
|
||||
'lines': [
|
||||
{
|
||||
'category': 'CONTRIBUTIONS',
|
||||
'name': 'Contributions',
|
||||
'amountPlanned': 1000000.0,
|
||||
'description': 'Membership contributions',
|
||||
},
|
||||
{
|
||||
'category': 'SAVINGS',
|
||||
'name': 'Savings',
|
||||
'amountPlanned': 500000.0,
|
||||
'description': 'Savings collection',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
final response = await client.post(
|
||||
url,
|
||||
headers: authHelper.getAuthHeaders(),
|
||||
body: json.encode(requestBody),
|
||||
);
|
||||
|
||||
expect(response.statusCode, inInclusiveRange(200, 201), reason: 'HTTP 200/201 expected');
|
||||
|
||||
final budget = json.decode(response.body);
|
||||
expect(budget['id'], isNotNull, reason: 'Budget ID must be present');
|
||||
expect(budget['name'], contains('Budget Integration Test'), reason: 'Name must match');
|
||||
|
||||
createdBudgetId = budget['id'];
|
||||
print('POST create budget: ${budget['id']} - ${budget['name']}');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('GET /api/finance/budgets/{id} - Get budget by ID', () async {
|
||||
String budgetId;
|
||||
|
||||
if (createdBudgetId != null) {
|
||||
budgetId = createdBudgetId!;
|
||||
} else {
|
||||
final listUrl = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets')
|
||||
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
||||
|
||||
final listResponse = await client.get(listUrl, headers: authHelper.getAuthHeaders());
|
||||
expect(listResponse.statusCode, 200);
|
||||
|
||||
final List<dynamic> budgets = json.decode(listResponse.body);
|
||||
if (budgets.isEmpty) {
|
||||
print('No budgets found - test skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
budgetId = budgets.first['id'];
|
||||
}
|
||||
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets/$budgetId');
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
||||
|
||||
final budget = json.decode(response.body);
|
||||
expect(budget['id'], equals(budgetId), reason: 'ID must match');
|
||||
expect(budget['lines'], isNotNull, reason: 'Budget lines must be present');
|
||||
|
||||
print('GET budget by ID: ${budget['id']} - ${budget['name']}');
|
||||
print(' Budget lines: ${budget['lines'].length}');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
});
|
||||
|
||||
group('Finance Workflow - Negative Tests', () {
|
||||
test('GET nonexistent approval - Should return 404', () async {
|
||||
final fakeId = '00000000-0000-0000-0000-000000000000';
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/$fakeId');
|
||||
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found expected');
|
||||
|
||||
print('Negative test: 404 for nonexistent approval');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('GET nonexistent budget - Should return 404', () async {
|
||||
final fakeId = '00000000-0000-0000-0000-000000000000';
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets/$fakeId');
|
||||
|
||||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||||
|
||||
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found expected');
|
||||
|
||||
print('Negative test: 404 for nonexistent budget');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
|
||||
test('POST budget without authentication - Should return 401', () async {
|
||||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
||||
final requestBody = {
|
||||
'name': 'Budget Without Auth',
|
||||
'organizationId': TestConfig.testOrganizationId,
|
||||
'period': 'ANNUAL',
|
||||
'year': 2026,
|
||||
'lines': [],
|
||||
};
|
||||
|
||||
final response = await client.post(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: json.encode(requestBody),
|
||||
);
|
||||
|
||||
expect(response.statusCode, 401, reason: 'HTTP 401 Unauthorized expected');
|
||||
|
||||
print('Negative test: 401 for unauthenticated request');
|
||||
|
||||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/// Helper pour l'authentification dans les tests d'intégration
|
||||
library auth_helper;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'test_config.dart';
|
||||
|
||||
/// Helper pour gérer l'authentification dans les tests
|
||||
class AuthHelper {
|
||||
final http.Client _client;
|
||||
String? _accessToken;
|
||||
String? _refreshToken;
|
||||
|
||||
AuthHelper(this._client);
|
||||
|
||||
/// Token d'accès actuel
|
||||
String? get accessToken => _accessToken;
|
||||
|
||||
/// Authentifie un utilisateur via Keycloak Direct Access Grant
|
||||
///
|
||||
/// Retourne true si l'authentification réussit, false sinon
|
||||
Future<bool> authenticate(String username, String password) async {
|
||||
final url = Uri.parse(
|
||||
'${TestConfig.keycloakUrl}/realms/${TestConfig.keycloakRealm}/protocol/openid-connect/token',
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await _client.post(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: {
|
||||
'grant_type': 'password',
|
||||
'client_id': TestConfig.keycloakClientId,
|
||||
'username': username,
|
||||
'password': password,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
_accessToken = data['access_token'];
|
||||
_refreshToken = data['refresh_token'];
|
||||
|
||||
if (TestConfig.enableDetailedLogs) {
|
||||
print('✅ Authentification réussie pour: $username');
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (TestConfig.enableDetailedLogs) {
|
||||
print('❌ Échec authentification: ${response.statusCode} - ${response.body}');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (TestConfig.enableDetailedLogs) {
|
||||
print('❌ Erreur authentification: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentifie l'utilisateur admin de test
|
||||
Future<bool> authenticateAsAdmin() async {
|
||||
return await authenticate(
|
||||
TestConfig.testAdminUsername,
|
||||
TestConfig.testAdminPassword,
|
||||
);
|
||||
}
|
||||
|
||||
/// Authentifie l'utilisateur org admin de test
|
||||
Future<bool> authenticateAsOrgAdmin() async {
|
||||
return await authenticate(
|
||||
TestConfig.testOrgAdminUsername,
|
||||
TestConfig.testOrgAdminPassword,
|
||||
);
|
||||
}
|
||||
|
||||
/// Rafraîchit le token d'accès
|
||||
Future<bool> refreshAccessToken() async {
|
||||
if (_refreshToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final url = Uri.parse(
|
||||
'${TestConfig.keycloakUrl}/realms/${TestConfig.keycloakRealm}/protocol/openid-connect/token',
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await _client.post(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: {
|
||||
'grant_type': 'refresh_token',
|
||||
'client_id': TestConfig.keycloakClientId,
|
||||
'refresh_token': _refreshToken!,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
_accessToken = data['access_token'];
|
||||
_refreshToken = data['refresh_token'];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (TestConfig.enableDetailedLogs) {
|
||||
print('❌ Erreur rafraîchissement token: $e');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Déconnecte l'utilisateur
|
||||
Future<void> logout() async {
|
||||
_accessToken = null;
|
||||
_refreshToken = null;
|
||||
|
||||
if (TestConfig.enableDetailedLogs) {
|
||||
print('🔓 Déconnexion effectuée');
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourne les headers HTTP avec authentification
|
||||
Map<String, String> getAuthHeaders() {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
if (_accessToken != null) 'Authorization': 'Bearer $_accessToken',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/// Configuration pour les tests d'intégration
|
||||
library test_config;
|
||||
|
||||
/// Configuration des tests d'intégration
|
||||
class TestConfig {
|
||||
/// URL de base de l'API backend (environnement de test)
|
||||
static const String apiBaseUrl = 'http://localhost:8085';
|
||||
|
||||
/// URL de Keycloak (environnement de test)
|
||||
static const String keycloakUrl = 'http://localhost:8180';
|
||||
|
||||
/// Realm Keycloak
|
||||
static const String keycloakRealm = 'unionflow';
|
||||
|
||||
/// Client ID Keycloak
|
||||
static const String keycloakClientId = 'unionflow-mobile';
|
||||
|
||||
/// Credentials utilisateur de test (SUPER_ADMIN)
|
||||
static const String testAdminUsername = 'admin@unionflow.test';
|
||||
static const String testAdminPassword = 'Admin@123';
|
||||
|
||||
/// Credentials utilisateur de test (ORG_ADMIN)
|
||||
static const String testOrgAdminUsername = 'orgadmin@unionflow.test';
|
||||
static const String testOrgAdminPassword = 'OrgAdmin@123';
|
||||
|
||||
/// ID d'organisation de test
|
||||
static const String testOrganizationId = '00000000-0000-0000-0000-000000000001';
|
||||
|
||||
/// Timeout pour les requêtes HTTP (ms)
|
||||
static const int httpTimeout = 30000;
|
||||
|
||||
/// Délai d'attente entre les tests (ms)
|
||||
static const int delayBetweenTests = 500;
|
||||
|
||||
/// Active les logs détaillés
|
||||
static const bool enableDetailedLogs = true;
|
||||
}
|
||||
Reference in New Issue
Block a user