Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
/// Tests unitaires pour CreateContribution use case
library create_contribution_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/create_contribution.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
@GenerateMocks([IContributionRepository])
import 'create_contribution_test.mocks.dart';
void main() {
late CreateContribution useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = CreateContribution(mockRepository);
});
group('CreateContribution Use Case', () {
final tNewContribution = ContributionModel(
membreId: 'membre1',
montant: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.nonPayee,
);
final tCreatedContribution = ContributionModel(
id: 'cont123',
membreId: 'membre1',
membreNom: 'Dupont',
membrePrenom: 'Jean',
montant: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.nonPayee,
);
test('should create contribution successfully', () async {
// Arrange
when(mockRepository.createCotisation(tNewContribution))
.thenAnswer((_) async => tCreatedContribution);
// Act
final result = await useCase(tNewContribution);
// Assert
expect(result, equals(tCreatedContribution));
expect(result.id, isNotNull);
expect(result.id, equals('cont123'));
expect(result.montant, equals(5000.0));
verify(mockRepository.createCotisation(tNewContribution));
verifyNoMoreInteractions(mockRepository);
});
test('should create monthly contribution', () async {
// Arrange
final monthlyContribution = ContributionModel(
membreId: 'membre1',
montant: 2000.0,
dateEcheance: DateTime(2024, 1, 31),
annee: 2024,
mois: 1,
type: ContributionType.mensuelle,
statut: ContributionStatus.nonPayee,
);
final createdMonthly = ContributionModel(
id: 'cont456',
membreId: 'membre1',
montant: 2000.0,
dateEcheance: DateTime(2024, 1, 31),
annee: 2024,
mois: 1,
type: ContributionType.mensuelle,
statut: ContributionStatus.nonPayee,
);
when(mockRepository.createCotisation(monthlyContribution))
.thenAnswer((_) async => createdMonthly);
// Act
final result = await useCase(monthlyContribution);
// Assert
expect(result.type, equals(ContributionType.mensuelle));
expect(result.mois, equals(1));
});
test('should create contribution with description', () async {
// Arrange
final contributionWithDesc = ContributionModel(
membreId: 'membre1',
montant: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
annee: 2024,
type: ContributionType.exceptionnelle,
statut: ContributionStatus.nonPayee,
description: 'Cotisation exceptionnelle pour projet spécial',
);
when(mockRepository.createCotisation(any))
.thenAnswer((_) async => contributionWithDesc.copyWith(id: 'cont789'));
// Act
final result = await useCase(contributionWithDesc);
// Assert
expect(result.description, isNotNull);
expect(result.type, equals(ContributionType.exceptionnelle));
});
test('should throw exception when creation fails', () async {
// Arrange
when(mockRepository.createCotisation(any))
.thenThrow(Exception('Erreur de validation'));
// Act & Assert
expect(() => useCase(tNewContribution), throwsException);
});
});
}

View File

