swiftops-backend / tests /integration /test_admin_edit_profiles.js
kamau1's picture
feat: user search by email and phone
eae7030
#!/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=<email> endpoint', 'cyan');
log(' 2. Run: node test_accept_invitation.js <token>', '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);
});