/// 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 approvals = json.decode(response.body); expect(approvals, isA(), 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 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 budgets = json.decode(response.body); expect(budgets, isA(), 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 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)); }); }); }