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

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

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

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

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

- Nettoyage: fichiers temporaires supprimés

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

View File

@@ -0,0 +1,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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}

View File

@@ -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);
});
});
}