const axios = require('axios'); const API_URL = 'http://localhost:3000/api'; // Test data let testUser = { email: 'dashboarduser@example.com', password: 'Test@123', username: 'dashboarduser' }; let secondUser = { email: 'otheruser2@example.com', password: 'Test@123', username: 'otheruser2' }; let userToken = null; let userId = null; let secondUserToken = null; let secondUserId = null; let testCategory = null; // Helper to add delay between tests const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // Helper to create and complete a quiz async function createAndCompleteQuiz(token, categoryId, questionCount = 3) { const headers = { 'Authorization': `Bearer ${token}` }; // Start quiz const startRes = await axios.post(`${API_URL}/quiz/start`, { categoryId, questionCount, difficulty: 'mixed', quizType: 'practice' }, { headers }); const sessionId = startRes.data.data.sessionId; const questions = startRes.data.data.questions; // Submit answers for all questions for (const question of questions) { let answer; if (question.questionType === 'multiple') { answer = question.options[0].id; } else if (question.questionType === 'trueFalse') { answer = 'true'; } else { answer = 'Sample answer'; } await axios.post(`${API_URL}/quiz/submit`, { quizSessionId: sessionId, questionId: question.id, userAnswer: answer, timeTaken: Math.floor(Math.random() * 15) + 5 }, { headers }); await delay(100); } // Complete quiz await axios.post(`${API_URL}/quiz/complete`, { sessionId }, { headers }); return sessionId; } // Test setup async function setup() { console.log('Setting up test data...\n'); try { // Register 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 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 categories const categoriesRes = await axios.get(`${API_URL}/categories`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const categories = categoriesRes.data.data; categories.sort((a, b) => b.questionCount - a.questionCount); testCategory = categories.find(c => c.questionCount >= 3); if (!testCategory) { throw new Error('No category with enough questions found'); } console.log(`✓ Test category selected: ${testCategory.name}`); await delay(500); // Create some quizzes for the first user to populate dashboard console.log('Creating quiz sessions for dashboard data...'); for (let i = 0; i < 3; i++) { await createAndCompleteQuiz(userToken, testCategory.id, 3); await delay(500); } console.log('✓ Quiz sessions created\n'); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); throw error; } } // Test cases const tests = [ { name: 'Test 1: Get user dashboard successfully', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (response.status !== 200) throw new Error('Expected 200 status'); if (!response.data.success) throw new Error('Expected success true'); const { user, stats, recentSessions, categoryPerformance, recentActivity } = response.data.data; if (!user || !stats || !recentSessions || !categoryPerformance || !recentActivity) { throw new Error('Missing required dashboard sections'); } return '✓ Dashboard retrieved successfully'; } }, { name: 'Test 2: User info includes required fields', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { user } = response.data.data; const requiredFields = ['id', 'username', 'email', 'role', 'memberSince']; requiredFields.forEach(field => { if (!(field in user)) { throw new Error(`Missing user field: ${field}`); } }); if (user.id !== userId) throw new Error('User ID mismatch'); if (user.email !== testUser.email) throw new Error('Email mismatch'); return '✓ User info correct'; } }, { name: 'Test 3: Stats include all required fields', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { stats } = response.data.data; const requiredFields = [ 'totalQuizzes', 'quizzesPassed', 'passRate', 'totalQuestionsAnswered', 'correctAnswers', 'overallAccuracy', 'currentStreak', 'longestStreak', 'streakStatus', 'lastActiveDate' ]; requiredFields.forEach(field => { if (!(field in stats)) { throw new Error(`Missing stats field: ${field}`); } }); // Validate data types if (typeof stats.totalQuizzes !== 'number') throw new Error('totalQuizzes should be number'); if (typeof stats.overallAccuracy !== 'number') throw new Error('overallAccuracy should be number'); if (typeof stats.passRate !== 'number') throw new Error('passRate should be number'); return '✓ Stats fields correct'; } }, { name: 'Test 4: Stats calculations are accurate', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { stats } = response.data.data; // Pass rate calculation if (stats.totalQuizzes > 0) { const expectedPassRate = Math.round((stats.quizzesPassed / stats.totalQuizzes) * 100); if (stats.passRate !== expectedPassRate) { throw new Error(`Pass rate mismatch: expected ${expectedPassRate}, got ${stats.passRate}`); } } // Accuracy calculation if (stats.totalQuestionsAnswered > 0) { const expectedAccuracy = Math.round((stats.correctAnswers / stats.totalQuestionsAnswered) * 100); if (stats.overallAccuracy !== expectedAccuracy) { throw new Error(`Accuracy mismatch: expected ${expectedAccuracy}, got ${stats.overallAccuracy}`); } } // Streak validation if (stats.currentStreak < 0) throw new Error('Current streak cannot be negative'); if (stats.longestStreak < stats.currentStreak) { throw new Error('Longest streak should be >= current streak'); } return '✓ Stats calculations accurate'; } }, { name: 'Test 5: Recent sessions returned correctly', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { recentSessions } = response.data.data; if (!Array.isArray(recentSessions)) throw new Error('recentSessions should be array'); if (recentSessions.length === 0) throw new Error('Should have recent sessions'); if (recentSessions.length > 10) throw new Error('Should have max 10 recent sessions'); // Validate session structure const session = recentSessions[0]; const requiredFields = [ 'id', 'category', 'quizType', 'difficulty', 'status', 'score', 'isPassed', 'questionsAnswered', 'correctAnswers', 'accuracy', 'timeSpent', 'completedAt' ]; requiredFields.forEach(field => { if (!(field in session)) { throw new Error(`Session missing field: ${field}`); } }); // Validate category structure if (!session.category || !session.category.name) { throw new Error('Session should have category info'); } // Validate score structure if (!session.score || typeof session.score.earned !== 'number') { throw new Error('Session should have score object with earned field'); } return '✓ Recent sessions correct'; } }, { name: 'Test 6: Recent sessions ordered by completion date', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { recentSessions } = response.data.data; if (recentSessions.length > 1) { for (let i = 1; i < recentSessions.length; i++) { const prev = new Date(recentSessions[i - 1].completedAt); const curr = new Date(recentSessions[i].completedAt); if (curr > prev) { throw new Error('Sessions not ordered by completion date (DESC)'); } } } return '✓ Sessions ordered correctly'; } }, { name: 'Test 7: Category performance includes all categories', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { categoryPerformance } = response.data.data; if (!Array.isArray(categoryPerformance)) { throw new Error('categoryPerformance should be array'); } if (categoryPerformance.length === 0) { throw new Error('Should have category performance data'); } // Validate structure const catPerf = categoryPerformance[0]; if (!catPerf.category || !catPerf.stats || !catPerf.lastAttempt) { throw new Error('Category performance missing required fields'); } const requiredStatsFields = [ 'quizzesTaken', 'quizzesPassed', 'passRate', 'averageScore', 'totalQuestions', 'correctAnswers', 'accuracy' ]; requiredStatsFields.forEach(field => { if (!(field in catPerf.stats)) { throw new Error(`Category stats missing field: ${field}`); } }); return '✓ Category performance correct'; } }, { name: 'Test 8: Category performance calculations accurate', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { categoryPerformance } = response.data.data; categoryPerformance.forEach((catPerf, idx) => { const stats = catPerf.stats; // Pass rate if (stats.quizzesTaken > 0) { const expectedPassRate = Math.round((stats.quizzesPassed / stats.quizzesTaken) * 100); if (stats.passRate !== expectedPassRate) { throw new Error(`Category ${idx + 1} pass rate mismatch`); } } // Accuracy if (stats.totalQuestions > 0) { const expectedAccuracy = Math.round((stats.correctAnswers / stats.totalQuestions) * 100); if (stats.accuracy !== expectedAccuracy) { throw new Error(`Category ${idx + 1} accuracy mismatch`); } } // All values should be non-negative Object.values(stats).forEach(val => { if (typeof val === 'number' && val < 0) { throw new Error('Stats values should be non-negative'); } }); }); return '✓ Category performance calculations accurate'; } }, { name: 'Test 9: Recent activity includes date and count', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { recentActivity } = response.data.data; if (!Array.isArray(recentActivity)) { throw new Error('recentActivity should be array'); } if (recentActivity.length > 0) { const activity = recentActivity[0]; if (!activity.date) throw new Error('Activity missing date'); if (typeof activity.quizzesCompleted !== 'number') { throw new Error('Activity quizzesCompleted should be number'); } } return '✓ Recent activity correct'; } }, { name: 'Test 10: Cannot access other user\'s dashboard (403)', run: async () => { try { await axios.get(`${API_URL}/users/${secondUserId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed with 403'); } catch (error) { if (error.response?.status !== 403) { throw new Error(`Expected 403, got ${error.response?.status}`); } return '✓ Cross-user access blocked'; } } }, { name: 'Test 11: Unauthenticated request returns 401', run: async () => { try { await axios.get(`${API_URL}/users/${userId}/dashboard`); throw new Error('Should have failed with 401'); } catch (error) { if (error.response?.status !== 401) { throw new Error(`Expected 401, got ${error.response?.status}`); } return '✓ Unauthenticated request blocked'; } } }, { name: 'Test 12: Invalid UUID format returns 400', run: async () => { try { await axios.get(`${API_URL}/users/invalid-uuid/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed with 400'); } catch (error) { if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Invalid UUID returns 400'; } } }, { name: 'Test 13: Non-existent user returns 404', run: async () => { try { const fakeUuid = '00000000-0000-0000-0000-000000000000'; await axios.get(`${API_URL}/users/${fakeUuid}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed with 404'); } catch (error) { if (error.response?.status !== 404) { throw new Error(`Expected 404, got ${error.response?.status}`); } return '✓ Non-existent user returns 404'; } } }, { name: 'Test 14: Streak status is valid', run: async () => { const response = await axios.get(`${API_URL}/users/${userId}/dashboard`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { stats } = response.data.data; const validStatuses = ['active', 'at-risk', 'inactive']; if (!validStatuses.includes(stats.streakStatus)) { throw new Error(`Invalid streak status: ${stats.streakStatus}`); } return '✓ Streak status valid'; } } ]; // Run all tests async function runTests() { console.log('='.repeat(60)); console.log('USER DASHBOARD API TESTS'); console.log('='.repeat(60) + '\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++; await delay(500); // Delay between tests } catch (error) { console.log(`✗ ${test.name}`); console.log(` Error: ${error.response?.data?.message || error.message}`); if (error.response?.data && process.env.VERBOSE) { console.log(` Response:`, JSON.stringify(error.response.data, null, 2)); } failed++; } } console.log('\n' + '='.repeat(60)); console.log(`RESULTS: ${passed} passed, ${failed} failed out of ${tests.length} tests`); console.log('='.repeat(60)); process.exit(failed > 0 ? 1 : 0); } runTests();