const axios = require('axios'); const API_URL = 'http://localhost:3000/api'; // Test users const testUser = { username: 'profiletest', email: 'profiletest@example.com', password: 'Test123!@#' }; const secondUser = { username: 'profiletest2', email: 'profiletest2@example.com', password: 'Test123!@#' }; let userToken; let userId; let secondUserToken; let secondUserId; // Test setup async function setup() { console.log('Setting up test data...\n'); try { // Register/login first user try { const registerRes = await axios.post(`${API_URL}/auth/register`, testUser); userToken = registerRes.data.data.token; userId = registerRes.data.data.user.id; console.log('✓ First user registered'); } catch (error) { const loginRes = await axios.post(`${API_URL}/auth/login`, { email: testUser.email, password: testUser.password }); userToken = loginRes.data.data.token; userId = loginRes.data.data.user.id; console.log('✓ First user logged in'); } // Register/login second user try { const registerRes = await axios.post(`${API_URL}/auth/register`, secondUser); secondUserToken = registerRes.data.data.token; secondUserId = registerRes.data.data.user.id; console.log('✓ Second user registered'); } catch (error) { const loginRes = await axios.post(`${API_URL}/auth/login`, { email: secondUser.email, password: secondUser.password }); secondUserToken = loginRes.data.data.token; secondUserId = loginRes.data.data.user.id; console.log('✓ Second user logged in'); } console.log(''); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); throw error; } } // Tests const tests = [ { name: 'Test 1: Update username successfully', run: async () => { const newUsername = 'profiletestupdated'; // No underscore - alphanumeric only const response = await axios.put(`${API_URL}/users/${userId}`, { username: newUsername }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Request failed'); if (response.data.data.user.username !== newUsername) { throw new Error('Username not updated'); } if (!response.data.data.changedFields.includes('username')) { throw new Error('changedFields missing username'); } // Revert username await axios.put(`${API_URL}/users/${userId}`, { username: testUser.username }, { headers: { 'Authorization': `Bearer ${userToken}` } }); return '✓ Username update works'; } }, { name: 'Test 2: Update email successfully', run: async () => { const newEmail = 'profiletestnew@example.com'; const response = await axios.put(`${API_URL}/users/${userId}`, { email: newEmail }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Request failed'); if (response.data.data.user.email !== newEmail) { throw new Error('Email not updated'); } if (!response.data.data.changedFields.includes('email')) { throw new Error('changedFields missing email'); } // Revert email immediately and verify const revertResponse = await axios.put(`${API_URL}/users/${userId}`, { email: testUser.email }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (revertResponse.data.data.user.email !== testUser.email) { throw new Error(`Email revert failed. Expected: ${testUser.email}, Got: ${revertResponse.data.data.user.email}`); } // Small delay to ensure database write completes await new Promise(resolve => setTimeout(resolve, 200)); return '✓ Email update works'; } }, { name: 'Test 3: Update password successfully', run: async () => { const newPassword = 'NewPass123!@#'; // Skip the verification login - the token should still be valid // Just proceed with the password change const response = await axios.put(`${API_URL}/users/${userId}`, { currentPassword: testUser.password, newPassword: newPassword }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Password update request failed'); if (!response.data.data.changedFields.includes('password')) { throw new Error('changedFields missing password'); } // Test login with new password try { const loginRes = await axios.post(`${API_URL}/auth/login`, { email: testUser.email, password: newPassword }); if (!loginRes.data.success) throw new Error('Login with new password failed'); userToken = loginRes.data.data.token; // Update token } catch (err) { throw new Error(`Login with new password failed. Email: ${testUser.email}, NewPwd: ${newPassword}, Error: ${err.response?.data?.message || err.message}`); } // Revert password await axios.put(`${API_URL}/users/${userId}`, { currentPassword: newPassword, newPassword: testUser.password }, { headers: { 'Authorization': `Bearer ${userToken}` } }); // Get new token with original password const loginRes2 = await axios.post(`${API_URL}/auth/login`, { email: testUser.email, password: testUser.password }); userToken = loginRes2.data.data.token; return '✓ Password update works'; } }, { name: 'Test 4: Update profile image successfully', run: async () => { const imageUrl = 'https://example.com/profile.jpg'; const response = await axios.put(`${API_URL}/users/${userId}`, { profileImage: imageUrl }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Request failed'); if (response.data.data.user.profileImage !== imageUrl) { throw new Error('Profile image not updated'); } if (!response.data.data.changedFields.includes('profileImage')) { throw new Error('changedFields missing profileImage'); } return '✓ Profile image update works'; } }, { name: 'Test 5: Remove profile image (set to null)', run: async () => { const response = await axios.put(`${API_URL}/users/${userId}`, { profileImage: null }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Request failed'); if (response.data.data.user.profileImage !== null) { throw new Error('Profile image not removed'); } return '✓ Profile image removal works'; } }, { name: 'Test 6: Update multiple fields at once', run: async () => { const updates = { username: 'multifieldtest', email: 'multifield@example.com', profileImage: 'https://example.com/multi.jpg' }; const response = await axios.put(`${API_URL}/users/${userId}`, updates, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (!response.data.success) throw new Error('Request failed'); if (response.data.data.user.username !== updates.username) { throw new Error('Username not updated'); } if (response.data.data.user.email !== updates.email) { throw new Error('Email not updated'); } if (response.data.data.user.profileImage !== updates.profileImage) { throw new Error('Profile image not updated'); } if (response.data.data.changedFields.length !== 3) { throw new Error('Should have 3 changed fields'); } // Revert await axios.put(`${API_URL}/users/${userId}`, { username: testUser.username, email: testUser.email, profileImage: null }, { headers: { 'Authorization': `Bearer ${userToken}` } }); return '✓ Multiple field update works'; } }, { name: 'Test 7: Reject duplicate username', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { username: secondUser.username // Try to use second user's username }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 409) { throw new Error(`Expected 409, got ${error.response?.status}`); } if (!error.response.data.message.toLowerCase().includes('username')) { throw new Error('Error message should mention username'); } return '✓ Duplicate username rejected'; } } }, { name: 'Test 8: Reject duplicate email', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { email: secondUser.email // Try to use second user's email }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 409) { throw new Error(`Expected 409, got ${error.response?.status}`); } if (!error.response.data.message.toLowerCase().includes('email')) { throw new Error('Error message should mention email'); } return '✓ Duplicate email rejected'; } } }, { name: 'Test 9: Reject invalid email format', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { email: 'invalid-email-format' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Invalid email rejected'; } } }, { name: 'Test 10: Reject short username', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { username: 'ab' // Too short (min 3) }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Short username rejected'; } } }, { name: 'Test 11: Reject invalid username characters', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { username: 'user@name!' // Invalid characters }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Invalid username characters rejected'; } } }, { name: 'Test 12: Reject short password', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { currentPassword: testUser.password, newPassword: '12345' // Too short (min 6) }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { // Controller validates current password first (returns 401 if wrong) // Then validates new password length (returns 400) // Since we're providing correct current password, we should get 400 if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Short password rejected'; } } }, { name: 'Test 13: Reject password change without current password', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { newPassword: 'NewPassword123' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } if (!error.response.data.message.toLowerCase().includes('current password')) { throw new Error('Error should mention current password'); } return '✓ Password change without current password rejected'; } } }, { name: 'Test 14: Reject incorrect current password', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { currentPassword: 'WrongPassword123', newPassword: 'NewPassword123' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 401) { throw new Error(`Expected 401, got ${error.response?.status}`); } if (!error.response.data.message.toLowerCase().includes('incorrect')) { throw new Error('Error should mention incorrect password'); } return '✓ Incorrect current password rejected'; } } }, { name: 'Test 15: Reject empty update (no fields provided)', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, {}, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } if (!error.response.data.message.toLowerCase().includes('no fields')) { throw new Error('Error should mention no fields'); } return '✓ Empty update rejected'; } } }, { name: 'Test 16: Cross-user update blocked', run: async () => { try { await axios.put(`${API_URL}/users/${secondUserId}`, { username: 'hackedusername' }, { headers: { 'Authorization': `Bearer ${userToken}` } // Using first user's token }); throw new Error('Should have been blocked'); } catch (error) { if (error.response?.status !== 403) { throw new Error(`Expected 403, got ${error.response?.status}`); } return '✓ Cross-user update blocked'; } } }, { name: 'Test 17: Unauthenticated request blocked', run: async () => { try { await axios.put(`${API_URL}/users/${userId}`, { username: 'newusername' }); throw new Error('Should have been blocked'); } catch (error) { if (error.response?.status !== 401) { throw new Error(`Expected 401, got ${error.response?.status}`); } return '✓ Unauthenticated blocked'; } } }, { name: 'Test 18: Invalid UUID format returns 400', run: async () => { try { await axios.put(`${API_URL}/users/invalid-uuid`, { username: 'newusername' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Invalid UUID returns 400'; } } }, { name: 'Test 19: Non-existent user returns 404', run: async () => { try { const fakeUuid = '00000000-0000-0000-0000-000000000000'; await axios.put(`${API_URL}/users/${fakeUuid}`, { username: 'newusername' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed'); } catch (error) { if (error.response?.status !== 404) { throw new Error(`Expected 404, got ${error.response?.status}`); } return '✓ Non-existent user returns 404'; } } }, { name: 'Test 20: Profile image URL too long rejected', run: async () => { try { const longUrl = 'https://example.com/' + 'a'.repeat(250); // Over 255 chars await axios.put(`${API_URL}/users/${userId}`, { profileImage: longUrl }, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have been rejected'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Long profile image URL rejected'; } } }, { name: 'Test 21: Response excludes password field', run: async () => { const response = await axios.put(`${API_URL}/users/${userId}`, { username: 'passwordtest' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (response.data.data.user.password !== undefined) { throw new Error('Password should not be in response'); } // Revert await axios.put(`${API_URL}/users/${userId}`, { username: testUser.username }, { headers: { 'Authorization': `Bearer ${userToken}` } }); return '✓ Password excluded from response'; } } ]; // Run tests async function runTests() { console.log('============================================================'); console.log('UPDATE USER PROFILE API TESTS'); console.log('============================================================\n'); await setup(); console.log('Running tests...\n'); let passed = 0; let failed = 0; for (const test of tests) { try { const result = await test.run(); console.log(result); passed++; } catch (error) { console.log(`✗ ${test.name}`); console.log(` Error: ${error.message}`); if (error.response?.data) { console.log(` Response:`, JSON.stringify(error.response.data, null, 2)); } failed++; } // Small delay to avoid rate limiting await new Promise(resolve => setTimeout(resolve, 100)); } console.log('\n============================================================'); console.log(`RESULTS: ${passed} passed, ${failed} failed out of ${tests.length} tests`); console.log('============================================================'); process.exit(failed > 0 ? 1 : 0); } runTests();