const axios = require('axios'); const BASE_URL = 'http://localhost:3000/api'; // Test user credentials const testUser = { username: 'bookmarklist1', email: 'bookmarklist1@example.com', password: 'Test123!@#' }; const testUser2 = { username: 'bookmarklist2', email: 'bookmarklist2@example.com', password: 'Test123!@#' }; let userToken = ''; let userId = ''; let user2Token = ''; let user2Id = ''; let categoryId = ''; let questionIds = []; let bookmarkIds = []; // Test results tracking let passedTests = 0; let failedTests = 0; const testResults = []; // Helper function to log test results function logTest(testName, passed, error = null) { if (passed) { console.log(`✓ ${testName}`); passedTests++; } else { console.log(`✗ ${testName}`); if (error) { console.log(` Error: ${error}`); } failedTests++; } testResults.push({ testName, passed, error }); } // Helper function to delay between tests const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); async function setup() { try { console.log('Setting up test data...'); // Register and login first user try { const regRes = await axios.post(`${BASE_URL}/auth/register`, testUser); console.log('✓ Test user registered'); } catch (err) { // User might already exist, continue to login if (err.response?.status !== 409) { console.log('Registration error:', err.response?.data?.message || err.message); } } const loginRes = await axios.post(`${BASE_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 and login second user try { const regRes = await axios.post(`${BASE_URL}/auth/register`, testUser2); console.log('✓ Second user registered'); } catch (err) { // User might already exist, continue to login if (err.response?.status !== 409) { console.log('Registration error:', err.response?.data?.message || err.message); } } const login2Res = await axios.post(`${BASE_URL}/auth/login`, { email: testUser2.email, password: testUser2.password }); user2Token = login2Res.data.data.token; user2Id = login2Res.data.data.user.id; console.log('✓ Second user logged in'); // Get categories const categoriesRes = await axios.get(`${BASE_URL}/categories`); const categories = categoriesRes.data.data; // Find a category with questions let testCategory = null; for (const cat of categories) { if (cat.questionCount > 0) { testCategory = cat; break; } } if (!testCategory) { throw new Error('No categories with questions found'); } categoryId = testCategory.id; console.log(`✓ Found test category: ${testCategory.name} (${testCategory.questionCount} questions)`); // Get questions from this category const questionsRes = await axios.get(`${BASE_URL}/questions/category/${categoryId}?limit=10`); const questions = questionsRes.data.data; if (questions.length === 0) { throw new Error('No questions available in category for testing'); } questionIds = questions.slice(0, Math.min(5, questions.length)).map(q => q.id); console.log(`✓ Found ${questionIds.length} test questions`); // Delete any existing bookmarks first (cleanup from previous test runs) for (const questionId of questionIds) { try { await axios.delete( `${BASE_URL}/users/${userId}/bookmarks/${questionId}`, { headers: { Authorization: `Bearer ${userToken}` } } ); } catch (err) { // Ignore if bookmark doesn't exist } } // Create bookmarks for testing for (const questionId of questionIds) { const bookmarkRes = await axios.post( `${BASE_URL}/users/${userId}/bookmarks`, { questionId }, { headers: { Authorization: `Bearer ${userToken}` } } ); bookmarkIds.push(bookmarkRes.data.data.id); await delay(100); } console.log(`✓ Created ${bookmarkIds.length} bookmarks for testing`); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); throw error; } } async function runTests() { console.log('\n============================================================'); console.log('USER BOOKMARKS API TESTS'); console.log('============================================================\n'); await setup(); console.log('\nRunning tests...'); // Test 1: Get bookmarks with default pagination await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.success === true && Array.isArray(response.data.data.bookmarks) && response.data.data.bookmarks.length > 0 && response.data.data.pagination.currentPage === 1 && response.data.data.pagination.itemsPerPage === 10; logTest('Get bookmarks with default pagination', passed); } catch (error) { logTest('Get bookmarks with default pagination', false, error.response?.data?.message || error.message); } // Test 2: Pagination structure validation await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?page=1&limit=5`, { headers: { Authorization: `Bearer ${userToken}` } } ); const pagination = response.data.data.pagination; const passed = response.status === 200 && pagination.currentPage === 1 && pagination.itemsPerPage === 5 && typeof pagination.totalPages === 'number' && typeof pagination.totalItems === 'number' && typeof pagination.hasNextPage === 'boolean' && typeof pagination.hasPreviousPage === 'boolean'; logTest('Pagination structure validation', passed); } catch (error) { logTest('Pagination structure validation', false, error.response?.data?.message || error.message); } // Test 3: Bookmark fields validation await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); const bookmark = response.data.data.bookmarks[0]; const passed = response.status === 200 && bookmark.bookmarkId && bookmark.bookmarkedAt && bookmark.question && bookmark.question.id && bookmark.question.questionText && bookmark.question.difficulty && bookmark.question.category && bookmark.question.statistics; logTest('Bookmark fields validation', passed); } catch (error) { logTest('Bookmark fields validation', false, error.response?.data?.message || error.message); } // Test 4: Custom limit await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?limit=2`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.bookmarks.length <= 2 && response.data.data.pagination.itemsPerPage === 2; logTest('Custom limit', passed); } catch (error) { logTest('Custom limit', false, error.response?.data?.message || error.message); } // Test 5: Page 2 navigation await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?page=2&limit=3`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.pagination.currentPage === 2 && response.data.data.pagination.hasPreviousPage === true; logTest('Page 2 navigation', passed); } catch (error) { logTest('Page 2 navigation', false, error.response?.data?.message || error.message); } // Test 6: Category filter await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?category=${categoryId}`, { headers: { Authorization: `Bearer ${userToken}` } } ); const allMatchCategory = response.data.data.bookmarks.every( b => b.question.category.id === categoryId ); const passed = response.status === 200 && allMatchCategory && response.data.data.filters.category === categoryId; logTest('Category filter', passed); } catch (error) { logTest('Category filter', false, error.response?.data?.message || error.message); } // Test 7: Difficulty filter await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?difficulty=medium`, { headers: { Authorization: `Bearer ${userToken}` } } ); const allMatchDifficulty = response.data.data.bookmarks.every( b => b.question.difficulty === 'medium' ); const passed = response.status === 200 && (response.data.data.bookmarks.length === 0 || allMatchDifficulty) && response.data.data.filters.difficulty === 'medium'; logTest('Difficulty filter', passed); } catch (error) { logTest('Difficulty filter', false, error.response?.data?.message || error.message); } // Test 8: Sort by difficulty ascending await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?sortBy=difficulty&sortOrder=asc`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.sorting.sortBy === 'difficulty' && response.data.data.sorting.sortOrder === 'asc'; logTest('Sort by difficulty ascending', passed); } catch (error) { logTest('Sort by difficulty ascending', false, error.response?.data?.message || error.message); } // Test 9: Sort by date descending (default) await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?sortBy=date&sortOrder=desc`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.sorting.sortBy === 'date' && response.data.data.sorting.sortOrder === 'desc'; logTest('Sort by date descending', passed); } catch (error) { logTest('Sort by date descending', false, error.response?.data?.message || error.message); } // Test 10: Max limit enforcement (50) await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?limit=100`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.pagination.itemsPerPage === 50; logTest('Max limit enforcement (50)', passed); } catch (error) { logTest('Max limit enforcement (50)', false, error.response?.data?.message || error.message); } // Test 11: Cross-user access blocked await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${user2Id}/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Cross-user access blocked', false, 'Expected 403 but got 200'); } catch (error) { const passed = error.response?.status === 403; logTest('Cross-user access blocked', passed, passed ? null : `Expected 403 but got ${error.response?.status}`); } // Test 12: Unauthenticated request blocked await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks` ); logTest('Unauthenticated request blocked', false, 'Expected 401 but got 200'); } catch (error) { const passed = error.response?.status === 401; logTest('Unauthenticated request blocked', passed, passed ? null : `Expected 401 but got ${error.response?.status}`); } // Test 13: Invalid UUID format await delay(100); try { const response = await axios.get( `${BASE_URL}/users/invalid-uuid/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Invalid UUID format', false, 'Expected 400 but got 200'); } catch (error) { const passed = error.response?.status === 400; logTest('Invalid UUID format', passed, passed ? null : `Expected 400 but got ${error.response?.status}`); } // Test 14: Non-existent user await delay(100); try { const response = await axios.get( `${BASE_URL}/users/11111111-1111-1111-1111-111111111111/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Non-existent user', false, 'Expected 404 but got 200'); } catch (error) { const passed = error.response?.status === 404; logTest('Non-existent user', passed, passed ? null : `Expected 404 but got ${error.response?.status}`); } // Test 15: Invalid category ID format await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?category=invalid-uuid`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Invalid category ID format', false, 'Expected 400 but got 200'); } catch (error) { const passed = error.response?.status === 400; logTest('Invalid category ID format', passed, passed ? null : `Expected 400 but got ${error.response?.status}`); } // Test 16: Invalid difficulty value await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?difficulty=invalid`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Invalid difficulty value', false, 'Expected 400 but got 200'); } catch (error) { const passed = error.response?.status === 400; logTest('Invalid difficulty value', passed, passed ? null : `Expected 400 but got ${error.response?.status}`); } // Test 17: Invalid sort order await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?sortOrder=invalid`, { headers: { Authorization: `Bearer ${userToken}` } } ); logTest('Invalid sort order', false, 'Expected 400 but got 200'); } catch (error) { const passed = error.response?.status === 400; logTest('Invalid sort order', passed, passed ? null : `Expected 400 but got ${error.response?.status}`); } // Test 18: Empty bookmarks list await delay(100); try { // Use second user who has no bookmarks const response = await axios.get( `${BASE_URL}/users/${user2Id}/bookmarks`, { headers: { Authorization: `Bearer ${user2Token}` } } ); const passed = response.status === 200 && Array.isArray(response.data.data.bookmarks) && response.data.data.bookmarks.length === 0 && response.data.data.pagination.totalItems === 0; logTest('Empty bookmarks list', passed); } catch (error) { logTest('Empty bookmarks list', false, error.response?.data?.message || error.message); } // Test 19: Combined filters (category + difficulty + sorting) await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks?category=${categoryId}&difficulty=easy&sortBy=date&sortOrder=asc`, { headers: { Authorization: `Bearer ${userToken}` } } ); const passed = response.status === 200 && response.data.data.filters.category === categoryId && response.data.data.filters.difficulty === 'easy' && response.data.data.sorting.sortBy === 'date' && response.data.data.sorting.sortOrder === 'asc'; logTest('Combined filters', passed); } catch (error) { logTest('Combined filters', false, error.response?.data?.message || error.message); } // Test 20: Question statistics included await delay(100); try { const response = await axios.get( `${BASE_URL}/users/${userId}/bookmarks`, { headers: { Authorization: `Bearer ${userToken}` } } ); const bookmark = response.data.data.bookmarks[0]; const stats = bookmark.question.statistics; const passed = response.status === 200 && stats && typeof stats.timesAttempted === 'number' && typeof stats.timesCorrect === 'number' && typeof stats.accuracy === 'number'; logTest('Question statistics included', passed); } catch (error) { logTest('Question statistics included', false, error.response?.data?.message || error.message); } // Print results console.log('\n============================================================'); console.log(`RESULTS: ${passedTests} passed, ${failedTests} failed out of ${passedTests + failedTests} tests`); console.log('============================================================\n'); process.exit(failedTests > 0 ? 1 : 0); } // Run tests runTests();