Refactoring

This commit is contained in:
DahoudG
2025-09-17 17:54:06 +00:00
parent 12d514d866
commit 63fe107f98
165 changed files with 54220 additions and 276 deletions

View File

@@ -0,0 +1,443 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/core/error/exceptions.dart';
import 'package:unionflow_mobile_apps/core/network/api_client.dart';
import 'package:unionflow_mobile_apps/features/solidarite/data/datasources/solidarite_remote_data_source.dart';
import 'package:unionflow_mobile_apps/features/solidarite/data/models/demande_aide_model.dart';
import '../../../../fixtures/fixture_reader.dart';
import 'solidarite_remote_data_source_test.mocks.dart';
@GenerateMocks([ApiClient])
void main() {
group('SolidariteRemoteDataSource', () {
late SolidariteRemoteDataSourceImpl dataSource;
late MockApiClient mockApiClient;
setUp(() {
mockApiClient = MockApiClient();
dataSource = SolidariteRemoteDataSourceImpl(apiClient: mockApiClient);
});
group('creerDemandeAide', () {
final tDemandeModel = DemandeAideModel.fromJson(
json.decode(fixture('demande_aide.json')),
);
test('doit effectuer un POST vers /api/solidarite/demandes avec les bonnes données', () async {
// arrange
when(mockApiClient.post(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response(fixture('demande_aide.json'), 201));
// act
final result = await dataSource.creerDemandeAide(tDemandeModel);
// assert
verify(mockApiClient.post(
'/api/solidarite/demandes',
data: tDemandeModel.toJson(),
));
expect(result, equals(tDemandeModel));
});
test('doit lancer ServerException quand le code de réponse n\'est pas 201', () async {
// arrange
when(mockApiClient.post(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response('Erreur serveur', 500));
// act & assert
expect(
() => dataSource.creerDemandeAide(tDemandeModel),
throwsA(isA<ServerException>()),
);
});
test('doit lancer ValidationException quand le code de réponse est 400', () async {
// arrange
when(mockApiClient.post(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response('Données invalides', 400));
// act & assert
expect(
() => dataSource.creerDemandeAide(tDemandeModel),
throwsA(isA<ValidationException>()),
);
});
test('doit lancer NetworkException en cas d\'erreur réseau', () async {
// arrange
when(mockApiClient.post(any, data: anyNamed('data')))
.thenThrow(const NetworkException('Pas de connexion'));
// act & assert
expect(
() => dataSource.creerDemandeAide(tDemandeModel),
throwsA(isA<NetworkException>()),
);
});
});
group('obtenirDemandeAide', () {
const tId = 'demande-123';
final tDemandeModel = DemandeAideModel.fromJson(
json.decode(fixture('demande_aide.json')),
);
test('doit effectuer un GET vers /api/solidarite/demandes/{id}', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response(fixture('demande_aide.json'), 200));
// act
final result = await dataSource.obtenirDemandeAide(tId);
// assert
verify(mockApiClient.get('/api/solidarite/demandes/$tId'));
expect(result, equals(tDemandeModel));
});
test('doit lancer NotFoundException quand le code de réponse est 404', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('Non trouvé', 404));
// act & assert
expect(
() => dataSource.obtenirDemandeAide(tId),
throwsA(isA<NotFoundException>()),
);
});
test('doit lancer ServerException quand le code de réponse n\'est pas 200', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('Erreur serveur', 500));
// act & assert
expect(
() => dataSource.obtenirDemandeAide(tId),
throwsA(isA<ServerException>()),
);
});
});
group('rechercherDemandesAide', () {
final tDemandesJson = json.decode(fixture('demandes_aide_list.json'));
final tDemandesModels = (tDemandesJson['content'] as List)
.map((json) => DemandeAideModel.fromJson(json))
.toList();
test('doit effectuer un GET vers /api/solidarite/demandes avec les paramètres de recherche', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response(fixture('demandes_aide_list.json'), 200));
// act
final result = await dataSource.rechercherDemandesAide(
organisationId: 'org-1',
typeAide: 'AIDE_FINANCIERE_MEDICALE',
statut: 'EN_ATTENTE',
demandeurId: 'user-1',
urgente: true,
page: 0,
taille: 20,
);
// assert
verify(mockApiClient.get(
'/api/solidarite/demandes?organisationId=org-1&typeAide=AIDE_FINANCIERE_MEDICALE&statut=EN_ATTENTE&demandeurId=user-1&urgente=true&page=0&size=20',
));
expect(result, equals(tDemandesModels));
});
test('doit construire l\'URL correctement avec des paramètres null', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response(fixture('demandes_aide_list.json'), 200));
// act
await dataSource.rechercherDemandesAide(
organisationId: null,
typeAide: null,
statut: null,
demandeurId: null,
urgente: null,
page: 0,
taille: 20,
);
// assert
verify(mockApiClient.get('/api/solidarite/demandes?page=0&size=20'));
});
test('doit retourner une liste vide quand aucune demande n\'est trouvée', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('{"content": [], "totalElements": 0}', 200));
// act
final result = await dataSource.rechercherDemandesAide(
page: 0,
taille: 20,
);
// assert
expect(result, isEmpty);
});
test('doit lancer ServerException quand le code de réponse n\'est pas 200', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('Erreur serveur', 500));
// act & assert
expect(
() => dataSource.rechercherDemandesAide(page: 0, taille: 20),
throwsA(isA<ServerException>()),
);
});
});
group('mettreAJourDemandeAide', () {
final tDemandeModel = DemandeAideModel.fromJson(
json.decode(fixture('demande_aide.json')),
);
test('doit effectuer un PUT vers /api/solidarite/demandes/{id}', () async {
// arrange
when(mockApiClient.put(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response(fixture('demande_aide.json'), 200));
// act
final result = await dataSource.mettreAJourDemandeAide(tDemandeModel);
// assert
verify(mockApiClient.put(
'/api/solidarite/demandes/${tDemandeModel.id}',
data: tDemandeModel.toJson(),
));
expect(result, equals(tDemandeModel));
});
test('doit lancer NotFoundException quand le code de réponse est 404', () async {
// arrange
when(mockApiClient.put(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response('Non trouvé', 404));
// act & assert
expect(
() => dataSource.mettreAJourDemandeAide(tDemandeModel),
throwsA(isA<NotFoundException>()),
);
});
test('doit lancer ValidationException quand le code de réponse est 400', () async {
// arrange
when(mockApiClient.put(any, data: anyNamed('data')))
.thenAnswer((_) async => http.Response('Données invalides', 400));
// act & assert
expect(
() => dataSource.mettreAJourDemandeAide(tDemandeModel),
throwsA(isA<ValidationException>()),
);
});
});
group('supprimerDemandeAide', () {
const tId = 'demande-123';
test('doit effectuer un DELETE vers /api/solidarite/demandes/{id}', () async {
// arrange
when(mockApiClient.delete(any))
.thenAnswer((_) async => http.Response('', 204));
// act
await dataSource.supprimerDemandeAide(tId);
// assert
verify(mockApiClient.delete('/api/solidarite/demandes/$tId'));
});
test('doit lancer NotFoundException quand le code de réponse est 404', () async {
// arrange
when(mockApiClient.delete(any))
.thenAnswer((_) async => http.Response('Non trouvé', 404));
// act & assert
expect(
() => dataSource.supprimerDemandeAide(tId),
throwsA(isA<NotFoundException>()),
);
});
test('doit lancer ServerException quand le code de réponse n\'est pas 204', () async {
// arrange
when(mockApiClient.delete(any))
.thenAnswer((_) async => http.Response('Erreur serveur', 500));
// act & assert
expect(
() => dataSource.supprimerDemandeAide(tId),
throwsA(isA<ServerException>()),
);
});
});
group('soumettreDemandeAide', () {
const tId = 'demande-123';
final tDemandeModel = DemandeAideModel.fromJson(
json.decode(fixture('demande_aide.json')),
);
test('doit effectuer un POST vers /api/solidarite/demandes/{id}/soumettre', () async {
// arrange
when(mockApiClient.post(any))
.thenAnswer((_) async => http.Response(fixture('demande_aide.json'), 200));
// act
final result = await dataSource.soumettreDemandeAide(tId);
// assert
verify(mockApiClient.post('/api/solidarite/demandes/$tId/soumettre'));
expect(result, equals(tDemandeModel));
});
test('doit lancer NotFoundException quand le code de réponse est 404', () async {
// arrange
when(mockApiClient.post(any))
.thenAnswer((_) async => http.Response('Non trouvé', 404));
// act & assert
expect(
() => dataSource.soumettreDemandeAide(tId),
throwsA(isA<NotFoundException>()),
);
});
test('doit lancer ValidationException quand la demande ne peut pas être soumise', () async {
// arrange
when(mockApiClient.post(any))
.thenAnswer((_) async => http.Response('Demande incomplète', 400));
// act & assert
expect(
() => dataSource.soumettreDemandeAide(tId),
throwsA(isA<ValidationException>()),
);
});
});
group('obtenirDemandesUrgentes', () {
final tDemandesJson = json.decode(fixture('demandes_aide_urgentes.json'));
final tDemandesModels = (tDemandesJson as List)
.map((json) => DemandeAideModel.fromJson(json))
.toList();
test('doit effectuer un GET vers /api/solidarite/demandes/urgentes', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response(fixture('demandes_aide_urgentes.json'), 200));
// act
final result = await dataSource.obtenirDemandesUrgentes('org-1');
// assert
verify(mockApiClient.get('/api/solidarite/demandes/urgentes?organisationId=org-1'));
expect(result, equals(tDemandesModels));
});
test('doit retourner une liste vide quand aucune demande urgente n\'est trouvée', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('[]', 200));
// act
final result = await dataSource.obtenirDemandesUrgentes('org-1');
// assert
expect(result, isEmpty);
});
});
group('obtenirMesDemandes', () {
final tDemandesJson = json.decode(fixture('mes_demandes.json'));
final tDemandesModels = (tDemandesJson['content'] as List)
.map((json) => DemandeAideModel.fromJson(json))
.toList();
test('doit effectuer un GET vers /api/solidarite/demandes/mes-demandes', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response(fixture('mes_demandes.json'), 200));
// act
final result = await dataSource.obtenirMesDemandes(
demandeurId: 'user-1',
page: 0,
taille: 20,
);
// assert
verify(mockApiClient.get('/api/solidarite/demandes/mes-demandes?demandeurId=user-1&page=0&size=20'));
expect(result, equals(tDemandesModels));
});
});
group('gestion des erreurs réseau', () {
test('doit lancer NetworkException en cas de timeout', () async {
// arrange
when(mockApiClient.get(any))
.thenThrow(const NetworkException('Timeout'));
// act & assert
expect(
() => dataSource.obtenirDemandeAide('demande-123'),
throwsA(isA<NetworkException>()),
);
});
test('doit lancer NetworkException en cas d\'erreur de connexion', () async {
// arrange
when(mockApiClient.get(any))
.thenThrow(const NetworkException('Connexion refusée'));
// act & assert
expect(
() => dataSource.obtenirDemandeAide('demande-123'),
throwsA(isA<NetworkException>()),
);
});
});
group('gestion des réponses malformées', () {
test('doit lancer ServerException en cas de JSON invalide', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('JSON invalide', 200));
// act & assert
expect(
() => dataSource.obtenirDemandeAide('demande-123'),
throwsA(isA<ServerException>()),
);
});
test('doit lancer ServerException en cas de structure JSON inattendue', () async {
// arrange
when(mockApiClient.get(any))
.thenAnswer((_) async => http.Response('{"unexpected": "structure"}', 200));
// act & assert
expect(
() => dataSource.obtenirDemandeAide('demande-123'),
throwsA(isA<ServerException>()),
);
});
});
});
}