@@ -0,0 +1,335 @@
// Mocks generated by Mockito 5.4.4 from annotations
// in unionflow_mobile_apps/test/features/contributions/domain/usecases/create_contribution_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'package:mockito/mockito.dart' as _i1;
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart'
as _i3;
import 'package:unionflow_mobile_apps/features/contributions/data/repositories/contribution_repository.dart'
as _i2;
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart'
as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeContributionPageResult_0 extends _i1.SmartFake
implements _i2.ContributionPageResult {
_FakeContributionPageResult_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeContributionModel_1 extends _i1.SmartFake
implements _i3.ContributionModel {
_FakeContributionModel_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeWavePaiementInitResult_2 extends _i1.SmartFake
implements _i2.WavePaiementInitResult {
_FakeWavePaiementInitResult_2(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [IContributionRepository].
///
/// See the documentation for Mockito's code generation for more information.
class MockIContributionRepository extends _i1.Mock
implements _i4.IContributionRepository {
MockIContributionRepository() {
_i1.throwOnMissingStub(this);
}
@override
_i5.Future<_i2.ContributionPageResult> getMesCotisations({
int? page = 0,
int? size = 50,
}) =>
(super.noSuchMethod(
Invocation.method(
#getMesCotisations,
[],
{
#page: page,
#size: size,
},
),
returnValue: _i5.Future<_i2.ContributionPageResult>.value(
_FakeContributionPageResult_0(
this,
Invocation.method(
#getMesCotisations,
[],
{
#page: page,
#size: size,
},
),
)),
) as _i5.Future<_i2.ContributionPageResult>);
@override
_i5.Future<_i3.ContributionModel> getCotisationById(String? id) =>
(super.noSuchMethod(
Invocation.method(
#getCotisationById,
[id],
),
returnValue:
_i5.Future<_i3.ContributionModel>.value(_FakeContributionModel_1(
this,
Invocation.method(
#getCotisationById,
[id],
),
)),
) as _i5.Future<_i3.ContributionModel>);
@override
_i5.Future<_i3.ContributionModel> createCotisation(
_i3.ContributionModel? contribution) =>
(super.noSuchMethod(
Invocation.method(
#createCotisation,
[contribution],
),
returnValue:
_i5.Future<_i3.ContributionModel>.value(_FakeContributionModel_1(
this,
Invocation.method(
#createCotisation,
[contribution],
),
)),
) as _i5.Future<_i3.ContributionModel>);
@override
_i5.Future<_i3.ContributionModel> updateCotisation(
String? id,
_i3.ContributionModel? contribution,
) =>
(super.noSuchMethod(
Invocation.method(
#updateCotisation,
[
id,
contribution,
],
),
returnValue:
_i5.Future<_i3.ContributionModel>.value(_FakeContributionModel_1(
this,
Invocation.method(
#updateCotisation,
[
id,
contribution,
],
),
)),
) as _i5.Future<_i3.ContributionModel>);
@override
_i5.Future<void> deleteCotisation(String? id) => (super.noSuchMethod(
Invocation.method(
#deleteCotisation,
[id],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<_i3.ContributionModel> enregistrerPaiement(
String? cotisationId, {
required double? montant,
required DateTime? datePaiement,
required String? methodePaiement,
String? numeroPaiement,
String? referencePaiement,
}) =>
(super.noSuchMethod(
Invocation.method(
#enregistrerPaiement,
[cotisationId],
{
#montant: montant,
#datePaiement: datePaiement,
#methodePaiement: methodePaiement,
#numeroPaiement: numeroPaiement,
#referencePaiement: referencePaiement,
},
),
returnValue:
_i5.Future<_i3.ContributionModel>.value(_FakeContributionModel_1(
this,
Invocation.method(
#enregistrerPaiement,
[cotisationId],
{
#montant: montant,
#datePaiement: datePaiement,
#methodePaiement: methodePaiement,
#numeroPaiement: numeroPaiement,
#referencePaiement: referencePaiement,
},
),
)),
) as _i5.Future<_i3.ContributionModel>);
@override
_i5.Future<_i2.WavePaiementInitResult> initierPaiementEnLigne({
required String? cotisationId,
required String? methodePaiement,
required String? numeroTelephone,
}) =>
(super.noSuchMethod(
Invocation.method(
#initierPaiementEnLigne,
[],
{
#cotisationId: cotisationId,
#methodePaiement: methodePaiement,
#numeroTelephone: numeroTelephone,
},
),
returnValue: _i5.Future<_i2.WavePaiementInitResult>.value(
_FakeWavePaiementInitResult_2(
this,
Invocation.method(
#initierPaiementEnLigne,
[],
{
#cotisationId: cotisationId,
#methodePaiement: methodePaiement,
#numeroTelephone: numeroTelephone,
},
),
)),
) as _i5.Future<_i2.WavePaiementInitResult>);
@override
_i5.Future<Map<String, dynamic>?> getMesCotisationsSynthese() =>
(super.noSuchMethod(
Invocation.method(
#getMesCotisationsSynthese,
[],
),
returnValue: _i5.Future<Map<String, dynamic>?>.value(),
) as _i5.Future<Map<String, dynamic>?>);
@override
_i5.Future<Map<String, dynamic>> getStatistiques() => (super.noSuchMethod(
Invocation.method(
#getStatistiques,
[],
),
returnValue:
_i5.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
) as _i5.Future<Map<String, dynamic>>);
@override
_i5.Future<_i2.ContributionPageResult> getMesCotisationsEnAttente() =>
(super.noSuchMethod(
Invocation.method(
#getMesCotisationsEnAttente,
[],
),
returnValue: _i5.Future<_i2.ContributionPageResult>.value(
_FakeContributionPageResult_0(
this,
Invocation.method(
#getMesCotisationsEnAttente,
[],
),
)),
) as _i5.Future<_i2.ContributionPageResult>);
@override
_i5.Future<_i2.ContributionPageResult> getCotisations({
int? page = 0,
int? size = 20,
String? membreId,
String? statut,
String? type,
int? annee,
}) =>
(super.noSuchMethod(
Invocation.method(
#getCotisations,
[],
{
#page: page,
#size: size,
#membreId: membreId,
#statut: statut,
#type: type,
#annee: annee,
},
),
returnValue: _i5.Future<_i2.ContributionPageResult>.value(
_FakeContributionPageResult_0(
this,
Invocation.method(
#getCotisations,
[],
{
#page: page,
#size: size,
#membreId: membreId,
#statut: statut,
#type: type,
#annee: annee,
},
),
)),
) as _i5.Future<_i2.ContributionPageResult>);
@override
_i5.Future<void> envoyerRappel(String? cotisationId) => (super.noSuchMethod(
Invocation.method(
#envoyerRappel,
[cotisationId],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<int> genererCotisationsAnnuelles(int? annee) =>
(super.noSuchMethod(
Invocation.method(
#genererCotisationsAnnuelles,
[annee],
),
returnValue: _i5.Future<int>.value(0),
) as _i5.Future<int>);
}

View File

@@ -0,0 +1,66 @@
/// Tests unitaires pour DeleteContribution use case
library delete_contribution_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/delete_contribution.dart';
@GenerateMocks([IContributionRepository])
import 'delete_contribution_test.mocks.dart';
void main() {
late DeleteContribution useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = DeleteContribution(mockRepository);
});
group('DeleteContribution Use Case', () {
const tContributionId = 'cont123';
test('should delete contribution successfully', () async {
// Arrange
when(mockRepository.deleteCotisation(tContributionId))
.thenAnswer((_) async => Future.value());
// Act
await useCase(tContributionId);
// Assert
verify(mockRepository.deleteCotisation(tContributionId));
verifyNoMoreInteractions(mockRepository);
});
test('should throw exception when contribution not found', () async {
// Arrange
when(mockRepository.deleteCotisation(any))
.thenThrow(Exception('Contribution non trouvée'));
// Act & Assert
expect(() => useCase('nonexistent'), throwsA(isA<Exception>()));
verify(mockRepository.deleteCotisation('nonexistent'));
});
test('should throw exception when contribution is already paid', () async {
// Arrange
when(mockRepository.deleteCotisation(any))
.thenThrow(Exception('Impossible de supprimer une cotisation payée'));
// Act & Assert
expect(() => useCase(tContributionId), throwsA(isA<Exception>()));
});
test('should throw exception when deletion fails', () async {
// Arrange
when(mockRepository.deleteCotisation(any))
.thenThrow(Exception('Erreur de suppression'));
// Act & Assert
expect(() => useCase(tContributionId), throwsException);
});
});
}

View File

@@ -0,0 +1,103 @@
/// Tests unitaires pour GetContributionById use case
library get_contribution_by_id_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_by_id.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
@GenerateMocks([IContributionRepository])
import 'get_contribution_by_id_test.mocks.dart';
void main() {
late GetContributionById useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = GetContributionById(mockRepository);
});
group('GetContributionById Use Case', () {
const tContributionId = 'cont123';
final tContribution = ContributionModel(
id: tContributionId,
membreId: 'membre1',
membreNom: 'Dupont',
membrePrenom: 'Jean',
montant: 5000.0,
montantPaye: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
datePaiement: DateTime(2024, 11, 15),
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.payee,
methodePaiement: PaymentMethod.waveMoney,
numeroPaiement: 'WAVE123456',
);
test('should return contribution by id', () async {
// Arrange
when(mockRepository.getCotisationById(tContributionId))
.thenAnswer((_) async => tContribution);
// Act
final result = await useCase(tContributionId);
// Assert
expect(result, equals(tContribution));
expect(result.id, equals(tContributionId));
expect(result.statut, equals(ContributionStatus.payee));
verify(mockRepository.getCotisationById(tContributionId));
verifyNoMoreInteractions(mockRepository);
});
test('should return contribution with payment details', () async {
// Arrange
when(mockRepository.getCotisationById(tContributionId))
.thenAnswer((_) async => tContribution);
// Act
final result = await useCase(tContributionId);
// Assert
expect(result.montantPaye, equals(5000.0));
expect(result.methodePaiement, equals(PaymentMethod.waveMoney));
expect(result.numeroPaiement, isNotNull);
});
test('should return unpaid contribution', () async {
// Arrange
final unpaidContribution = ContributionModel(
id: 'cont456',
membreId: 'membre2',
montant: 10000.0,
dateEcheance: DateTime(2025, 1, 31),
annee: 2025,
type: ContributionType.mensuelle,
statut: ContributionStatus.enRetard,
);
when(mockRepository.getCotisationById('cont456'))
.thenAnswer((_) async => unpaidContribution);
// Act
final result = await useCase('cont456');
// Assert
expect(result.statut, equals(ContributionStatus.enRetard));
expect(result.montantPaye, isNull);
expect(result.datePaiement, isNull);
});
test('should throw exception when contribution not found', () async {
// Arrange
when(mockRepository.getCotisationById(any))
.thenThrow(Exception('Contribution non trouvée'));
// Act & Assert
expect(() => useCase('nonexistent'), throwsException);
});
});
}

View File

@@ -0,0 +1,153 @@
/// Tests unitaires pour GetContributionHistory use case
library get_contribution_history_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_history.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/repositories/contribution_repository.dart';
@GenerateMocks([IContributionRepository])
import 'get_contribution_history_test.mocks.dart';
void main() {
late GetContributionHistory useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = GetContributionHistory(mockRepository);
});
group('GetContributionHistory Use Case', () {
final tHistoryList = [
ContributionModel(
id: 'cont1',
membreId: 'membre1',
montant: 5000.0,
montantPaye: 5000.0,
dateEcheance: DateTime(2024, 1, 31),
datePaiement: DateTime(2024, 1, 15),
annee: 2024,
mois: 1,
type: ContributionType.mensuelle,
statut: ContributionStatus.payee,
),
ContributionModel(
id: 'cont2',
membreId: 'membre1',
montant: 5000.0,
dateEcheance: DateTime(2024, 2, 28),
annee: 2024,
mois: 2,
type: ContributionType.mensuelle,
statut: ContributionStatus.enAttente,
),
];
final tPageResult = ContributionPageResult(
contributions: tHistoryList,
total: 2,
totalPages: 1,
page: 0,
size: 50,
);
test('should return contribution history', () async {
// Arrange
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
.thenAnswer((_) async => tPageResult);
// Act
final result = await useCase(page: 0, size: 50);
// Assert
expect(result, equals(tPageResult));
expect(result.contributions.length, equals(2));
expect(result.contributions[0].statut, equals(ContributionStatus.payee));
expect(result.contributions[1].statut, equals(ContributionStatus.enAttente));
verify(mockRepository.getMesCotisations(page: 0, size: 50));
verifyNoMoreInteractions(mockRepository);
});
test('should return history for specific year', () async {
// Arrange
final year2023List = [
ContributionModel(
id: 'cont3',
membreId: 'membre1',
montant: 60000.0,
montantPaye: 60000.0,
dateEcheance: DateTime(2023, 12, 31),
datePaiement: DateTime(2023, 12, 15),
annee: 2023,
type: ContributionType.annuelle,
statut: ContributionStatus.payee,
),
];
final yearPageResult = ContributionPageResult(
contributions: year2023List,
total: 1,
totalPages: 1,
page: 0,
size: 50,
);
when(mockRepository.getMesCotisations(page: 0, size: 50))
.thenAnswer((_) async => yearPageResult);
// Act
final result = await useCase(page: 0, size: 50, annee: 2023);
// Assert
expect(result.contributions.length, equals(1));
expect(result.contributions.first.annee, equals(2023));
});
test('should return history filtered by status', () async {
// Arrange
final paidOnly = [tHistoryList[0]];
final paidPageResult = ContributionPageResult(
contributions: paidOnly,
total: 1,
totalPages: 1,
page: 0,
size: 50,
);
when(mockRepository.getMesCotisations(page: 0, size: 50))
.thenAnswer((_) async => paidPageResult);
// Act
final result = await useCase(
page: 0,
size: 50,
statut: ContributionStatus.payee,
);
// Assert
expect(result.contributions.length, equals(1));
expect(result.contributions.first.statut, equals(ContributionStatus.payee));
});
test('should return empty history when no contributions', () async {
// Arrange
final emptyResult = ContributionPageResult(
contributions: [],
total: 0,
totalPages: 0,
page: 0,
size: 50,
);
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
.thenAnswer((_) async => emptyResult);
// Act
final result = await useCase();
// Assert
expect(result.contributions, isEmpty);
expect(result.total, equals(0));
});
});
}

View File

@@ -0,0 +1,89 @@
/// Tests unitaires pour GetContributionStats use case
library get_contribution_stats_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contribution_stats.dart';
@GenerateMocks([IContributionRepository])
import 'get_contribution_stats_test.mocks.dart';
void main() {
late GetContributionStats useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = GetContributionStats(mockRepository);
});
group('GetContributionStats Use Case', () {
final tStats = {
'montantDu': 60000.0,
'totalPayeAnnee': 45000.0,
'cotisationsEnAttente': 3,
'prochaineEcheance': '2024-12-31T00:00:00.000Z',
'tauxPaiement': 75.0,
'nombreCotisations': 12,
'montantMoyenCotisation': 5000.0,
};
test('should return contribution statistics', () async {
// Arrange
when(mockRepository.getMesCotisationsSynthese())
.thenAnswer((_) async => tStats);
// Act
final result = await useCase();
// Assert
expect(result, equals(tStats));
expect(result?['montantDu'], equals(60000.0));
expect(result?['totalPayeAnnee'], equals(45000.0));
expect(result?['tauxPaiement'], equals(75.0));
verify(mockRepository.getMesCotisationsSynthese());
verifyNoMoreInteractions(mockRepository);
});
test('should return stats with payment rate', () async {
// Arrange
when(mockRepository.getMesCotisationsSynthese())
.thenAnswer((_) async => tStats);
// Act
final result = await useCase();
// Assert
expect(result?['tauxPaiement'], equals(75.0));
expect(result?['cotisationsEnAttente'], equals(3));
});
test('should return stats with next deadline', () async {
// Arrange
when(mockRepository.getMesCotisationsSynthese())
.thenAnswer((_) async => tStats);
// Act
final result = await useCase();
// Assert
expect(result?['prochaineEcheance'], isNotNull);
expect(result?['prochaineEcheance'], contains('2024-12-31'));
});
test('should return null when no data available', () async {
// Arrange
when(mockRepository.getMesCotisationsSynthese())
.thenAnswer((_) async => null);
// Act
final result = await useCase();
// Assert
expect(result, isNull);
verify(mockRepository.getMesCotisationsSynthese());
});
});
}

View File

@@ -0,0 +1,124 @@
/// Tests unitaires pour GetContributions use case
library get_contributions_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/get_contributions.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/repositories/contribution_repository.dart';
@GenerateMocks([IContributionRepository])
import 'get_contributions_test.mocks.dart';
void main() {
late GetContributions useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = GetContributions(mockRepository);
});
group('GetContributions Use Case', () {
final tContributionList = [
ContributionModel(
id: 'cont1',
membreId: 'membre1',
membreNom: 'Dupont',
membrePrenom: 'Jean',
montant: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
annee: 2024,
type: ContributionType.mensuelle,
statut: ContributionStatus.payee,
),
ContributionModel(
id: 'cont2',
membreId: 'membre1',
membreNom: 'Dupont',
membrePrenom: 'Jean',
montant: 5000.0,
dateEcheance: DateTime(2025, 1, 31),
annee: 2025,
type: ContributionType.mensuelle,
statut: ContributionStatus.enAttente,
),
];
final tPageResult = ContributionPageResult(
contributions: tContributionList,
total: 2,
totalPages: 1,
page: 0,
size: 50,
);
test('should return paginated list of contributions', () async {
// Arrange
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
.thenAnswer((_) async => tPageResult);
// Act
final result = await useCase(page: 0, size: 50);
// Assert
expect(result, equals(tPageResult));
expect(result.contributions.length, equals(2));
expect(result.total, equals(2));
verify(mockRepository.getMesCotisations(page: 0, size: 50));
verifyNoMoreInteractions(mockRepository);
});
test('should return contributions with custom page size', () async {
// Arrange
final smallPageResult = ContributionPageResult(
contributions: [tContributionList[0]],
total: 2,
totalPages: 2,
page: 0,
size: 1,
);
when(mockRepository.getMesCotisations(page: 0, size: 1))
.thenAnswer((_) async => smallPageResult);
// Act
final result = await useCase(page: 0, size: 1);
// Assert
expect(result.contributions.length, equals(1));
expect(result.size, equals(1));
verify(mockRepository.getMesCotisations(page: 0, size: 1));
});
test('should return empty result when no contributions exist', () async {
// Arrange
final emptyResult = ContributionPageResult(
contributions: [],
total: 0,
totalPages: 0,
page: 0,
size: 50,
);
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
.thenAnswer((_) async => emptyResult);
// Act
final result = await useCase(page: 0, size: 50);
// Assert
expect(result.contributions, isEmpty);
expect(result.total, equals(0));
});
test('should throw exception when repository fails', () async {
// Arrange
when(mockRepository.getMesCotisations(page: anyNamed('page'), size: anyNamed('size')))
.thenThrow(Exception('Network error'));
// Act & Assert
expect(() => useCase(page: 0, size: 50), throwsException);
});
});
}

