249 lines
9.0 KiB
Dart
249 lines
9.0 KiB
Dart
/// Integration tests for 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=== Finance Workflow Integration Tests ===\n');
|
|
client = http.Client();
|
|
authHelper = AuthHelper(client);
|
|
|
|
// Authenticate as ORG_ADMIN
|
|
final authenticated = await authHelper.authenticateAsOrgAdmin();
|
|
expect(authenticated, true, reason: 'Authentication must succeed');
|
|
|
|
print('Setup complete - Token obtained\n');
|
|
});
|
|
|
|
tearDownAll(() {
|
|
client.close();
|
|
print('\n=== Integration Tests Completed ===\n');
|
|
});
|
|
|
|
group('Finance Workflow - Approvals', () {
|
|
test('GET /api/finance/approvals/pending - List pending approvals', () async {
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/pending')
|
|
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
|
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
|
|
|
final List<dynamic> approvals = json.decode(response.body);
|
|
expect(approvals, isA<List>(), reason: 'Response must be a list');
|
|
|
|
print('GET pending approvals: ${approvals.length} found');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('GET /api/finance/approvals/{id} - Get approval by ID', () async {
|
|
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('No pending approvals - test skipped');
|
|
return;
|
|
}
|
|
|
|
final approvalId = approvals.first['id'];
|
|
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/$approvalId');
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
|
|
|
final approval = json.decode(response.body);
|
|
expect(approval['id'], equals(approvalId), reason: 'ID must match');
|
|
|
|
print('GET approval by ID: ${approval['id']}');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('POST /api/finance/approvals/{id}/approve - Approve transaction (simulated)', () async {
|
|
print('Test approve transaction - Simulated (avoids prod modification)');
|
|
print(' Endpoint: POST /api/finance/approvals/{id}/approve');
|
|
print(' Body: { "comment": "Approved by integration test" }');
|
|
print(' Expected: HTTP 200, status=approved');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('POST /api/finance/approvals/{id}/reject - Reject transaction (simulated)', () async {
|
|
print('Test reject transaction - Simulated (avoids prod modification)');
|
|
print(' Endpoint: POST /api/finance/approvals/{id}/reject');
|
|
print(' Body: { "reason": "Rejected by integration test" }');
|
|
print(' Expected: HTTP 200, status=rejected');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
});
|
|
|
|
group('Finance Workflow - Budgets', () {
|
|
String? createdBudgetId;
|
|
|
|
test('GET /api/finance/budgets - List budgets', () async {
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets')
|
|
.replace(queryParameters: {'organizationId': TestConfig.testOrganizationId});
|
|
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
|
|
|
final List<dynamic> budgets = json.decode(response.body);
|
|
expect(budgets, isA<List>(), reason: 'Response must be a list');
|
|
|
|
print('GET budgets: ${budgets.length} found');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('POST /api/finance/budgets - Create budget', () async {
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
|
|
|
final requestBody = {
|
|
'name': 'Budget Integration Test ${DateTime.now().millisecondsSinceEpoch}',
|
|
'description': 'Budget created by integration test',
|
|
'organizationId': TestConfig.testOrganizationId,
|
|
'period': 'ANNUAL',
|
|
'year': DateTime.now().year,
|
|
'lines': [
|
|
{
|
|
'category': 'CONTRIBUTIONS',
|
|
'name': 'Contributions',
|
|
'amountPlanned': 1000000.0,
|
|
'description': 'Membership contributions',
|
|
},
|
|
{
|
|
'category': 'SAVINGS',
|
|
'name': 'Savings',
|
|
'amountPlanned': 500000.0,
|
|
'description': 'Savings collection',
|
|
},
|
|
],
|
|
};
|
|
|
|
final response = await client.post(
|
|
url,
|
|
headers: authHelper.getAuthHeaders(),
|
|
body: json.encode(requestBody),
|
|
);
|
|
|
|
expect(response.statusCode, inInclusiveRange(200, 201), reason: 'HTTP 200/201 expected');
|
|
|
|
final budget = json.decode(response.body);
|
|
expect(budget['id'], isNotNull, reason: 'Budget ID must be present');
|
|
expect(budget['name'], contains('Budget Integration Test'), reason: 'Name must match');
|
|
|
|
createdBudgetId = budget['id'];
|
|
print('POST create budget: ${budget['id']} - ${budget['name']}');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('GET /api/finance/budgets/{id} - Get budget by ID', () async {
|
|
String budgetId;
|
|
|
|
if (createdBudgetId != null) {
|
|
budgetId = createdBudgetId!;
|
|
} else {
|
|
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('No budgets found - test skipped');
|
|
return;
|
|
}
|
|
|
|
budgetId = budgets.first['id'];
|
|
}
|
|
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets/$budgetId');
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 200, reason: 'HTTP 200 OK expected');
|
|
|
|
final budget = json.decode(response.body);
|
|
expect(budget['id'], equals(budgetId), reason: 'ID must match');
|
|
expect(budget['lines'], isNotNull, reason: 'Budget lines must be present');
|
|
|
|
print('GET budget by ID: ${budget['id']} - ${budget['name']}');
|
|
print(' Budget lines: ${budget['lines'].length}');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
});
|
|
|
|
group('Finance Workflow - Negative Tests', () {
|
|
test('GET nonexistent approval - Should return 404', () async {
|
|
final fakeId = '00000000-0000-0000-0000-000000000000';
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/$fakeId');
|
|
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found expected');
|
|
|
|
print('Negative test: 404 for nonexistent approval');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('GET nonexistent budget - Should return 404', () async {
|
|
final fakeId = '00000000-0000-0000-0000-000000000000';
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets/$fakeId');
|
|
|
|
final response = await client.get(url, headers: authHelper.getAuthHeaders());
|
|
|
|
expect(response.statusCode, 404, reason: 'HTTP 404 Not Found expected');
|
|
|
|
print('Negative test: 404 for nonexistent budget');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
|
|
test('POST budget without authentication - Should return 401', () async {
|
|
final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets');
|
|
final requestBody = {
|
|
'name': 'Budget Without Auth',
|
|
'organizationId': TestConfig.testOrganizationId,
|
|
'period': 'ANNUAL',
|
|
'year': 2026,
|
|
'lines': [],
|
|
};
|
|
|
|
final response = await client.post(
|
|
url,
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: json.encode(requestBody),
|
|
);
|
|
|
|
expect(response.statusCode, 401, reason: 'HTTP 401 Unauthorized expected');
|
|
|
|
print('Negative test: 401 for unauthenticated request');
|
|
|
|
await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests));
|
|
});
|
|
});
|
|
}
|