View File

@@ -0,0 +1,42 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/entities/demande_aide.dart';
void main() {
group('DemandeAide Entity', () {
test('doit créer une instance simple', () {
final demande = DemandeAide(
id: 'test-id',
numeroReference: 'REF-001',
titre: 'Test',
description: 'Description test',
typeAide: TypeAide.aideFinanciereUrgente,
statut: StatutAide.brouillon,
priorite: PrioriteAide.normale,
demandeurId: 'user-1',
nomDemandeur: 'Test User',
organisationId: 'org-1',
dateCreation: DateTime.now(),
dateModification: DateTime.now(),
);
expect(demande.id, 'test-id');
expect(demande.titre, 'Test');
expect(demande.typeAide, TypeAide.aideFinanciereUrgente);
expect(demande.statut, StatutAide.brouillon);
});
test('doit tester les enums de base', () {
// Test TypeAide
expect(TypeAide.values.isNotEmpty, true);
expect(TypeAide.aideFinanciereUrgente.toString(), contains('aideFinanciereUrgente'));
// Test StatutAide
expect(StatutAide.values.isNotEmpty, true);
expect(StatutAide.brouillon.toString(), contains('brouillon'));
// Test PrioriteAide
expect(PrioriteAide.values.isNotEmpty, true);
expect(PrioriteAide.normale.toString(), contains('normale'));
});
});
}

View File