View File

@@ -0,0 +1,165 @@
/// Tests unitaires pour PayContribution use case
library pay_contribution_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/pay_contribution.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
@GenerateMocks([IContributionRepository])
import 'pay_contribution_test.mocks.dart';
void main() {
late PayContribution useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = PayContribution(mockRepository);
});
group('PayContribution Use Case', () {
const tContributionId = 'cont123';
const tMontant = 5000.0;
final tDatePaiement = DateTime(2024, 11, 15);
const tMethode = 'WAVE_MONEY';
const tNumero = 'WAVE123456';
final tPaidContribution = ContributionModel(
id: tContributionId,
membreId: 'membre1',
montant: 5000.0,
montantPaye: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
datePaiement: tDatePaiement,
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.payee,
methodePaiement: PaymentMethod.waveMoney,
numeroPaiement: tNumero,
);
test('should record payment successfully', () async {
// Arrange
when(mockRepository.enregistrerPaiement(
tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
numeroPaiement: tNumero,
referencePaiement: anyNamed('referencePaiement'),
)).thenAnswer((_) async => tPaidContribution);
// Act
final result = await useCase(
cotisationId: tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
numeroPaiement: tNumero,
);
// Assert
expect(result, equals(tPaidContribution));
expect(result.statut, equals(ContributionStatus.payee));
expect(result.montantPaye, equals(5000.0));
expect(result.datePaiement, equals(tDatePaiement));
verify(mockRepository.enregistrerPaiement(
tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
numeroPaiement: tNumero,
referencePaiement: null,
));
verifyNoMoreInteractions(mockRepository);
});
test('should record partial payment', () async {
// Arrange
const partialMontant = 2500.0;
final partialPaid = ContributionModel(
id: tContributionId,
membreId: 'membre1',
montant: 5000.0,
montantPaye: 2500.0,
dateEcheance: DateTime(2024, 12, 31),
datePaiement: tDatePaiement,
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.partielle,
methodePaiement: PaymentMethod.especes,
);
when(mockRepository.enregistrerPaiement(
tContributionId,
montant: partialMontant,
datePaiement: tDatePaiement,
methodePaiement: 'ESPECES',
numeroPaiement: null,
referencePaiement: null,
)).thenAnswer((_) async => partialPaid);
// Act
final result = await useCase(
cotisationId: tContributionId,
montant: partialMontant,
datePaiement: tDatePaiement,
methodePaiement: 'ESPECES',
);
// Assert
expect(result.statut, equals(ContributionStatus.partielle));
expect(result.montantPaye, equals(2500.0));
});
test('should record payment with reference', () async {
// Arrange
const reference = 'REF-2024-001';
when(mockRepository.enregistrerPaiement(
tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
numeroPaiement: null,
referencePaiement: reference,
)).thenAnswer((_) async => tPaidContribution.copyWith(referencePaiement: reference));
// Act
final result = await useCase(
cotisationId: tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
referencePaiement: reference,
);
// Assert
expect(result.referencePaiement, equals(reference));
});
test('should throw exception when payment fails', () async {
// Arrange
when(mockRepository.enregistrerPaiement(
tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
numeroPaiement: null,
referencePaiement: null,
)).thenThrow(Exception('Erreur lors de l\'enregistrement du paiement'));
// Act & Assert
expect(
() => useCase(
cotisationId: tContributionId,
montant: tMontant,
datePaiement: tDatePaiement,
methodePaiement: tMethode,
),
throwsException,
);
});
});
}

