add changes

This commit is contained in:
AD2025
2025-11-12 23:06:27 +02:00
parent c664d0a341
commit ec6534fcc2
42 changed files with 11854 additions and 299 deletions

411
backend/test-bookmarks.js Normal file
View File

@@ -0,0 +1,411 @@
const axios = require('axios');
const API_URL = 'http://localhost:3000/api';
// Test users
const testUser = {
username: 'bookmarktest',
email: 'bookmarktest@example.com',
password: 'Test123!@#'
};
const secondUser = {
username: 'bookmarktest2',
email: 'bookmarktest2@example.com',
password: 'Test123!@#'
};
let userToken;
let userId;
let secondUserToken;
let secondUserId;
let questionId; // Will get from a real question
let categoryId; // Will get from a real category
// Test setup
async function setup() {
console.log('Setting up test data...\n');
try {
// Register/login 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('✓ Test 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('✓ Test 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');
}
// Get a real category with questions
const categoriesRes = await axios.get(`${API_URL}/categories`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (!categoriesRes.data.data || categoriesRes.data.data.length === 0) {
throw new Error('No categories available for testing');
}
// Find a category that has questions
let foundQuestion = false;
for (const category of categoriesRes.data.data) {
if (category.questionCount > 0) {
categoryId = category.id;
console.log(`✓ Found test category: ${category.name} (${category.questionCount} questions)`);
// Get a real question from that category
const questionsRes = await axios.get(`${API_URL}/questions/category/${categoryId}?limit=1`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (questionsRes.data.data.length > 0) {
questionId = questionsRes.data.data[0].id;
console.log(`✓ Found test question`);
foundQuestion = true;
break;
}
}
}
if (!foundQuestion) {
throw new Error('No questions available for testing. Please seed the database first.');
}
console.log('');
} catch (error) {
console.error('Setup failed:', error.response?.data || error.message);
throw error;
}
}
// Tests
const tests = [
{
name: 'Test 1: Add bookmark successfully',
run: async () => {
const response = await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId
}, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (!response.data.success) throw new Error('Request failed');
if (response.status !== 201) throw new Error(`Expected 201, got ${response.status}`);
if (!response.data.data.id) throw new Error('Missing bookmark ID');
if (response.data.data.questionId !== questionId) {
throw new Error('Question ID mismatch');
}
if (!response.data.data.bookmarkedAt) throw new Error('Missing bookmarkedAt timestamp');
if (!response.data.data.question) throw new Error('Missing question details');
return '✓ Bookmark added successfully';
}
},
{
name: 'Test 2: Reject duplicate bookmark',
run: async () => {
try {
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId
}, {
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('already')) {
throw new Error('Error message should mention already bookmarked');
}
return '✓ Duplicate bookmark rejected';
}
}
},
{
name: 'Test 3: Remove bookmark successfully',
run: async () => {
const response = await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (!response.data.success) throw new Error('Request failed');
if (response.status !== 200) throw new Error(`Expected 200, got ${response.status}`);
if (response.data.data.questionId !== questionId) {
throw new Error('Question ID mismatch');
}
return '✓ Bookmark removed successfully';
}
},
{
name: 'Test 4: Reject removing non-existent bookmark',
run: async () => {
try {
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
throw new Error('Should have been rejected');
} catch (error) {
if (error.response?.status !== 404) {
throw new Error(`Expected 404, got ${error.response?.status}`);
}
if (!error.response.data.message.toLowerCase().includes('not found')) {
throw new Error('Error message should mention not found');
}
return '✓ Non-existent bookmark rejected';
}
}
},
{
name: 'Test 5: Reject missing questionId',
run: async () => {
try {
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {}, {
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('required')) {
throw new Error('Error message should mention required');
}
return '✓ Missing questionId rejected';
}
}
},
{
name: 'Test 6: Reject invalid questionId format',
run: async () => {
try {
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId: 'invalid-uuid'
}, {
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 questionId format rejected';
}
}
},
{
name: 'Test 7: Reject non-existent question',
run: async () => {
try {
const fakeQuestionId = '00000000-0000-0000-0000-000000000000';
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId: fakeQuestionId
}, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
throw new Error('Should have been rejected');
} catch (error) {
if (error.response?.status !== 404) {
throw new Error(`Expected 404, got ${error.response?.status}`);
}
if (!error.response.data.message.toLowerCase().includes('not found')) {
throw new Error('Error message should mention not found');
}
return '✓ Non-existent question rejected';
}
}
},
{
name: 'Test 8: Reject invalid userId format',
run: async () => {
try {
await axios.post(`${API_URL}/users/invalid-uuid/bookmarks`, {
questionId
}, {
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 userId format rejected';
}
}
},
{
name: 'Test 9: Reject non-existent user',
run: async () => {
try {
const fakeUserId = '00000000-0000-0000-0000-000000000000';
await axios.post(`${API_URL}/users/${fakeUserId}/bookmarks`, {
questionId
}, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
throw new Error('Should have been rejected');
} catch (error) {
if (error.response?.status !== 404) {
throw new Error(`Expected 404, got ${error.response?.status}`);
}
return '✓ Non-existent user rejected';
}
}
},
{
name: 'Test 10: Cross-user bookmark addition blocked',
run: async () => {
try {
// Try to add bookmark to second user's account using first user's token
await axios.post(`${API_URL}/users/${secondUserId}/bookmarks`, {
questionId
}, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
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 bookmark addition blocked';
}
}
},
{
name: 'Test 11: Cross-user bookmark removal blocked',
run: async () => {
try {
// Try to remove bookmark from second user's account using first user's token
await axios.delete(`${API_URL}/users/${secondUserId}/bookmarks/${questionId}`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
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 bookmark removal blocked';
}
}
},
{
name: 'Test 12: Unauthenticated add bookmark blocked',
run: async () => {
try {
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId
});
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 add bookmark blocked';
}
}
},
{
name: 'Test 13: Unauthenticated remove bookmark blocked',
run: async () => {
try {
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`);
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 remove bookmark blocked';
}
}
},
{
name: 'Test 14: Response structure validation',
run: async () => {
// Add a bookmark for testing response structure
const response = await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
questionId
}, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
if (!response.data.success) throw new Error('success field missing');
if (!response.data.data) throw new Error('data field missing');
if (!response.data.data.id) throw new Error('bookmark id missing');
if (!response.data.data.questionId) throw new Error('questionId missing');
if (!response.data.data.question) throw new Error('question details missing');
if (!response.data.data.question.id) throw new Error('question.id missing');
if (!response.data.data.question.questionText) throw new Error('question.questionText missing');
if (!response.data.data.question.difficulty) throw new Error('question.difficulty missing');
if (!response.data.data.question.category) throw new Error('question.category missing');
if (!response.data.data.bookmarkedAt) throw new Error('bookmarkedAt missing');
if (!response.data.message) throw new Error('message field missing');
// Clean up
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
headers: { 'Authorization': `Bearer ${userToken}` }
});
return '✓ Response structure valid';
}
}
];
// Run tests
async function runTests() {
console.log('============================================================');
console.log('BOOKMARK 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();