@@ -0,0 +1,356 @@
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/core/error/failures.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/entities/demande_aide.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/repositories/solidarite_repository.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/usecases/gerer_demandes_aide_usecase.dart';
import 'creer_demande_aide_usecase_test.mocks.dart';
@GenerateMocks([SolidariteRepository])
void main() {
group('CreerDemandeAideUseCase', () {
late CreerDemandeAideUseCase usecase;
late MockSolidariteRepository mockRepository;
setUp(() {
mockRepository = MockSolidariteRepository();
usecase = CreerDemandeAideUseCase(mockRepository);
});
final tDemande = DemandeAide(
id: '',
numeroReference: '',
titre: 'Aide médicale urgente',
description: 'Besoin d\'aide pour frais médicaux',
typeAide: TypeAide.aideFinanciereMedicale,
statut: StatutAide.brouillon,
priorite: PrioriteAide.haute,
estUrgente: true,
montantDemande: 500000.0,
dateCreation: DateTime.now(),
dateModification: DateTime.now(),
organisationId: 'org-1',
demandeurId: 'user-1',
nomDemandeur: 'Marie Kouassi',
emailDemandeur: 'marie@example.com',
telephoneDemandeur: '+225123456789',
beneficiaires: const [],
evaluations: const [],
commentairesInternes: const [],
historiqueStatuts: const [],
piecesJustificatives: const [],
tags: const [],
metadonnees: const {},
);
final tDemandeCreee = tDemande.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
test('doit créer une demande d\'aide avec succès', () async {
// arrange
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreee));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemande));
// assert
expect(result, Right(tDemandeCreee));
verify(mockRepository.creerDemandeAide(tDemande));
verifyNoMoreInteractions(mockRepository);
});
test('doit retourner ValidationFailure quand les données sont invalides', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(titre: '');
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('Le titre est requis')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result, const Left(ValidationFailure('Le titre est requis')));
verify(mockRepository.creerDemandeAide(tDemandeInvalide));
verifyNoMoreInteractions(mockRepository);
});
test('doit retourner ServerFailure quand le serveur échoue', () async {
// arrange
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ServerFailure('Erreur serveur')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemande));
// assert
expect(result, const Left(ServerFailure('Erreur serveur')));
verify(mockRepository.creerDemandeAide(tDemande));
verifyNoMoreInteractions(mockRepository);
});
test('doit retourner NetworkFailure quand il n\'y a pas de connexion', () async {
// arrange
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(NetworkFailure('Pas de connexion internet')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemande));
// assert
expect(result, const Left(NetworkFailure('Pas de connexion internet')));
verify(mockRepository.creerDemandeAide(tDemande));
verifyNoMoreInteractions(mockRepository);
});
group('validation des paramètres', () {
test('doit valider que le titre n\'est pas vide', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(titre: '');
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('Le titre est requis')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result.isLeft(), true);
result.fold(
(failure) => expect(failure, isA<ValidationFailure>()),
(success) => fail('Devrait échouer avec ValidationFailure'),
);
});
test('doit valider que la description n\'est pas vide', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(description: '');
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('La description est requise')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result.isLeft(), true);
result.fold(
(failure) => expect(failure, isA<ValidationFailure>()),
(success) => fail('Devrait échouer avec ValidationFailure'),
);
});
test('doit valider que le montant est positif', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(montantDemande: -100.0);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('Le montant doit être positif')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result.isLeft(), true);
result.fold(
(failure) => expect(failure, isA<ValidationFailure>()),
(success) => fail('Devrait échouer avec ValidationFailure'),
);
});
test('doit valider que l\'email du demandeur est valide', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(emailDemandeur: 'email-invalide');
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('Email invalide')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result.isLeft(), true);
result.fold(
(failure) => expect(failure, isA<ValidationFailure>()),
(success) => fail('Devrait échouer avec ValidationFailure'),
);
});
test('doit valider que le téléphone du demandeur est valide', () async {
// arrange
final tDemandeInvalide = tDemande.copyWith(telephoneDemandeur: '123');
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => const Left(ValidationFailure('Numéro de téléphone invalide')));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeInvalide));
// assert
expect(result.isLeft(), true);
result.fold(
(failure) => expect(failure, isA<ValidationFailure>()),
(success) => fail('Devrait échouer avec ValidationFailure'),
);
});
});
group('gestion des cas limites', () {
test('doit gérer une demande avec montant null', () async {
// arrange
final tDemandeSansMontant = tDemande.copyWith(montantDemande: null);
final tDemandeCreeSansMontant = tDemandeSansMontant.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreeSansMontant));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeSansMontant));
// assert
expect(result, Right(tDemandeCreeSansMontant));
verify(mockRepository.creerDemandeAide(tDemandeSansMontant));
});
test('doit gérer une demande avec justification null', () async {
// arrange
final tDemandeSansJustification = tDemande.copyWith(justification: null);
final tDemandeCreeSansJustification = tDemandeSansJustification.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreeSansJustification));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeSansJustification));
// assert
expect(result, Right(tDemandeCreeSansJustification));
verify(mockRepository.creerDemandeAide(tDemandeSansJustification));
});
test('doit gérer une demande avec bénéficiaires multiples', () async {
// arrange
final tBeneficiaires = [
const BeneficiaireAide(
prenom: 'Jean',
nom: 'Kouassi',
age: 25,
),
const BeneficiaireAide(
prenom: 'Marie',
nom: 'Kouassi',
age: 23,
),
];
final tDemandeAvecBeneficiaires = tDemande.copyWith(beneficiaires: tBeneficiaires);
final tDemandeCreeeAvecBeneficiaires = tDemandeAvecBeneficiaires.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreeeAvecBeneficiaires));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeAvecBeneficiaires));
// assert
expect(result, Right(tDemandeCreeeAvecBeneficiaires));
verify(mockRepository.creerDemandeAide(tDemandeAvecBeneficiaires));
});
test('doit gérer une demande avec contact d\'urgence', () async {
// arrange
const tContactUrgence = ContactUrgence(
prenom: 'Paul',
nom: 'Kouassi',
telephone: '+225987654321',
email: 'paul@example.com',
relation: 'Frère',
);
final tDemandeAvecContact = tDemande.copyWith(contactUrgence: tContactUrgence);
final tDemandeCreeeAvecContact = tDemandeAvecContact.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreeeAvecContact));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeAvecContact));
// assert
expect(result, Right(tDemandeCreeeAvecContact));
verify(mockRepository.creerDemandeAide(tDemandeAvecContact));
});
test('doit gérer une demande avec localisation', () async {
// arrange
const tLocalisation = Localisation(
adresse: '123 Rue de la Paix',
ville: 'Abidjan',
codePostal: '00225',
pays: 'Côte d\'Ivoire',
latitude: 5.3600,
longitude: -4.0083,
);
final tDemandeAvecLocalisation = tDemande.copyWith(localisation: tLocalisation);
final tDemandeCreeeAvecLocalisation = tDemandeAvecLocalisation.copyWith(
id: 'demande-123',
numeroReference: 'REF-2024-001',
);
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreeeAvecLocalisation));
// act
final result = await usecase(CreerDemandeAideParams(demande: tDemandeAvecLocalisation));
// assert
expect(result, Right(tDemandeCreeeAvecLocalisation));
verify(mockRepository.creerDemandeAide(tDemandeAvecLocalisation));
});
});
group('performance et concurrence', () {
test('doit gérer les appels concurrents', () async {
// arrange
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async => Right(tDemandeCreee));
// act
final futures = List.generate(5, (index) {
final demande = tDemande.copyWith(titre: 'Demande $index');
return usecase(CreerDemandeAideParams(demande: demande));
});
final results = await Future.wait(futures);
// assert
expect(results.length, 5);
for (final result in results) {
expect(result.isRight(), true);
}
verify(mockRepository.creerDemandeAide(any)).called(5);
});
test('doit gérer les timeouts', () async {
// arrange
when(mockRepository.creerDemandeAide(any))
.thenAnswer((_) async {
await Future.delayed(const Duration(seconds: 10));
return Right(tDemandeCreee);
});
// act & assert
expect(
() => usecase(CreerDemandeAideParams(demande: tDemande))
.timeout(const Duration(seconds: 5)),
throwsA(isA<TimeoutException>()),
);
});
});
});
}

View File

