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,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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user