add a lot of changes

This commit is contained in:
AD2025
2025-11-12 00:49:22 +02:00
parent e3ca132c5e
commit a5fac2aaf7
17 changed files with 6704 additions and 1 deletions

View File

@@ -0,0 +1,595 @@
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();