@@ -0,0 +1,441 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/core/error/failures.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/entities/demande_aide.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/usecases/gerer_demandes_aide_usecase.dart';
import 'package:unionflow_mobile_apps/features/solidarite/presentation/bloc/demandes_aide/demandes_aide_bloc.dart';
import 'package:unionflow_mobile_apps/features/solidarite/presentation/bloc/demandes_aide/demandes_aide_event.dart';
import 'package:unionflow_mobile_apps/features/solidarite/presentation/bloc/demandes_aide/demandes_aide_state.dart';
import 'demandes_aide_bloc_test.mocks.dart';
@GenerateMocks([
CreerDemandeAideUseCase,
MettreAJourDemandeAideUseCase,
ObtenirDemandeAideUseCase,
SoumettreDemandeAideUseCase,
EvaluerDemandeAideUseCase,
RechercherDemandesAideUseCase,
ObtenirDemandesUrgentesUseCase,
ObtenirMesDemandesUseCase,
ValiderDemandeAideUseCase,
CalculerPrioriteDemandeUseCase,
])
void main() {
group('DemandesAideBloc', () {
late DemandesAideBloc bloc;
late MockCreerDemandeAideUseCase mockCreerDemandeAideUseCase;
late MockMettreAJourDemandeAideUseCase mockMettreAJourDemandeAideUseCase;
late MockObtenirDemandeAideUseCase mockObtenirDemandeAideUseCase;
late MockSoumettreDemandeAideUseCase mockSoumettreDemandeAideUseCase;
late MockEvaluerDemandeAideUseCase mockEvaluerDemandeAideUseCase;
late MockRechercherDemandesAideUseCase mockRechercherDemandesAideUseCase;
late MockObtenirDemandesUrgentesUseCase mockObtenirDemandesUrgentesUseCase;
late MockObtenirMesDemandesUseCase mockObtenirMesDemandesUseCase;
late MockValiderDemandeAideUseCase mockValiderDemandeAideUseCase;
late MockCalculerPrioriteDemandeUseCase mockCalculerPrioriteDemandeUseCase;
setUp(() {
mockCreerDemandeAideUseCase = MockCreerDemandeAideUseCase();
mockMettreAJourDemandeAideUseCase = MockMettreAJourDemandeAideUseCase();
mockObtenirDemandeAideUseCase = MockObtenirDemandeAideUseCase();
mockSoumettreDemandeAideUseCase = MockSoumettreDemandeAideUseCase();
mockEvaluerDemandeAideUseCase = MockEvaluerDemandeAideUseCase();
mockRechercherDemandesAideUseCase = MockRechercherDemandesAideUseCase();
mockObtenirDemandesUrgentesUseCase = MockObtenirDemandesUrgentesUseCase();
mockObtenirMesDemandesUseCase = MockObtenirMesDemandesUseCase();
mockValiderDemandeAideUseCase = MockValiderDemandeAideUseCase();
mockCalculerPrioriteDemandeUseCase = MockCalculerPrioriteDemandeUseCase();
bloc = DemandesAideBloc(
creerDemandeAideUseCase: mockCreerDemandeAideUseCase,
mettreAJourDemandeAideUseCase: mockMettreAJourDemandeAideUseCase,
obtenirDemandeAideUseCase: mockObtenirDemandeAideUseCase,
soumettreDemandeAideUseCase: mockSoumettreDemandeAideUseCase,
evaluerDemandeAideUseCase: mockEvaluerDemandeAideUseCase,
rechercherDemandesAideUseCase: mockRechercherDemandesAideUseCase,
obtenirDemandesUrgentesUseCase: mockObtenirDemandesUrgentesUseCase,
obtenirMesDemandesUseCase: mockObtenirMesDemandesUseCase,
validerDemandeAideUseCase: mockValiderDemandeAideUseCase,
calculerPrioriteDemandeUseCase: mockCalculerPrioriteDemandeUseCase,
);
});
tearDown(() {
bloc.close();
});
test('état initial est DemandesAideInitial', () {
expect(bloc.state, equals(const DemandesAideInitial()));
});
group('ChargerDemandesAideEvent', () {
final tDemandes = [
_createTestDemandeAide('1', 'Demande 1'),
_createTestDemandeAide('2', 'Demande 2'),
];
blocTest<DemandesAideBloc, DemandesAideState>(
'émet [DemandesAideLoading, DemandesAideLoaded] quand les données sont chargées avec succès',
build: () {
when(mockRechercherDemandesAideUseCase(any))
.thenAnswer((_) async => Right(tDemandes));
return bloc;
},
act: (bloc) => bloc.add(const ChargerDemandesAideEvent()),
expect: () => [
const DemandesAideLoading(),
isA<DemandesAideLoaded>()
.having((state) => state.demandes, 'demandes', tDemandes)
.having((state) => state.demandesFiltrees, 'demandesFiltrees', tDemandes)
.having((state) => state.hasReachedMax, 'hasReachedMax', true)
.having((state) => state.currentPage, 'currentPage', 0)
.having((state) => state.totalElements, 'totalElements', 2),
],
verify: (_) {
verify(mockRechercherDemandesAideUseCase(
RechercherDemandesAideParams(
organisationId: null,
typeAide: null,
statut: null,
demandeurId: null,
urgente: null,
page: 0,
taille: 20,
),
));
},
);
blocTest<DemandesAideBloc, DemandesAideState>(
'émet [DemandesAideLoading, DemandesAideError] quand le chargement échoue',
build: () {
when(mockRechercherDemandesAideUseCase(any))
.thenAnswer((_) async => const Left(ServerFailure('Erreur serveur')));
return bloc;
},
act: (bloc) => bloc.add(const ChargerDemandesAideEvent()),
expect: () => [
const DemandesAideLoading(),
isA<DemandesAideError>()
.having((state) => state.message, 'message', 'Erreur serveur. Veuillez réessayer plus tard.')
.having((state) => state.isNetworkError, 'isNetworkError', false)
.having((state) => state.canRetry, 'canRetry', true),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'émet [DemandesAideLoaded] avec isRefreshing=true quand forceRefresh=false et état déjà chargé',
build: () {
when(mockRechercherDemandesAideUseCase(any))
.thenAnswer((_) async => Right(tDemandes));
return bloc;
},
seed: () => DemandesAideLoaded(
demandes: const [],
demandesFiltrees: const [],
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const ChargerDemandesAideEvent()),
expect: () => [
isA<DemandesAideLoaded>().having((state) => state.isRefreshing, 'isRefreshing', true),
isA<DemandesAideLoaded>()
.having((state) => state.demandes, 'demandes', tDemandes)
.having((state) => state.isRefreshing, 'isRefreshing', false),
],
);
});
group('CreerDemandeAideEvent', () {
final tDemande = _createTestDemandeAide('1', 'Nouvelle demande');
blocTest<DemandesAideBloc, DemandesAideState>(
'émet [DemandesAideLoading, DemandesAideOperationSuccess, DemandesAideLoading, DemandesAideLoaded] quand la création réussit',
build: () {
when(mockCreerDemandeAideUseCase(any))
.thenAnswer((_) async => Right(tDemande));
when(mockRechercherDemandesAideUseCase(any))
.thenAnswer((_) async => Right([tDemande]));
return bloc;
},
act: (bloc) => bloc.add(CreerDemandeAideEvent(demande: tDemande)),
expect: () => [
const DemandesAideLoading(),
isA<DemandesAideOperationSuccess>()
.having((state) => state.message, 'message', 'Demande d\'aide créée avec succès')
.having((state) => state.demande, 'demande', tDemande)
.having((state) => state.operation, 'operation', TypeOperationDemande.creation),
const DemandesAideLoading(),
isA<DemandesAideLoaded>(),
],
verify: (_) {
verify(mockCreerDemandeAideUseCase(CreerDemandeAideParams(demande: tDemande)));
},
);
blocTest<DemandesAideBloc, DemandesAideState>(
'émet [DemandesAideLoading, DemandesAideError] quand la création échoue',
build: () {
when(mockCreerDemandeAideUseCase(any))
.thenAnswer((_) async => const Left(ValidationFailure('Données invalides')));
return bloc;
},
act: (bloc) => bloc.add(CreerDemandeAideEvent(demande: tDemande)),
expect: () => [
const DemandesAideLoading(),
isA<DemandesAideError>()
.having((state) => state.message, 'message', 'Données invalides'),
],
);
});
group('FiltrerDemandesAideEvent', () {
final tDemandes = [
_createTestDemandeAide('1', 'Demande urgente', estUrgente: true),
_createTestDemandeAide('2', 'Demande normale', estUrgente: false),
];
blocTest<DemandesAideBloc, DemandesAideState>(
'filtre les demandes par urgence',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const FiltrerDemandesAideEvent(urgente: true)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesFiltrees.length, 'demandesFiltrees.length', 1)
.having((state) => state.demandesFiltrees.first.estUrgente, 'estUrgente', true)
.having((state) => state.filtres.urgente, 'filtres.urgente', true),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'filtre les demandes par mot-clé',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const FiltrerDemandesAideEvent(motCle: 'urgente')),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesFiltrees.length, 'demandesFiltrees.length', 1)
.having((state) => state.demandesFiltrees.first.titre, 'titre', 'Demande urgente')
.having((state) => state.filtres.motCle, 'filtres.motCle', 'urgente'),
],
);
});
group('TrierDemandesAideEvent', () {
final tDemandes = [
_createTestDemandeAide('1', 'B Demande', dateCreation: DateTime(2023, 1, 2)),
_createTestDemandeAide('2', 'A Demande', dateCreation: DateTime(2023, 1, 1)),
];
blocTest<DemandesAideBloc, DemandesAideState>(
'trie les demandes par titre croissant',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const TrierDemandesAideEvent(
critere: TriDemandes.titre,
croissant: true,
)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesFiltrees.first.titre, 'premier titre', 'A Demande')
.having((state) => state.demandesFiltrees.last.titre, 'dernier titre', 'B Demande')
.having((state) => state.criterieTri, 'criterieTri', TriDemandes.titre)
.having((state) => state.triCroissant, 'triCroissant', true),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'trie les demandes par date décroissant',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const TrierDemandesAideEvent(
critere: TriDemandes.dateCreation,
croissant: false,
)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesFiltrees.first.dateCreation, 'première date', DateTime(2023, 1, 2))
.having((state) => state.demandesFiltrees.last.dateCreation, 'dernière date', DateTime(2023, 1, 1))
.having((state) => state.criterieTri, 'criterieTri', TriDemandes.dateCreation)
.having((state) => state.triCroissant, 'triCroissant', false),
],
);
});
group('SelectionnerDemandeAideEvent', () {
final tDemandes = [
_createTestDemandeAide('1', 'Demande 1'),
_createTestDemandeAide('2', 'Demande 2'),
];
blocTest<DemandesAideBloc, DemandesAideState>(
'sélectionne une demande',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const SelectionnerDemandeAideEvent(
demandeId: '1',
selectionne: true,
)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesSelectionnees['1'], 'demande sélectionnée', true)
.having((state) => state.nombreDemandesSelectionnees, 'nombre sélectionnées', 1),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'désélectionne une demande',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
demandesSelectionnees: const {'1': true},
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const SelectionnerDemandeAideEvent(
demandeId: '1',
selectionne: false,
)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesSelectionnees.containsKey('1'), 'demande désélectionnée', false)
.having((state) => state.nombreDemandesSelectionnees, 'nombre sélectionnées', 0),
],
);
});
group('SelectionnerToutesDemandesAideEvent', () {
final tDemandes = [
_createTestDemandeAide('1', 'Demande 1'),
_createTestDemandeAide('2', 'Demande 2'),
];
blocTest<DemandesAideBloc, DemandesAideState>(
'sélectionne toutes les demandes',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const SelectionnerToutesDemandesAideEvent(selectionne: true)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesSelectionnees.length, 'nombre sélectionnées', 2)
.having((state) => state.toutesDemandesSelectionnees, 'toutes sélectionnées', true),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'désélectionne toutes les demandes',
build: () => bloc,
seed: () => DemandesAideLoaded(
demandes: tDemandes,
demandesFiltrees: tDemandes,
demandesSelectionnees: const {'1': true, '2': true},
lastUpdated: DateTime.now(),
),
act: (bloc) => bloc.add(const SelectionnerToutesDemandesAideEvent(selectionne: false)),
expect: () => [
isA<DemandesAideLoaded>()
.having((state) => state.demandesSelectionnees.isEmpty, 'aucune sélectionnée', true)
.having((state) => state.toutesDemandesSelectionnees, 'toutes désélectionnées', false),
],
);
});
group('ValiderDemandeAideEvent', () {
final tDemande = _createTestDemandeAide('1', 'Demande à valider');
blocTest<DemandesAideBloc, DemandesAideState>(
'émet DemandesAideValidation avec isValid=true quand la validation réussit',
build: () {
when(mockValiderDemandeAideUseCase(any))
.thenAnswer((_) async => const Right(true));
return bloc;
},
act: (bloc) => bloc.add(ValiderDemandeAideEvent(demande: tDemande)),
expect: () => [
isA<DemandesAideValidation>()
.having((state) => state.isValid, 'isValid', true)
.having((state) => state.erreurs.isEmpty, 'erreurs vides', true)
.having((state) => state.demande, 'demande', tDemande),
],
);
blocTest<DemandesAideBloc, DemandesAideState>(
'émet DemandesAideValidation avec erreurs quand la validation échoue',
build: () {
when(mockValiderDemandeAideUseCase(any))
.thenAnswer((_) async => const Left(ValidationFailure('Titre requis')));
return bloc;
},
act: (bloc) => bloc.add(ValiderDemandeAideEvent(demande: tDemande)),
expect: () => [
isA<DemandesAideValidation>()
.having((state) => state.isValid, 'isValid', false)
.having((state) => state.erreurs['general'], 'erreur générale', 'Titre requis')
.having((state) => state.demande, 'demande', tDemande),
],
);
});
});
}
/// Fonction utilitaire pour créer une demande d'aide de test
DemandeAide _createTestDemandeAide(
String id,
String titre, {
bool estUrgente = false,
DateTime? dateCreation,
}) {
return DemandeAide(
id: id,
numeroReference: 'REF-$id',
titre: titre,
description: 'Description de la $titre',
typeAide: TypeAide.aideFinanciereUrgente,
statut: StatutAide.brouillon,
priorite: PrioriteAide.normale,
estUrgente: estUrgente,
dateCreation: dateCreation ?? DateTime.now(),
dateModification: dateCreation ?? DateTime.now(),
organisationId: 'org-1',
demandeurId: 'user-1',
nomDemandeur: 'John Doe',
emailDemandeur: 'john@example.com',
telephoneDemandeur: '+225123456789',
beneficiaires: const [],
evaluations: const [],
commentairesInternes: const [],
historiqueStatuts: const [],
piecesJustificatives: const [],
tags: const [],
metadonnees: const {},
);
}

