#!/usr/bin/env node /** * Admin Profile Management Integration Test * Tests platform admin editing field agent profiles * * Flow: * 1. Login as platform admin (Lewis) * 2. Get Lesley's profile * 3. Update Lesley's basic info, health info, PPE sizes * 4. Validate Lesley's profile completion * 5. Get Irene's profile (if exists) * 6. Update Irene's profile * 7. Verify audit logs * * Usage: node tests/integration/test_admin_edit_profiles.js */ const https = require('https'); const BASE_URL = 'https://kamau1-swiftops-backend.hf.space'; // Admin credentials const ADMIN_EMAIL = 'lewis.kamau421@gmail.com'; const ADMIN_PASSWORD = 'TestPass123'; // Note: Password requires special character // Field agents to manage const LESLEY_EMAIL = 'lesley@example.com'; const IRENE_EMAIL = 'irene@example.com'; // If she accepted invitation // Store data between tests let adminToken = null; let lesleyUserId = null; let ireneUserId = null; // Colors for console output const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[36m', magenta: '\x1b[35m', cyan: '\x1b[96m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function makeRequest(method, path, data = null, token = null, timeout = 30000) { return new Promise((resolve, reject) => { const url = new URL(path, BASE_URL); const options = { method, headers: { 'Content-Type': 'application/json', }, timeout: timeout }; if (token) { options.headers['Authorization'] = `Bearer ${token}`; } log(` → ${method} ${path}`, 'yellow'); const req = https.request(url, options, (res) => { let body = ''; res.on('data', chunk => body += chunk); res.on('end', () => { try { const response = { status: res.statusCode, data: body ? JSON.parse(body) : null }; log(` ← ${res.statusCode} ${res.statusMessage}`, 'yellow'); resolve(response); } catch (e) { log(` ← ${res.statusCode} (non-JSON response)`, 'yellow'); resolve({ status: res.statusCode, data: body }); } }); }); req.on('error', (error) => { log(` ← Error: ${error.message}`, 'red'); reject(error); }); req.on('timeout', () => { req.destroy(); const error = new Error(`Request timeout after ${timeout}ms`); log(` ← Timeout after ${timeout}ms`, 'red'); reject(error); }); if (data) { req.write(JSON.stringify(data)); } req.end(); }); } async function step1_LoginAsAdmin() { log('\n🔐 Step 1: Login as Platform Admin (Lewis)', 'blue'); try { const response = await makeRequest('POST', '/api/v1/auth/login', { email: ADMIN_EMAIL, password: ADMIN_PASSWORD }); if (response.status === 200) { adminToken = response.data.access_token; log('✅ Admin login successful', 'green'); log(` Admin: ${response.data.user.email}`); log(` Name: ${response.data.user.full_name}`); log(` Role: ${response.data.user.role || 'platform_admin'}`); log(` Token: ${adminToken.substring(0, 30)}...`); return true; } else { log(`❌ Login failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step2_FindLesleyUser() { log('\n🔍 Step 2: Find Lesley\'s User Account', 'blue'); if (!adminToken) { log('❌ No admin token available', 'red'); return false; } try { // Try to get user by email (assuming we have a users list endpoint) // For now, we'll use the invitation to find the user const response = await makeRequest('GET', '/api/v1/invitations?email=' + LESLEY_EMAIL, null, adminToken); if (response.status === 200 && response.data.invitations.length > 0) { const invitation = response.data.invitations[0]; log('✅ Found Lesley\'s invitation', 'green'); log(` Email: ${invitation.email}`); log(` Phone: ${invitation.phone}`); log(` Role: ${invitation.invited_role}`); log(` Status: ${invitation.status}`); if (invitation.status === 'accepted') { log(' ✓ Invitation accepted - user account exists', 'green'); // We'll need to get the user ID from the profile endpoint return { found: true, email: invitation.email }; } else { log(' ⚠️ Invitation not yet accepted', 'yellow'); return { found: false, reason: 'not_accepted' }; } } else { log(`❌ Could not find Lesley's invitation`, 'red'); return { found: false, reason: 'not_found' }; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return { found: false, reason: 'error' }; } } async function step3_GetLesleyProfile() { log('\n👤 Step 3: Get Lesley\'s Complete Profile', 'blue'); if (!adminToken) { log('❌ No admin token available', 'red'); return false; } try { // First, we need to find Lesley's user ID // Since we don't have a direct user search endpoint yet, we'll use a workaround // In production, you'd have GET /api/v1/users?email=lesley@example.com log(' Note: Using workaround to find user ID', 'yellow'); log(' In production, use: GET /api/v1/users?email=lesley@example.com', 'yellow'); // For now, let's assume we have the user ID from the invitation acceptance // We'll demonstrate the profile endpoints with a placeholder log('\n 📋 Profile Endpoints Available:', 'cyan'); log(' GET /api/v1/profile/{user_id} - Get user profile', 'cyan'); log(' GET /api/v1/profile/{user_id}/permissions - Check edit permissions', 'cyan'); log(' GET /api/v1/profile/{user_id}/validation - Validate profile', 'cyan'); log(' PUT /api/v1/profile/{user_id}/basic - Update basic info', 'cyan'); log(' PUT /api/v1/profile/{user_id}/ppe - Update PPE sizes', 'cyan'); return { found: false, reason: 'need_user_id' }; } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step4_UpdateLesleyBasicInfo(userId) { log('\n✏️ Step 4: Update Lesley\'s Basic Profile', 'blue'); if (!adminToken || !userId) { log('❌ Missing admin token or user ID', 'red'); return false; } try { const response = await makeRequest('PUT', `/api/v1/profile/${userId}/basic`, { emergency_contact_name: 'Jane Wanjiru', emergency_contact_phone: '+254723456789', phone_alternate: '+254798765432' }, adminToken); if (response.status === 200) { log('✅ Basic profile updated successfully', 'green'); log(` Name: ${response.data.name}`); log(` Phone: ${response.data.phone}`); log(` Alternate Phone: ${response.data.phone_alternate}`); log(` Emergency Contact: ${response.data.emergency_contact_name}`); log(` Emergency Phone: ${response.data.emergency_contact_phone}`); return true; } else { log(`❌ Update failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step5_UpdateLesleyHealthInfo(userId) { log('\n🏥 Step 5: Update Lesley\'s Health Information', 'blue'); if (!adminToken || !userId) { log('❌ Missing admin token or user ID', 'red'); return false; } try { const response = await makeRequest('PUT', `/api/v1/profile/${userId}/health`, { blood_type: 'O+', allergies: 'None', chronic_conditions: 'None', medications: 'None', last_medical_check: '2024-01-15', medical_notes: 'Fit for field work' }, adminToken); if (response.status === 200) { log('✅ Health info updated successfully', 'green'); log(` Blood Type: ${response.data.blood_type}`); log(` Allergies: ${response.data.allergies}`); log(` Chronic Conditions: ${response.data.chronic_conditions}`); log(` Last Medical Check: ${response.data.last_medical_check}`); return true; } else { log(`❌ Update failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step6_UpdateLesleyPPESizes(userId) { log('\n👷 Step 6: Update Lesley\'s PPE Sizes', 'blue'); if (!adminToken || !userId) { log('❌ Missing admin token or user ID', 'red'); return false; } try { const response = await makeRequest('PUT', `/api/v1/profile/${userId}/ppe`, { height: '165cm', weight: '60kg', shirt_size: 'M', pants_size: '30', shoe_size: '38', helmet_size: 'M', glove_size: 'M', vest_size: 'M' }, adminToken); if (response.status === 200) { log('✅ PPE sizes updated successfully', 'green'); log(` Height: ${response.data.height}`); log(` Weight: ${response.data.weight}`); log(` Shirt: ${response.data.shirt_size}`); log(` Pants: ${response.data.pants_size}`); log(` Shoes: ${response.data.shoe_size}`); log(` Helmet: ${response.data.helmet_size}`); log(` Gloves: ${response.data.glove_size}`); log(` Vest: ${response.data.vest_size}`); return true; } else { log(`❌ Update failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step7_ValidateLesleyProfile(userId) { log('\n✅ Step 7: Validate Lesley\'s Profile Completion', 'blue'); if (!adminToken || !userId) { log('❌ Missing admin token or user ID', 'red'); return false; } try { const response = await makeRequest('GET', `/api/v1/profile/${userId}/validation`, null, adminToken); if (response.status === 200) { log('✅ Profile validation retrieved', 'green'); log(` Is Valid: ${response.data.is_valid}`); log(` Completion: ${response.data.is_valid ? '100%' : 'Incomplete'}`); if (response.data.missing_fields && response.data.missing_fields.length > 0) { log(` Missing Fields: ${response.data.missing_fields.join(', ')}`, 'yellow'); } if (response.data.invalid_fields && Object.keys(response.data.invalid_fields).length > 0) { log(` Invalid Fields:`, 'red'); Object.entries(response.data.invalid_fields).forEach(([field, error]) => { log(` - ${field}: ${error}`, 'red'); }); } if (response.data.warnings && response.data.warnings.length > 0) { log(` Warnings:`, 'yellow'); response.data.warnings.forEach(warning => { log(` - ${warning}`, 'yellow'); }); } return true; } else { log(`❌ Validation failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function step8_CheckEditPermissions(userId) { log('\n🔒 Step 8: Check Admin\'s Edit Permissions for Lesley', 'blue'); if (!adminToken || !userId) { log('❌ Missing admin token or user ID', 'red'); return false; } try { const response = await makeRequest('GET', `/api/v1/profile/${userId}/permissions`, null, adminToken); if (response.status === 200) { log('✅ Permissions retrieved', 'green'); log(` Can Edit Basic Info: ${response.data.can_edit_basic_info}`); log(` Can Edit Health Info: ${response.data.can_edit_health_info}`); log(` Can Edit PPE Sizes: ${response.data.can_edit_ppe_sizes}`); log(` Can Edit Location: ${response.data.can_edit_location}`); log(` Can Edit Financial Accounts: ${response.data.can_edit_financial_accounts}`); log(` Can Upload Documents: ${response.data.can_upload_documents}`); log(` Can Edit Role: ${response.data.can_edit_role}`); log(` Can Edit Status: ${response.data.can_edit_status}`); log(` Can Assign Assets: ${response.data.can_assign_assets}`); return true; } else { log(`❌ Get permissions failed: ${response.status}`, 'red'); log(` ${JSON.stringify(response.data, null, 2)}`); return false; } } catch (error) { log(`❌ Error: ${error.message}`, 'red'); return false; } } async function demonstrateWorkflow() { log('\n📚 DEMONSTRATION: Admin Profile Management Workflow', 'magenta'); log('='.repeat(70), 'magenta'); log('\n1️⃣ FIND USER', 'cyan'); log(' GET /api/v1/users?email=lesley@example.com', 'cyan'); log(' → Returns user_id', 'cyan'); log('\n2️⃣ CHECK PERMISSIONS', 'cyan'); log(' GET /api/v1/profile/{user_id}/permissions', 'cyan'); log(' → Verify admin can edit this user', 'cyan'); log('\n3️⃣ GET CURRENT PROFILE', 'cyan'); log(' GET /api/v1/profile/{user_id}', 'cyan'); log(' → See current profile data and completion status', 'cyan'); log('\n4️⃣ UPDATE SECTIONS', 'cyan'); log(' PUT /api/v1/profile/{user_id}/basic', 'cyan'); log(' PUT /api/v1/profile/{user_id}/health', 'cyan'); log(' PUT /api/v1/profile/{user_id}/ppe', 'cyan'); log(' → Update each section as needed', 'cyan'); log('\n5️⃣ VALIDATE COMPLETION', 'cyan'); log(' GET /api/v1/profile/{user_id}/validation', 'cyan'); log(' → Check if profile is complete and valid', 'cyan'); log('\n6️⃣ VIEW AUDIT LOGS', 'cyan'); log(' GET /api/v1/audit-logs?entity_type=user&entity_id={user_id}', 'cyan'); log(' → See who changed what and when', 'cyan'); log('\n' + '='.repeat(70), 'magenta'); } async function showExampleRequests() { log('\n📝 EXAMPLE API REQUESTS', 'magenta'); log('='.repeat(70), 'magenta'); log('\n🔹 Update Basic Info:', 'cyan'); log(`curl -X PUT ${BASE_URL}/api/v1/profile/{user_id}/basic \\`, 'yellow'); log(` -H "Authorization: Bearer {admin_token}" \\`, 'yellow'); log(` -H "Content-Type: application/json" \\`, 'yellow'); log(` -d '{`, 'yellow'); log(` "emergency_contact_name": "Jane Wanjiru",`, 'yellow'); log(` "emergency_contact_phone": "+254723456789",`, 'yellow'); log(` "phone_alternate": "+254798765432"`, 'yellow'); log(` }'`, 'yellow'); log('\n🔹 Update Health Info:', 'cyan'); log(`curl -X PUT ${BASE_URL}/api/v1/profile/{user_id}/health \\`, 'yellow'); log(` -H "Authorization: Bearer {admin_token}" \\`, 'yellow'); log(` -H "Content-Type: application/json" \\`, 'yellow'); log(` -d '{`, 'yellow'); log(` "blood_type": "O+",`, 'yellow'); log(` "allergies": "None",`, 'yellow'); log(` "chronic_conditions": "None"`, 'yellow'); log(` }'`, 'yellow'); log('\n🔹 Update PPE Sizes:', 'cyan'); log(`curl -X PUT ${BASE_URL}/api/v1/profile/{user_id}/ppe \\`, 'yellow'); log(` -H "Authorization: Bearer {admin_token}" \\`, 'yellow'); log(` -H "Content-Type: application/json" \\`, 'yellow'); log(` -d '{`, 'yellow'); log(` "height": "165cm",`, 'yellow'); log(` "shirt_size": "M",`, 'yellow'); log(` "shoe_size": "38",`, 'yellow'); log(` "helmet_size": "M"`, 'yellow'); log(` }'`, 'yellow'); log('\n' + '='.repeat(70), 'magenta'); } async function testConnectivity() { log('\n🔌 Testing Server Connectivity...', 'blue'); try { const response = await makeRequest('GET', '/health', null, null, 10000); if (response.status === 200) { log('✅ Server is reachable', 'green'); return true; } else { log(`⚠️ Server responded with status ${response.status}`, 'yellow'); return true; } } catch (error) { log(`❌ Cannot reach server: ${error.message}`, 'red'); return false; } } async function runAdminProfileTest() { log('='.repeat(70), 'blue'); log('👨‍💼 SwiftOps Admin Profile Management Test', 'blue'); log(`📍 Server: ${BASE_URL}`, 'blue'); log(`👤 Admin: ${ADMIN_EMAIL}`, 'blue'); log(`🎯 Managing: Lesley Wanjiru (Field Agent)`, 'blue'); log('='.repeat(70), 'blue'); // Test connectivity const canConnect = await testConnectivity(); if (!canConnect) { log('\n❌ Cannot proceed without server connectivity', 'red'); process.exit(1); } const results = []; // Step 1: Login as admin const loginResult = await step1_LoginAsAdmin(); results.push({ name: 'Login as Admin', status: loginResult }); if (!loginResult) { log('\n❌ Cannot continue without admin login', 'red'); printSummary(results); process.exit(1); } // Step 2: Find Lesley's user const findResult = await step2_FindLesleyUser(); results.push({ name: 'Find Lesley\'s Account', status: findResult.found }); // Step 3: Get profile (demonstration) const profileResult = await step3_GetLesleyProfile(); results.push({ name: 'Get Profile (Demo)', status: true }); // Show workflow and examples await demonstrateWorkflow(); await showExampleRequests(); printSummary(results); } function printSummary(results) { log('\n' + '='.repeat(70), 'blue'); log('📊 Test Summary', 'blue'); log('='.repeat(70), 'blue'); let passed = 0; let failed = 0; results.forEach(result => { const icon = result.status ? '✅' : '❌'; const color = result.status ? 'green' : 'red'; log(`${icon} ${result.name}`, color); if (result.status) passed++; else failed++; }); log('\n' + '-'.repeat(70)); log(`Total: ${results.length} | Passed: ${passed} | Failed: ${failed}`); log('\n📌 IMPORTANT NOTES:', 'magenta'); log(' 1. To complete this test, you need Lesley\'s user_id', 'magenta'); log(' 2. Get user_id from invitation acceptance or user search endpoint', 'magenta'); log(' 3. Then use the example curl commands above with the actual user_id', 'magenta'); log(' 4. All profile changes are logged in audit_logs table', 'magenta'); log('\n🎯 NEXT STEPS:', 'cyan'); log(' 1. Implement GET /api/v1/users?email= endpoint', 'cyan'); log(' 2. Run: node test_accept_invitation.js ', 'cyan'); log(' 3. Get Lesley\'s user_id from acceptance response', 'cyan'); log(' 4. Use the profile management endpoints shown above', 'cyan'); if (failed === 0) { log('\n✅ Admin authentication successful!', 'green'); log(' Ready to manage user profiles', 'green'); } } // Run the test runAdminProfileTest().catch(error => { log(`\n💥 Fatal error: ${error.message}`, 'red'); console.error(error); process.exit(1); });