/// Integration tests for Finance Workflow API (Pure Dart - no Flutter binding) library finance_workflow_api_test; import 'dart:convert'; import 'package:test/test.dart'; import 'package:http/http.dart' as http; import 'helpers/test_config.dart'; import 'helpers/auth_helper.dart'; void main() { late http.Client client; late AuthHelper authHelper; setUpAll(() async { print('\n╔═══════════════════════════════════════════════╗'); print('║ Finance Workflow Integration Tests (API) ║'); print('╚═══════════════════════════════════════════════╝\n'); client = http.Client(); authHelper = AuthHelper(client); print('→ Authenticating as ${TestConfig.testOrgAdminUsername}...'); final authenticated = await authHelper.authenticateAsOrgAdmin(); if (!authenticated) { print('✗ Authentication FAILED'); throw Exception('Authentication failed - check Keycloak and credentials'); } print('✓ Authentication successful\n'); }); tearDownAll(() { client.close(); print('\n╔═══════════════════════════════════════════════╗'); print('║ Integration Tests Completed ║'); print('╚═══════════════════════════════════════════════╝\n'); }); group('Finance Workflow - Approvals API', () { test('GET /api/finance/approvals/pending - List pending approvals', () async { print('\n[TEST] GET pending approvals...'); 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, equals(200), reason: 'HTTP 200 OK expected'); final List approvals = json.decode(response.body); expect(approvals, isA()); print(' ✓ Status: ${response.statusCode}'); print(' ✓ Approvals found: ${approvals.length}'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); test('GET /api/finance/approvals/{id} - Get approval by ID', () async { print('\n[TEST] GET approval by ID...'); // First, get list of approvals 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, equals(200)); final List approvals = json.decode(listResponse.body); if (approvals.isEmpty) { print(' ⚠ No pending approvals - test skipped'); return; } final approvalId = approvals.first['id']; // Get specific approval final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/approvals/$approvalId'); final response = await client.get(url, headers: authHelper.getAuthHeaders()); expect(response.statusCode, equals(200)); final approval = json.decode(response.body); expect(approval['id'], equals(approvalId)); print(' ✓ Status: ${response.statusCode}'); print(' ✓ Approval ID: ${approval['id']}'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); }); group('Finance Workflow - Budgets API', () { String? createdBudgetId; test('GET /api/finance/budgets - List budgets', () async { print('\n[TEST] GET budgets list...'); 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, equals(200)); final List budgets = json.decode(response.body); expect(budgets, isA()); print(' ✓ Status: ${response.statusCode}'); print(' ✓ Budgets found: ${budgets.length}'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); test('POST /api/finance/budgets - Create budget', () async { print('\n[TEST] POST create budget...'); final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets'); final timestamp = DateTime.now().millisecondsSinceEpoch; final requestBody = { 'name': 'Budget Integration Test $timestamp', 'description': 'Budget created by automated integration test', 'organizationId': TestConfig.testOrganizationId, 'period': 'ANNUAL', 'year': DateTime.now().year, 'lines': [ { 'category': 'CONTRIBUTIONS', 'name': 'Member Contributions', 'amountPlanned': 1000000.0, 'description': 'Monthly member contributions', }, { 'category': 'SAVINGS', 'name': 'Savings Deposits', 'amountPlanned': 500000.0, 'description': 'Member savings deposits', }, ], }; final response = await client.post( url, headers: authHelper.getAuthHeaders(), body: json.encode(requestBody), ); expect(response.statusCode, inInclusiveRange(200, 201)); final budget = json.decode(response.body); expect(budget['id'], isNotNull); expect(budget['name'], contains('Budget Integration Test')); createdBudgetId = budget['id']; print(' ✓ Status: ${response.statusCode}'); print(' ✓ Budget created: ${budget['id']}'); print(' ✓ Budget name: ${budget['name']}'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); test('GET /api/finance/budgets/{id} - Get budget by ID', () async { print('\n[TEST] GET budget by ID...'); String budgetId; if (createdBudgetId != null) { budgetId = createdBudgetId!; } else { // Fetch any existing budget 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, equals(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, equals(200)); final budget = json.decode(response.body); expect(budget['id'], equals(budgetId)); expect(budget['lines'], isNotNull); print(' ✓ Status: ${response.statusCode}'); print(' ✓ Budget ID: ${budget['id']}'); print(' ✓ Budget name: ${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 { print('\n[TEST] GET nonexistent approval (expect 404)...'); 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, equals(404)); print(' ✓ Status: ${response.statusCode} (404 as expected)'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); test('GET nonexistent budget - Should return 404', () async { print('\n[TEST] GET nonexistent budget (expect 404)...'); 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, equals(404)); print(' ✓ Status: ${response.statusCode} (404 as expected)'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); test('POST budget without auth - Should return 401', () async { print('\n[TEST] POST budget without authentication (expect 401)...'); final url = Uri.parse('${TestConfig.apiBaseUrl}/api/finance/budgets'); final requestBody = { 'name': 'Unauthorized Budget', '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, equals(401)); print(' ✓ Status: ${response.statusCode} (401 as expected)'); await Future.delayed(Duration(milliseconds: TestConfig.delayBetweenTests)); }); }); }