View File

@@ -0,0 +1,401 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/solidarite/domain/entities/demande_aide.dart';
import 'package:unionflow_mobile_apps/features/solidarite/presentation/widgets/demande_aide_card.dart';
void main() {
group('DemandeAideCard', () {
late DemandeAide testDemande;
setUp(() {
testDemande = DemandeAide(
id: 'demande-123',
numeroReference: 'REF-2024-001',
titre: 'Aide médicale urgente',
description: 'Besoin d\'aide pour frais médicaux d\'urgence suite à un accident',
typeAide: TypeAide.aideFinanciereMedicale,
statut: StatutAide.enAttente,
priorite: PrioriteAide.haute,
estUrgente: true,
montantDemande: 500000.0,
dateCreation: DateTime(2024, 1, 15, 10, 30),
dateModification: DateTime(2024, 1, 15, 14, 45),
organisationId: 'org-1',
demandeurId: 'user-1',
nomDemandeur: 'Marie Kouassi',
emailDemandeur: 'marie@example.com',
telephoneDemandeur: '+225123456789',
beneficiaires: const [
BeneficiaireAide(
prenom: 'Jean',
nom: 'Kouassi',
age: 25,
),
],
evaluations: const [],
commentairesInternes: const [],
historiqueStatuts: const [],
piecesJustificatives: const [],
tags: const ['urgent', 'médical'],
metadonnees: const {},
);
});
Widget createWidgetUnderTest({
DemandeAide? demande,
VoidCallback? onTap,
VoidCallback? onLongPress,
bool isSelected = false,
bool showSelection = false,
}) {
return MaterialApp(
home: Scaffold(
body: DemandeAideCard(
demande: demande ?? testDemande,
onTap: onTap,
onLongPress: onLongPress,
isSelected: isSelected,
showSelection: showSelection,
),
),
);
}
group('affichage des informations de base', () {
testWidgets('affiche le titre de la demande', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('Aide médicale urgente'), findsOneWidget);
});
testWidgets('affiche la description tronquée', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.textContaining('Besoin d\'aide pour frais médicaux'), findsOneWidget);
});
testWidgets('affiche le numéro de référence', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('REF-2024-001'), findsOneWidget);
});
testWidgets('affiche le nom du demandeur', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('Marie Kouassi'), findsOneWidget);
});
testWidgets('affiche le montant demandé formaté', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('500 000 FCFA'), findsOneWidget);
});
testWidgets('affiche la date de création formatée', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('15 jan. 2024'), findsOneWidget);
});
});
group('affichage des badges et indicateurs', () {
testWidgets('affiche le badge urgent pour une demande urgente', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('URGENT'), findsOneWidget);
expect(find.byIcon(Icons.priority_high), findsOneWidget);
});
testWidgets('n\'affiche pas le badge urgent pour une demande normale', (WidgetTester tester) async {
// arrange
final demandeNormale = testDemande.copyWith(estUrgente: false);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeNormale));
// assert
expect(find.text('URGENT'), findsNothing);
expect(find.byIcon(Icons.priority_high), findsNothing);
});
testWidgets('affiche le badge de statut avec la bonne couleur', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('En attente'), findsOneWidget);
// Vérifier la couleur du badge (orange pour "en attente")
final badgeContainer = tester.widget<Container>(
find.ancestor(
of: find.text('En attente'),
matching: find.byType(Container),
).first,
);
expect(badgeContainer.decoration, isA<BoxDecoration>());
});
testWidgets('affiche le badge de priorité', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('Haute'), findsOneWidget);
});
testWidgets('affiche le badge de type d\'aide', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('Aide médicale'), findsOneWidget);
});
});
group('affichage des informations supplémentaires', () {
testWidgets('affiche le nombre de bénéficiaires', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('1 bénéficiaire'), findsOneWidget);
expect(find.byIcon(Icons.people), findsOneWidget);
});
testWidgets('affiche le pluriel pour plusieurs bénéficiaires', (WidgetTester tester) async {
// arrange
final demandeAvecPlusieurs = testDemande.copyWith(
beneficiaires: const [
BeneficiaireAide(prenom: 'Jean', nom: 'Kouassi', age: 25),
BeneficiaireAide(prenom: 'Marie', nom: 'Kouassi', age: 23),
],
);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeAvecPlusieurs));
// assert
expect(find.text('2 bénéficiaires'), findsOneWidget);
});
testWidgets('affiche les tags', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.text('urgent'), findsOneWidget);
expect(find.text('médical'), findsOneWidget);
});
testWidgets('affiche l\'indicateur de progression', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.byType(LinearProgressIndicator), findsOneWidget);
});
});
group('interactions utilisateur', () {
testWidgets('appelle onTap quand la carte est tapée', (WidgetTester tester) async {
// arrange
bool tapCalled = false;
void onTap() => tapCalled = true;
await tester.pumpWidget(createWidgetUnderTest(onTap: onTap));
// act
await tester.tap(find.byType(DemandeAideCard));
await tester.pumpAndSettle();
// assert
expect(tapCalled, true);
});
testWidgets('appelle onLongPress quand la carte est pressée longuement', (WidgetTester tester) async {
// arrange
bool longPressCalled = false;
void onLongPress() => longPressCalled = true;
await tester.pumpWidget(createWidgetUnderTest(onLongPress: onLongPress));
// act
await tester.longPress(find.byType(DemandeAideCard));
await tester.pumpAndSettle();
// assert
expect(longPressCalled, true);
});
testWidgets('affiche l\'état sélectionné quand isSelected=true', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest(
isSelected: true,
showSelection: true,
));
// assert
expect(find.byIcon(Icons.check_circle), findsOneWidget);
// Vérifier que la carte a une bordure différente quand sélectionnée
final card = tester.widget<Card>(find.byType(Card));
expect(card.elevation, greaterThan(1.0));
});
testWidgets('affiche l\'état non sélectionné quand isSelected=false', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest(
isSelected: false,
showSelection: true,
));
// assert
expect(find.byIcon(Icons.radio_button_unchecked), findsOneWidget);
});
testWidgets('n\'affiche pas les indicateurs de sélection quand showSelection=false', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest(
isSelected: true,
showSelection: false,
));
// assert
expect(find.byIcon(Icons.check_circle), findsNothing);
expect(find.byIcon(Icons.radio_button_unchecked), findsNothing);
});
});
group('gestion des cas limites', () {
testWidgets('gère une demande sans montant', (WidgetTester tester) async {
// arrange
final demandeSansMontant = testDemande.copyWith(montantDemande: null);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeSansMontant));
// assert
expect(find.text('Montant non spécifié'), findsOneWidget);
});
testWidgets('gère une demande sans bénéficiaires', (WidgetTester tester) async {
// arrange
final demandeSansBeneficiaires = testDemande.copyWith(beneficiaires: const []);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeSansBeneficiaires));
// assert
expect(find.text('Aucun bénéficiaire'), findsOneWidget);
});
testWidgets('gère une demande sans tags', (WidgetTester tester) async {
// arrange
final demandeSansTags = testDemande.copyWith(tags: const []);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeSansTags));
// assert
// Les tags ne devraient pas être affichés
expect(find.text('urgent'), findsNothing);
expect(find.text('médical'), findsNothing);
});
testWidgets('gère une description très longue', (WidgetTester tester) async {
// arrange
final descriptionLongue = 'Ceci est une description très longue qui devrait être tronquée ' * 10;
final demandeDescriptionLongue = testDemande.copyWith(description: descriptionLongue);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeDescriptionLongue));
// assert
// Vérifier que la description est tronquée (contient "...")
final descriptionWidget = find.byType(Text).evaluate()
.where((element) => (element.widget as Text).data?.contains('...') == true)
.isNotEmpty;
expect(descriptionWidget, true);
});
testWidgets('gère un titre très long', (WidgetTester tester) async {
// arrange
final titreLong = 'Ceci est un titre très long qui devrait être géré correctement ' * 5;
final demandeTitreLong = testDemande.copyWith(titre: titreLong);
// act
await tester.pumpWidget(createWidgetUnderTest(demande: demandeTitreLong));
// assert
// Le widget ne devrait pas déborder
expect(tester.takeException(), isNull);
});
});
group('accessibilité', () {
testWidgets('a des labels d\'accessibilité appropriés', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
expect(find.bySemanticsLabel('Demande d\'aide: Aide médicale urgente'), findsOneWidget);
});
testWidgets('supporte la navigation au clavier', (WidgetTester tester) async {
// arrange & act
await tester.pumpWidget(createWidgetUnderTest());
// assert
final inkWell = find.byType(InkWell);
expect(inkWell, findsOneWidget);
final inkWellWidget = tester.widget<InkWell>(inkWell);
expect(inkWellWidget.focusNode, isNotNull);
});
});
group('performance', () {
testWidgets('se construit rapidement avec de nombreuses demandes', (WidgetTester tester) async {
// arrange
final stopwatch = Stopwatch()..start();
// act
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => DemandeAideCard(
demande: testDemande.copyWith(
id: 'demande-$index',
titre: 'Demande $index',
),
),
),
),
));
stopwatch.stop();
// assert
expect(stopwatch.elapsedMilliseconds, lessThan(1000)); // Moins d'1 seconde
});
});
});
}

