feat: WebSocket temps réel + Finance Workflow + corrections
- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics) * Backend: KafkaEventProducer, KafkaEventConsumer * Mobile: WebSocketService (reconnection, heartbeat, typed events) * DashboardBloc: Auto-refresh depuis WebSocket events - Finance Workflow: approbations + budgets (backend + mobile) * Backend: entities, services, resources, migrations Flyway V6 * Mobile: features finance_workflow complète avec BLoC - Corrections DI: interfaces IRepository partout * IProfileRepository, IOrganizationRepository, IMembreRepository * GetIt configuré avec @injectable - Spec-Kit: constitution + templates mis à jour * .specify/memory/constitution.md enrichie * Templates agent, plan, spec, tasks, checklist - Nettoyage: fichiers temporaires supprimés Signed-off-by: lions dev Team
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/// Tests unitaires pour ExportReportExcel use case
|
||||
library export_report_excel_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/export_report_excel.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'export_report_excel_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ExportReportExcel useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ExportReportExcel(mockRepository);
|
||||
});
|
||||
|
||||
group('ExportReportExcel Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tExcelPath = '/storage/reports/membres_2024.xlsx';
|
||||
const tCsvPath = '/storage/reports/membres_2024.csv';
|
||||
|
||||
test('should export report to Excel successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(tReportType, format: 'excel'))
|
||||
.thenAnswer((_) async => tExcelPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tExcelPath));
|
||||
expect(result, contains('.xlsx'));
|
||||
verify(mockRepository.exportReportExcel(tReportType, format: 'excel'));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should export report to CSV when format specified', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(tReportType, format: 'csv'))
|
||||
.thenAnswer((_) async => tCsvPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType, format: 'csv');
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tCsvPath));
|
||||
expect(result, contains('.csv'));
|
||||
verify(mockRepository.exportReportExcel(tReportType, format: 'csv'));
|
||||
});
|
||||
|
||||
test('should return valid Excel path with type name', () async {
|
||||
// Arrange
|
||||
const cotisationsType = 'cotisations';
|
||||
const cotisationsExcel = '/storage/reports/cotisations_2024.xlsx';
|
||||
when(mockRepository.exportReportExcel(cotisationsType, format: 'excel'))
|
||||
.thenAnswer((_) async => cotisationsExcel);
|
||||
|
||||
// Act
|
||||
final result = await useCase(cotisationsType);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('cotisations'));
|
||||
expect(result, contains('.xlsx'));
|
||||
});
|
||||
|
||||
test('should throw exception when Excel export fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportExcel(any, format: anyNamed('format')))
|
||||
.thenThrow(Exception('Excel export failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/// Tests unitaires pour ExportReportPdf use case
|
||||
library export_report_pdf_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/export_report_pdf.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'export_report_pdf_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ExportReportPdf useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ExportReportPdf(mockRepository);
|
||||
});
|
||||
|
||||
group('ExportReportPdf Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tPdfPath = '/storage/reports/membres_2024.pdf';
|
||||
|
||||
test('should export report to PDF successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportPdf(tReportType))
|
||||
.thenAnswer((_) async => tPdfPath);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tPdfPath));
|
||||
expect(result, contains('.pdf'));
|
||||
verify(mockRepository.exportReportPdf(tReportType));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return valid PDF path with type name', () async {
|
||||
// Arrange
|
||||
const cotisationsType = 'cotisations';
|
||||
const cotisationsPdf = '/storage/reports/cotisations_2024.pdf';
|
||||
when(mockRepository.exportReportPdf(cotisationsType))
|
||||
.thenAnswer((_) async => cotisationsPdf);
|
||||
|
||||
// Act
|
||||
final result = await useCase(cotisationsType);
|
||||
|
||||
// Assert
|
||||
expect(result, contains('cotisations'));
|
||||
expect(result, endsWith('.pdf'));
|
||||
});
|
||||
|
||||
test('should return URL for remote PDF file', () async {
|
||||
// Arrange
|
||||
const remotePdf = 'https://api.example.com/reports/membres.pdf';
|
||||
when(mockRepository.exportReportPdf(tReportType))
|
||||
.thenAnswer((_) async => remotePdf);
|
||||
|
||||
// Act
|
||||
final result = await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
expect(result, startsWith('https://'));
|
||||
expect(result, endsWith('.pdf'));
|
||||
});
|
||||
|
||||
test('should throw exception when PDF export fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.exportReportPdf(any))
|
||||
.thenThrow(Exception('PDF generation failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/// Tests unitaires pour GenerateReport use case
|
||||
library generate_report_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/generate_report.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'generate_report_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GenerateReport useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GenerateReport(mockRepository);
|
||||
});
|
||||
|
||||
group('GenerateReport Use Case', () {
|
||||
const tReportType = 'membres';
|
||||
const tFormat = 'pdf';
|
||||
|
||||
test('should generate report successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(tReportType, format: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tReportType);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(tReportType, format: null));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should generate report with specific format', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(tReportType, format: tFormat))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(tReportType, format: tFormat);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(tReportType, format: tFormat));
|
||||
});
|
||||
|
||||
test('should generate report for different types', () async {
|
||||
// Arrange
|
||||
const altType = 'cotisations';
|
||||
when(mockRepository.generateReport(altType, format: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(altType);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.generateReport(altType, format: null));
|
||||
});
|
||||
|
||||
test('should throw exception when generation fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.generateReport(any, format: anyNamed('format')))
|
||||
.thenThrow(Exception('Generation failed'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(tReportType), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour GetReports use case
|
||||
library get_reports_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/get_reports.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'get_reports_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetReports useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GetReports(mockRepository);
|
||||
});
|
||||
|
||||
group('GetReports Use Case', () {
|
||||
final tAvailableReports = [
|
||||
{
|
||||
'type': 'membres',
|
||||
'nom': 'Rapport Membres',
|
||||
'description': 'Statistiques complètes des membres',
|
||||
'categorie': 'administratif',
|
||||
},
|
||||
{
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations',
|
||||
'description': 'Analyse des cotisations',
|
||||
'categorie': 'financier',
|
||||
},
|
||||
{
|
||||
'type': 'evenements',
|
||||
'nom': 'Rapport Événements',
|
||||
'description': 'Bilan des événements',
|
||||
'categorie': 'activites',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of available reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenAnswer((_) async => tAvailableReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tAvailableReports));
|
||||
expect(result.length, equals(3));
|
||||
expect(result[0]['type'], equals('membres'));
|
||||
verify(mockRepository.getAvailableReports());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return reports with specific categories', () async {
|
||||
// Arrange
|
||||
final financialReports = [
|
||||
{
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations',
|
||||
'categorie': 'financier',
|
||||
},
|
||||
];
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenAnswer((_) async => financialReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(1));
|
||||
expect(result.first['categorie'], equals('financier'));
|
||||
verify(mockRepository.getAvailableReports());
|
||||
});
|
||||
|
||||
test('should return empty list when no reports available', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports()).thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.getAvailableReports());
|
||||
});
|
||||
|
||||
test('should throw exception when repository fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getAvailableReports())
|
||||
.thenThrow(Exception('Network error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/// Tests unitaires pour GetScheduledReports use case
|
||||
library get_scheduled_reports_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/get_scheduled_reports.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'get_scheduled_reports_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late GetScheduledReports useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = GetScheduledReports(mockRepository);
|
||||
});
|
||||
|
||||
group('GetScheduledReports Use Case', () {
|
||||
final tScheduledReports = [
|
||||
{
|
||||
'id': '1',
|
||||
'type': 'membres',
|
||||
'nom': 'Rapport Membres Mensuel',
|
||||
'cronExpression': '0 0 1 * *',
|
||||
'active': true,
|
||||
'derniereLancement': '2024-01-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'type': 'cotisations',
|
||||
'nom': 'Rapport Cotisations Hebdomadaire',
|
||||
'cronExpression': '0 9 * * 1',
|
||||
'active': true,
|
||||
'derniereLancement': '2024-01-08T09:00:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
test('should return list of scheduled reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenAnswer((_) async => tScheduledReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, equals(tScheduledReports));
|
||||
expect(result.length, equals(2));
|
||||
expect(result[0]['type'], equals('membres'));
|
||||
expect(result[0]['active'], isTrue);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should return only active scheduled reports', () async {
|
||||
// Arrange
|
||||
final activeReports = [
|
||||
{
|
||||
'id': '1',
|
||||
'type': 'membres',
|
||||
'cronExpression': '0 0 1 * *',
|
||||
'active': true,
|
||||
},
|
||||
];
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenAnswer((_) async => activeReports);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result.length, equals(1));
|
||||
expect(result.first['active'], isTrue);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
});
|
||||
|
||||
test('should return empty list when no scheduled reports', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports()).thenAnswer((_) async => []);
|
||||
|
||||
// Act
|
||||
final result = await useCase();
|
||||
|
||||
// Assert
|
||||
expect(result, isEmpty);
|
||||
verify(mockRepository.getScheduledReports());
|
||||
});
|
||||
|
||||
test('should throw exception when retrieval fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.getScheduledReports())
|
||||
.thenThrow(Exception('Database error'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/// Tests unitaires pour ScheduleReport use case
|
||||
library schedule_report_test;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/repositories/reports_repository.dart';
|
||||
import 'package:unionflow_mobile_apps/features/reports/domain/usecases/schedule_report.dart';
|
||||
|
||||
@GenerateMocks([IReportsRepository])
|
||||
import 'schedule_report_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late ScheduleReport useCase;
|
||||
late MockIReportsRepository mockRepository;
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockIReportsRepository();
|
||||
useCase = ScheduleReport(mockRepository);
|
||||
});
|
||||
|
||||
group('ScheduleReport Use Case', () {
|
||||
const tCronExpression = '0 0 1 * *'; // 1er de chaque mois à minuit
|
||||
|
||||
test('should schedule report successfully', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: null))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase();
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: null));
|
||||
verifyNoMoreInteractions(mockRepository);
|
||||
});
|
||||
|
||||
test('should schedule report with cron expression', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: tCronExpression))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(cronExpression: tCronExpression);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: tCronExpression));
|
||||
});
|
||||
|
||||
test('should schedule report with different cron expressions', () async {
|
||||
// Arrange
|
||||
const weeklyCron = '0 9 * * 1'; // Tous les lundis à 9h
|
||||
when(mockRepository.scheduleReport(cronExpression: weeklyCron))
|
||||
.thenAnswer((_) async => Future.value());
|
||||
|
||||
// Act
|
||||
await useCase(cronExpression: weeklyCron);
|
||||
|
||||
// Assert
|
||||
verify(mockRepository.scheduleReport(cronExpression: weeklyCron));
|
||||
});
|
||||
|
||||
test('should throw exception when scheduling fails', () async {
|
||||
// Arrange
|
||||
when(mockRepository.scheduleReport(cronExpression: anyNamed('cronExpression')))
|
||||
.thenThrow(Exception('Invalid cron expression'));
|
||||
|
||||
// Act & Assert
|
||||
expect(() => useCase(cronExpression: tCronExpression), throwsException);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user