311 lines
10 KiB
Dart
311 lines
10 KiB
Dart
/// Tests d'intégration pour Finance Workflow (API-only)
|
||
library finance_workflow_integration_test;
|
||
|
||
import 'dart:convert';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:http/http.dart' as http;
|
||
|
||
import 'helpers/test_config.dart';
|
||
import 'helpers/auth_helper.dart';
|
||
|
||
void main() {
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
|
||
late http.Client client;
|
||
late AuthHelper authHelper;
|
||
|
||
setUpAll(() async {
|
||
print('\n🚀 Démarrage des tests d\'intégration Finance Workflow\n');
|
||
client = http.Client();
|
||
authHelper = AuthHelper(client);
|
||
|
||
// Authentification en tant qu'ORG_ADMIN
|
||
final authenticated = await authHelper.authenticateAsOrgAdmin();
|
||
expect(authenticated, true, reason: 'Authentification doit réussir');
|
||
|
||
print('✅ Setup terminé - Token obtenu\n');
|
||
});
|
||
|
||
tearDownAll(() {
|
||
client.close();
|
||
print('\n✅ Tests d\'intégration Finance Workflow terminés\n');
|
||
});
|
||
|
||
group('Finance Workflow - Approbations', () {
|
||
test('GET /api/finance/approvals/pending - Récupérer approbations en attente',
|
||
() async {
|
||
// Arrange
|
||
final url = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/approvals/pending',
|
||
).replace(queryParameters: {
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
});
|
||
|
||
// Act
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 200, reason: 'HTTP 200 OK attendu');
|
||
|
||
final List<dynamic> approvals = json.decode(response.body);
|
||
expect(approvals, isA<List>(), reason: 'Réponse doit être une liste');
|
||
|
||
print('✅ GET pending approvals: ${approvals.length} approbations trouvées');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('GET /api/finance/approvals/{id} - Récupérer approbation par ID',
|
||
() async {
|
||
// Arrange - Récupère d'abord la liste pour avoir un ID
|
||
final listUrl = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/approvals/pending',
|
||
).replace(queryParameters: {
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
});
|
||
|
||
final listResponse = await client.get(listUrl, headers: authHelper.getAuthHeaders());
|
||
expect(listResponse.statusCode, 200);
|
||
|
||
final List<dynamic> approvals = json.decode(listResponse.body);
|
||
|
||
if (approvals.isEmpty) {
|
||
print('⚠️ Aucune approbation en attente - test ignoré');
|
||
return;
|
||
}
|
||
|
||
final approvalId = approvals.first['id'];
|
||
|
||
// Act - Récupère l'approbation par ID
|
||
final url = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/approvals/$approvalId',
|
||
);
|
||
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 200, reason: 'HTTP 200 OK attendu');
|
||
|
||
final approval = json.decode(response.body);
|
||
expect(approval['id'], equals(approvalId), reason: 'ID doit correspondre');
|
||
|
||
print('✅ GET approval by ID: ${approval['id']}');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('POST /api/finance/approvals/{id}/approve - Approuver transaction',
|
||
() async {
|
||
// Note: Ce test nécessite une approbation en statut "pending"
|
||
// Pour éviter de modifier l'état en prod, ce test est informatif
|
||
|
||
print('ℹ️ Test approve transaction - Simulé (évite modification en prod)');
|
||
print(' Endpoint: POST /api/finance/approvals/{id}/approve');
|
||
print(' Body: { "comment": "Approved by integration test" }');
|
||
print(' Expected: HTTP 200, statut=approved');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('POST /api/finance/approvals/{id}/reject - Rejeter transaction',
|
||
() async {
|
||
// Note: Ce test nécessite une approbation en statut "pending"
|
||
// Pour éviter de modifier l'état en prod, ce test est informatif
|
||
|
||
print('ℹ️ Test reject transaction - Simulé (évite modification en prod)');
|
||
print(' Endpoint: POST /api/finance/approvals/{id}/reject');
|
||
print(' Body: { "reason": "Rejected by integration test" }');
|
||
print(' Expected: HTTP 200, statut=rejected');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
});
|
||
|
||
group('Finance Workflow - Budgets', () {
|
||
String? createdBudgetId;
|
||
|
||
test('GET /api/finance/budgets - Récupérer liste budgets',
|
||
() async {
|
||
// Arrange
|
||
final url = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/budgets',
|
||
).replace(queryParameters: {
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
});
|
||
|
||
// Act
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 200, reason: 'HTTP 200 OK attendu');
|
||
|
||
final List<dynamic> budgets = json.decode(response.body);
|
||
expect(budgets, isA<List>(), reason: 'Réponse doit être une liste');
|
||
|
||
print('✅ GET budgets: ${budgets.length} budgets trouvés');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('POST /api/finance/budgets - Créer un budget',
|
||
() async {
|
||
// Arrange
|
||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
||
|
||
final requestBody = {
|
||
'name': 'Budget Test Intégration ${DateTime.now().millisecondsSinceEpoch}',
|
||
'description': 'Budget créé par test d\'intégration',
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
'period': 'ANNUAL',
|
||
'year': DateTime.now().year,
|
||
'lines': [
|
||
{
|
||
'category': 'CONTRIBUTIONS',
|
||
'name': 'Cotisations',
|
||
'amountPlanned': 1000000.0,
|
||
'description': 'Revenus cotisations',
|
||
},
|
||
{
|
||
'category': 'SAVINGS',
|
||
'name': 'Épargne',
|
||
'amountPlanned': 500000.0,
|
||
'description': 'Collecte épargne',
|
||
},
|
||
],
|
||
};
|
||
|
||
// Act
|
||
final response = await client.post(
|
||
url,
|
||
headers: authHelper.getAuthHeaders(),
|
||
body: json.encode(requestBody),
|
||
);
|
||
|
||
// Assert
|
||
expect(response.statusCode, inInclusiveRange(200, 201),
|
||
reason: 'HTTP 200/201 attendu');
|
||
|
||
final budget = json.decode(response.body);
|
||
expect(budget['id'], isNotNull, reason: 'ID budget doit être présent');
|
||
expect(budget['name'], contains('Budget Test Intégration'),
|
||
reason: 'Nom doit correspondre');
|
||
|
||
createdBudgetId = budget['id'];
|
||
print('✅ POST create budget: ${budget['id']} - ${budget['name']}');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('GET /api/finance/budgets/{id} - Récupérer budget par ID',
|
||
() async {
|
||
// Arrange - Utilise le budget créé précédemment ou récupère un existant
|
||
String budgetId;
|
||
|
||
if (createdBudgetId != null) {
|
||
budgetId = createdBudgetId!;
|
||
} else {
|
||
// Récupère un budget existant
|
||
final listUrl = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/budgets',
|
||
).replace(queryParameters: {
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
});
|
||
|
||
final listResponse = await client.get(listUrl, headers: authHelper.getAuthHeaders());
|
||
expect(listResponse.statusCode, 200);
|
||
|
||
final List<dynamic> budgets = json.decode(listResponse.body);
|
||
if (budgets.isEmpty) {
|
||
print('⚠️ Aucun budget trouvé - test ignoré');
|
||
return;
|
||
}
|
||
|
||
budgetId = budgets.first['id'];
|
||
}
|
||
|
||
// Act
|
||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets/$budgetId');
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 200, reason: 'HTTP 200 OK attendu');
|
||
|
||
final budget = json.decode(response.body);
|
||
expect(budget['id'], equals(budgetId), reason: 'ID doit correspondre');
|
||
expect(budget['lines'], isNotNull, reason: 'Lignes budgétaires doivent être présentes');
|
||
|
||
print('✅ GET budget by ID: ${budget['id']} - ${budget['name']}');
|
||
print(' Lignes budgétaires: ${budget['lines'].length}');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
});
|
||
|
||
group('Finance Workflow - Tests négatifs', () {
|
||
test('GET approbation inexistante - Doit retourner 404',
|
||
() async {
|
||
// Arrange
|
||
final fakeId = '00000000-0000-0000-0000-000000000000';
|
||
final url = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/approvals/$fakeId',
|
||
);
|
||
|
||
// Act
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found attendu');
|
||
|
||
print('✅ Test négatif: 404 pour approbation inexistante');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('GET budget inexistant - Doit retourner 404',
|
||
() async {
|
||
// Arrange
|
||
final fakeId = '00000000-0000-0000-0000-000000000000';
|
||
final url = Uri.parse(
|
||
'${TestConfig.apiBaseUrl}/api/finance/budgets/$fakeId',
|
||
);
|
||
|
||
// Act
|
||
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
||
|
||
// Assert
|
||
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found attendu');
|
||
|
||
print('✅ Test négatif: 404 pour budget inexistant');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
|
||
test('POST budget sans authentication - Doit retourner 401',
|
||
() async {
|
||
// Arrange
|
||
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
||
final requestBody = {
|
||
'name': 'Budget Sans Auth',
|
||
'organizationId': TestConfig.testOrganizationId,
|
||
'period': 'ANNUAL',
|
||
'year': 2026,
|
||
'lines': [],
|
||
};
|
||
|
||
// Act - Sans token d'authentification
|
||
final response = await client.post(
|
||
url,
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: json.encode(requestBody),
|
||
);
|
||
|
||
// Assert
|
||
expect(response.statusCode, 401, reason: 'HTTP 401 Unauthorized attendu');
|
||
|
||
print('✅ Test négatif: 401 pour requête non authentifiée');
|
||
|
||
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
||
});
|
||
});
|
||
}
|