View File

@@ -0,0 +1,120 @@
{
"id": "demande-123",
"numeroReference": "REF-2024-001",
"titre": "Aide médicale urgente",
"description": "Besoin d'aide pour frais médicaux d'urgence suite à un accident",
"typeAide": "AIDE_FINANCIERE_MEDICALE",
"statut": "EN_ATTENTE",
"priorite": "HAUTE",
"estUrgente": true,
"montantDemande": 500000.0,
"montantApprouve": null,
"justification": "Accident de moto nécessitant une intervention chirurgicale urgente",
"dateCreation": "2024-01-15T10:30:00Z",
"dateModification": "2024-01-15T14:45:00Z",
"dateLimite": "2024-01-20T23:59:59Z",
"dateTraitement": null,
"organisationId": "org-1",
"demandeurId": "user-1",
"nomDemandeur": "Marie Kouassi",
"emailDemandeur": "marie@example.com",
"telephoneDemandeur": "+225123456789",
"beneficiaires": [
{
"prenom": "Jean",
"nom": "Kouassi",
"age": 25
}
],
"contactUrgence": {
"prenom": "Paul",
"nom": "Kouassi",
"telephone": "+225987654321",
"email": "paul@example.com",
"relation": "Frère"
},
"localisation": {
"adresse": "123 Rue de la Paix",
"ville": "Abidjan",
"codePostal": "00225",
"pays": "Côte d'Ivoire",
"latitude": 5.3600,
"longitude": -4.0083
},
"evaluations": [
{
"id": "eval-1",
"demandeId": "demande-123",
"evaluateurId": "evaluateur-1",
"nomEvaluateur": "Dr. Koffi",
"typeEvaluateur": "PROFESSIONNEL_SANTE",
"dateEvaluation": "2024-01-16T09:00:00Z",
"noteGlobale": 4.5,
"criteres": {
"urgence": 5.0,
"legitimite": 4.0,
"faisabilite": 4.5,
"impact": 4.5
},
"decision": "APPROUVE",
"commentaires": "Cas médical urgent nécessitant une intervention rapide",
"recommandations": "Approuver rapidement pour éviter complications",
"piecesJustificativesValidees": true,
"signalements": [],
"metadonnees": {}
}
],
"commentairesInternes": [
{
"id": "comment-1",
"auteurId": "admin-1",
"nomAuteur": "Admin System",
"contenu": "Demande créée automatiquement",
"dateCreation": "2024-01-15T10:30:00Z",
"estPrive": true
}
],
"historiqueStatuts": [
{
"ancienStatut": null,
"nouveauStatut": "BROUILLON",
"dateChangement": "2024-01-15T10:30:00Z",
"utilisateurId": "user-1",
"commentaire": "Création de la demande"
},
{
"ancienStatut": "BROUILLON",
"nouveauStatut": "EN_ATTENTE",
"dateChangement": "2024-01-15T14:45:00Z",
"utilisateurId": "user-1",
"commentaire": "Soumission de la demande"
}
],
"piecesJustificatives": [
{
"id": "piece-1",
"nomFichier": "certificat_medical.pdf",
"typeDocument": {
"code": "CERTIFICAT_MEDICAL",
"libelle": "Certificat médical",
"description": "Document médical attestant de l'état de santé"
},
"tailleFichier": 1024000,
"urlFichier": "/api/files/piece-1",
"dateUpload": "2024-01-15T11:00:00Z",
"uploadePar": "user-1",
"estValide": true,
"commentaires": "Certificat médical confirmant la nécessité de l'intervention"
}
],
"tags": ["urgent", "médical", "accident"],
"metadonnees": {
"source": "mobile_app",
"version": "1.0.0",
"geolocalisation": {
"latitude": 5.3600,
"longitude": -4.0083,
"precision": 10.0
}
}
}