View File

@@ -0,0 +1,105 @@
/// Tests unitaires pour UpdateContribution use case
library update_contribution_test;
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/repositories/contribution_repository.dart';
import 'package:unionflow_mobile_apps/features/contributions/domain/usecases/update_contribution.dart';
import 'package:unionflow_mobile_apps/features/contributions/data/models/contribution_model.dart';
@GenerateMocks([IContributionRepository])
import 'update_contribution_test.mocks.dart';
void main() {
late UpdateContribution useCase;
late MockIContributionRepository mockRepository;
setUp(() {
mockRepository = MockIContributionRepository();
useCase = UpdateContribution(mockRepository);
});
group('UpdateContribution Use Case', () {
const tContributionId = 'cont123';
final tUpdatedContribution = ContributionModel(
id: tContributionId,
membreId: 'membre1',
montant: 6000.0,
dateEcheance: DateTime(2025, 12, 31),
annee: 2025,
type: ContributionType.annuelle,
statut: ContributionStatus.nonPayee,
description: 'Montant mis à jour',
);
test('should update contribution successfully', () async {
// Arrange
when(mockRepository.updateCotisation(tContributionId, tUpdatedContribution))
.thenAnswer((_) async => tUpdatedContribution);
// Act
final result = await useCase(tContributionId, tUpdatedContribution);
// Assert
expect(result, equals(tUpdatedContribution));
expect(result.montant, equals(6000.0));
expect(result.description, equals('Montant mis à jour'));
verify(mockRepository.updateCotisation(tContributionId, tUpdatedContribution));
verifyNoMoreInteractions(mockRepository);
});
test('should update contribution status', () async {
// Arrange
final statusUpdate = ContributionModel(
id: tContributionId,
membreId: 'membre1',
montant: 5000.0,
dateEcheance: DateTime(2024, 12, 31),
annee: 2024,
type: ContributionType.annuelle,
statut: ContributionStatus.enRetard,
);
when(mockRepository.updateCotisation(tContributionId, statusUpdate))
.thenAnswer((_) async => statusUpdate);
// Act
final result = await useCase(tContributionId, statusUpdate);
// Assert
expect(result.statut, equals(ContributionStatus.enRetard));
});
test('should update contribution type', () async {
// Arrange
final typeUpdate = ContributionModel(
id: tContributionId,
membreId: 'membre1',
montant: 5000.0,
dateEcheance: DateTime(2024, 3, 31),
annee: 2024,
trimestre: 1,
type: ContributionType.trimestrielle,
statut: ContributionStatus.nonPayee,
);
when(mockRepository.updateCotisation(tContributionId, typeUpdate))
.thenAnswer((_) async => typeUpdate);
// Act
final result = await useCase(tContributionId, typeUpdate);
// Assert
expect(result.type, equals(ContributionType.trimestrielle));
expect(result.trimestre, equals(1));
});
test('should throw exception when update fails', () async {
// Arrange
when(mockRepository.updateCotisation(any, any))
.thenThrow(Exception('Mise à jour échouée'));
// Act & Assert
expect(() => useCase(tContributionId, tUpdatedContribution), throwsException);
});
});
}