View File

@@ -0,0 +1,74 @@
{
"content": [
{
"id": "demande-123",
"numeroReference": "REF-2024-001",
"titre": "Aide médicale urgente",
"description": "Besoin d'aide pour frais médicaux d'urgence suite à un accident",
"typeAide": "AIDE_FINANCIERE_MEDICALE",
"statut": "EN_ATTENTE",
"priorite": "HAUTE",
"estUrgente": true,
"montantDemande": 500000.0,
"dateCreation": "2024-01-15T10:30:00Z",
"dateModification": "2024-01-15T14:45:00Z",
"organisationId": "org-1",
"demandeurId": "user-1",
"nomDemandeur": "Marie Kouassi",
"emailDemandeur": "marie@example.com",
"telephoneDemandeur": "+225123456789",
"beneficiaires": [],
"evaluations": [],
"commentairesInternes": [],
"historiqueStatuts": [],
"piecesJustificatives": [],
"tags": ["urgent", "médical"],
"metadonnees": {}
},
{
"id": "demande-124",
"numeroReference": "REF-2024-002",
"titre": "Aide alimentaire famille",
"description": "Besoin d'aide alimentaire pour famille nombreuse",
"typeAide": "AIDE_ALIMENTAIRE",
"statut": "APPROUVE",
"priorite": "NORMALE",
"estUrgente": false,
"montantDemande": 150000.0,
"dateCreation": "2024-01-14T08:00:00Z",
"dateModification": "2024-01-16T16:30:00Z",
"organisationId": "org-1",
"demandeurId": "user-2",
"nomDemandeur": "Jean Koffi",
"emailDemandeur": "jean@example.com",
"telephoneDemandeur": "+225987654321",
"beneficiaires": [
{
"prenom": "Marie",
"nom": "Koffi",
"age": 30
},
{
"prenom": "Paul",
"nom": "Koffi",
"age": 8
}
],
"evaluations": [],
"commentairesInternes": [],
"historiqueStatuts": [],
"piecesJustificatives": [],
"tags": ["famille", "alimentaire"],
"metadonnees": {}
}
],
"page": {
"number": 0,
"size": 20,
"totalElements": 2,
"totalPages": 1
},
"first": true,
"last": true,
"empty": false
}

View File

@@ -0,0 +1,31 @@
[
{
"id": "demande-urgent-1",
"numeroReference": "REF-URG-001",
"titre": "Urgence médicale - Accident",
"description": "Accident grave nécessitant intervention chirurgicale immédiate",
"typeAide": "AIDE_FINANCIERE_MEDICALE",
"statut": "EN_ATTENTE",
"priorite": "CRITIQUE",
"estUrgente": true,
"montantDemande": 1000000.0,
"dateCreation": "2024-01-16T20:00:00Z",
"dateModification": "2024-01-16T20:00:00Z",
"dateLimite": "2024-01-17T08:00:00Z",
"organisationId": "org-1",
"demandeurId": "user-urgent-1",
"nomDemandeur": "Urgence Patient",
"emailDemandeur": "urgent@example.com",
"telephoneDemandeur": "+225111222333",
"beneficiaires": [],
"evaluations": [],
"commentairesInternes": [],
"historiqueStatuts": [],
"piecesJustificatives": [],
"tags": ["urgent", "critique", "médical"],
"metadonnees": {
"urgenceLevel": "CRITIQUE",
"timeRemaining": "12h"
}
}
]

View File

@@ -0,0 +1,13 @@
import 'dart:io';
/// Utilitaire pour lire les fichiers de fixtures de test
///
/// Cette classe fournit une méthode simple pour charger
/// les données de test depuis des fichiers JSON.
String fixture(String name) {
final file = File('test/fixtures/$name');
if (!file.existsSync()) {
throw Exception('Fixture file not found: test/fixtures/$name');
}
return file.readAsStringSync();
}

View File

@@ -0,0 +1,90 @@
{
"content": [
{
"id": "ma-demande-1",
"numeroReference": "REF-ME-001",
"titre": "Ma demande d'aide logement",
"description": "Demande d'aide pour le loyer suite à perte d'emploi",
"typeAide": "AIDE_FINANCIERE_LOGEMENT",
"statut": "EN_COURS",
"priorite": "HAUTE",
"estUrgente": true,
"montantDemande": 300000.0,
"dateCreation": "2024-01-10T14:00:00Z",
"dateModification": "2024-01-15T10:00:00Z",
"organisationId": "org-1",
"demandeurId": "user-1",
"nomDemandeur": "Mon Nom",
"emailDemandeur": "mon@example.com",
"telephoneDemandeur": "+225123456789",
"beneficiaires": [
{
"prenom": "Mon Enfant",
"nom": "Nom",
"age": 5
}
],
"evaluations": [
{
"id": "eval-me-1",
"demandeId": "ma-demande-1",
"evaluateurId": "eval-1",
"nomEvaluateur": "Evaluateur Social",
"typeEvaluateur": "TRAVAILLEUR_SOCIAL",
"dateEvaluation": "2024-01-12T09:00:00Z",
"noteGlobale": 4.2,
"decision": "EN_COURS",
"commentaires": "Situation justifiée, vérifications en cours"
}
],
"commentairesInternes": [],
"historiqueStatuts": [
{
"ancienStatut": null,
"nouveauStatut": "BROUILLON",
"dateChangement": "2024-01-10T14:00:00Z",
"utilisateurId": "user-1",
"commentaire": "Création"
},
{
"ancienStatut": "BROUILLON",
"nouveauStatut": "EN_ATTENTE",
"dateChangement": "2024-01-10T15:00:00Z",
"utilisateurId": "user-1",
"commentaire": "Soumission"
},
{
"ancienStatut": "EN_ATTENTE",
"nouveauStatut": "EN_COURS",
"dateChangement": "2024-01-12T09:00:00Z",
"utilisateurId": "eval-1",
"commentaire": "Prise en charge"
}
],
"piecesJustificatives": [
{
"id": "piece-me-1",
"nomFichier": "attestation_pole_emploi.pdf",
"typeDocument": {
"code": "ATTESTATION_CHOMAGE",
"libelle": "Attestation Pôle Emploi"
},
"tailleFichier": 512000,
"dateUpload": "2024-01-10T14:30:00Z",
"estValide": true
}
],
"tags": ["logement", "urgent", "chomage"],
"metadonnees": {}
}
],
"page": {
"number": 0,
"size": 20,
"totalElements": 1,
"totalPages": 1
},
"first": true,
"last": true,
"empty": false
}

View File

@@ -0,0 +1,7 @@
import 'package:flutter_test/flutter_test.dart';
void main() {
test('test simple', () {
expect(1 + 1, 2);
});
}

View File

@@ -0,0 +1,315 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Configuration globale pour les tests
///
/// Cette classe configure l'environnement de test pour
/// garantir des conditions cohérentes et reproductibles.
class TestConfig {
static bool _initialized = false;
/// Initialise l'environnement de test
static Future<void> initialize() async {
if (_initialized) return;
TestWidgetsFlutterBinding.ensureInitialized();
// Configuration des SharedPreferences pour les tests
SharedPreferences.setMockInitialValues({});
// Configuration des canaux de méthodes pour les tests
_setupMethodChannels();
// Configuration des polices pour les tests de widgets
_setupFonts();
_initialized = true;
}
/// Configure les canaux de méthodes mockés
static void _setupMethodChannels() {
// Canal pour les permissions
const MethodChannel('flutter.baseflow.com/permissions/methods')
.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'checkPermissionStatus':
return 1; // PermissionStatus.granted
case 'requestPermissions':
return {0: 1}; // Permission granted
default:
return null;
}
});
// Canal pour la géolocalisation
const MethodChannel('flutter.baseflow.com/geolocator')
.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'getCurrentPosition':
return {
'latitude': 5.3600,
'longitude': -4.0083,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'accuracy': 10.0,
'altitude': 0.0,
'heading': 0.0,
'speed': 0.0,
'speedAccuracy': 0.0,
};
case 'getLocationAccuracy':
return 1; // LocationAccuracy.best
default:
return null;
}
});
// Canal pour le partage de fichiers
const MethodChannel('plugins.flutter.io/share')
.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'share':
return null; // Succès silencieux
default:
return null;
}
});
// Canal pour l'ouverture d'URLs
const MethodChannel('plugins.flutter.io/url_launcher')
.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'launch':
return true; // URL ouverte avec succès
case 'canLaunch':
return true; // URL peut être ouverte
default:
return null;
}
});
// Canal pour la sélection de fichiers
const MethodChannel('miguelruivo.flutter.plugins.filepicker')
.setMockMethodCallHandler((MethodCall methodCall) async {
switch (methodCall.method) {
case 'any':
return {
'files': [
{
'name': 'test_document.pdf',
'path': '/mock/path/test_document.pdf',
'size': 1024000,
'bytes': null,
}
]
};
default:
return null;
}
});
}
/// Configure les polices pour les tests de widgets
static void _setupFonts() {
// Chargement des polices Material Design
final binding = TestWidgetsFlutterBinding.ensureInitialized();
binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
const StandardMethodCodec(),
(dynamic message) async {
if (message is Map && message['method'] == 'SystemChrome.setApplicationSwitcherDescription') {
return null;
}
return null;
},
);
}
/// Nettoie l'environnement de test après chaque test
static Future<void> cleanup() async {
// Réinitialiser les SharedPreferences
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
// Nettoyer les canaux de méthodes
_clearMethodChannels();
}
/// Nettoie les canaux de méthodes
static void _clearMethodChannels() {
const MethodChannel('flutter.baseflow.com/permissions/methods')
.setMockMethodCallHandler(null);
const MethodChannel('flutter.baseflow.com/geolocator')
.setMockMethodCallHandler(null);
const MethodChannel('plugins.flutter.io/share')
.setMockMethodCallHandler(null);
const MethodChannel('plugins.flutter.io/url_launcher')
.setMockMethodCallHandler(null);
const MethodChannel('miguelruivo.flutter.plugins.filepicker')
.setMockMethodCallHandler(null);
}
}
/// Classe utilitaire pour les données de test
class TestData {
/// Données de test pour une demande d'aide
static Map<String, dynamic> get demandeAideJson => {
'id': 'demande-test-123',
'numeroReference': 'REF-TEST-001',
'titre': 'Test Aide Médicale',
'description': 'Description de test pour aide médicale',
'typeAide': 'AIDE_FINANCIERE_MEDICALE',
'statut': 'BROUILLON',
'priorite': 'NORMALE',
'estUrgente': false,
'montantDemande': 100000.0,
'dateCreation': '2024-01-15T10:00:00Z',
'dateModification': '2024-01-15T10:00:00Z',
'organisationId': 'org-test',
'demandeurId': 'user-test',
'nomDemandeur': 'Test User',
'emailDemandeur': 'test@example.com',
'telephoneDemandeur': '+225123456789',
'beneficiaires': [],
'evaluations': [],
'commentairesInternes': [],
'historiqueStatuts': [],
'piecesJustificatives': [],
'tags': ['test'],
'metadonnees': {},
};
/// Données de test pour une proposition d'aide
static Map<String, dynamic> get propositionAideJson => {
'id': 'proposition-test-123',
'titre': 'Test Proposition Aide',
'description': 'Description de test pour proposition',
'typeAide': 'AIDE_FINANCIERE_MEDICALE',
'statut': 'ACTIVE',
'montantMaximum': 200000.0,
'dateCreation': '2024-01-15T10:00:00Z',
'organisationId': 'org-test',
'proposantId': 'proposant-test',
'nomProposant': 'Test Proposant',
'emailProposant': 'proposant@example.com',
'telephoneProposant': '+225987654321',
'capacites': [],
'disponibilites': [],
'criteres': [],
'statistiques': {},
'metadonnees': {},
};
/// Données de test pour une évaluation
static Map<String, dynamic> get evaluationAideJson => {
'id': 'evaluation-test-123',
'demandeId': 'demande-test-123',
'evaluateurId': 'evaluateur-test',
'nomEvaluateur': 'Test Evaluateur',
'typeEvaluateur': 'ADMINISTRATEUR',
'dateEvaluation': '2024-01-16T10:00:00Z',
'noteGlobale': 4.0,
'criteres': {
'urgence': 4.0,
'legitimite': 4.0,
'faisabilite': 4.0,
'impact': 4.0,
},
'decision': 'APPROUVE',
'commentaires': 'Évaluation de test',
'recommandations': 'Recommandations de test',
'piecesJustificativesValidees': true,
'signalements': [],
'metadonnees': {},
};
}
/// Classe utilitaire pour les assertions personnalisées
class TestAssertions {
/// Vérifie qu'une demande d'aide a les propriétés attendues
static void assertDemandeAideValid(dynamic demande) {
expect(demande.id, isNotEmpty);
expect(demande.titre, isNotEmpty);
expect(demande.description, isNotEmpty);
expect(demande.typeAide, isNotNull);
expect(demande.statut, isNotNull);
expect(demande.priorite, isNotNull);
expect(demande.dateCreation, isNotNull);
expect(demande.organisationId, isNotEmpty);
expect(demande.demandeurId, isNotEmpty);
expect(demande.nomDemandeur, isNotEmpty);
expect(demande.emailDemandeur, isNotEmpty);
}
/// Vérifie qu'une proposition d'aide a les propriétés attendues
static void assertPropositionAideValid(dynamic proposition) {
expect(proposition.id, isNotEmpty);
expect(proposition.titre, isNotEmpty);
expect(proposition.description, isNotEmpty);
expect(proposition.typeAide, isNotNull);
expect(proposition.statut, isNotNull);
expect(proposition.dateCreation, isNotNull);
expect(proposition.organisationId, isNotEmpty);
expect(proposition.proposantId, isNotEmpty);
expect(proposition.nomProposant, isNotEmpty);
}
/// Vérifie qu'une évaluation a les propriétés attendues
static void assertEvaluationValid(dynamic evaluation) {
expect(evaluation.id, isNotEmpty);
expect(evaluation.demandeId, isNotEmpty);
expect(evaluation.evaluateurId, isNotEmpty);
expect(evaluation.nomEvaluateur, isNotEmpty);
expect(evaluation.typeEvaluateur, isNotNull);
expect(evaluation.dateEvaluation, isNotNull);
expect(evaluation.noteGlobale, greaterThanOrEqualTo(0.0));
expect(evaluation.noteGlobale, lessThanOrEqualTo(5.0));
expect(evaluation.decision, isNotNull);
}
}
/// Classe utilitaire pour les mocks
class TestMocks {
/// Crée un mock de réponse HTTP réussie
static Map<String, dynamic> createSuccessResponse(dynamic data) {
return {
'success': true,
'data': data,
'message': 'Opération réussie',
'timestamp': DateTime.now().toIso8601String(),
};
}
/// Crée un mock de réponse HTTP d'erreur
static Map<String, dynamic> createErrorResponse(String message, {int code = 500}) {
return {
'success': false,
'error': {
'code': code,
'message': message,
'details': null,
},
'timestamp': DateTime.now().toIso8601String(),
};
}
/// Crée un mock de réponse paginée
static Map<String, dynamic> createPagedResponse(List<dynamic> content, {
int page = 0,
int size = 20,
int totalElements = 0,
int totalPages = 0,
}) {
return {
'content': content,
'page': {
'number': page,
'size': size,
'totalElements': totalElements ?? content.length,
'totalPages': totalPages ?? ((totalElements ?? content.length) / size).ceil(),
},
'first': page == 0,
'last': page >= (totalPages - 1),
'empty': content.isEmpty,
};
}
}