const axios = require('axios'); const API_URL = 'http://localhost:3000/api'; // Test data let testUser = { email: 'sessiontest@example.com', password: 'Test@123', username: 'sessiontester' }; let secondUser = { email: 'otheruser@example.com', password: 'Test@123', username: 'otheruser' }; let userToken = null; let secondUserToken = null; let guestToken = null; let guestId = null; let testCategory = null; let userSessionId = null; let userSessionIdCompleted = null; let guestSessionId = 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, isGuest = false) { const headers = isGuest ? { 'X-Guest-Token': token } : { 'Authorization': `Bearer ${token}` }; // Get categories const categoriesRes = await axios.get(`${API_URL}/categories`, { headers }); const categories = categoriesRes.data.data; const category = categories.find(c => c.questionCount >= 3); if (!category) { throw new Error('No category with enough questions found'); } // Start quiz const startRes = await axios.post(`${API_URL}/quiz/start`, { categoryId: category.id, questionCount: 3, 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: 10 }, { 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; console.log('✓ First user registered'); } catch (error) { // User might already exist, try login const loginRes = await axios.post(`${API_URL}/auth/login`, { email: testUser.email, password: testUser.password }); userToken = loginRes.data.data.token; 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; 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; console.log('✓ Second user logged in'); } // Create guest session const guestRes = await axios.post(`${API_URL}/guest/start-session`); guestToken = guestRes.data.data.sessionToken; guestId = guestRes.data.data.guestId; console.log('✓ Guest session created'); // Get a test category const categoriesRes = await axios.get(`${API_URL}/categories`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const categories = categoriesRes.data.data; 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}`); // Create in-progress quiz for user const startRes = await axios.post(`${API_URL}/quiz/start`, { categoryId: testCategory.id, questionCount: 3, difficulty: 'mixed', quizType: 'practice' }, { headers: { 'Authorization': `Bearer ${userToken}` } }); userSessionId = startRes.data.data.sessionId; // Submit one answer const questions = startRes.data.data.questions; let answer = questions[0].questionType === 'multiple' ? questions[0].options[0].id : 'true'; await axios.post(`${API_URL}/quiz/submit`, { quizSessionId: userSessionId, questionId: questions[0].id, userAnswer: answer, timeTaken: 10 }, { headers: { 'Authorization': `Bearer ${userToken}` } }); console.log('✓ User in-progress session created'); await delay(500); // Create completed quiz for user userSessionIdCompleted = await createAndCompleteQuiz(userToken, false); console.log('✓ User completed session created'); await delay(500); // Create in-progress quiz for guest const guestStartRes = await axios.post(`${API_URL}/quiz/start`, { categoryId: testCategory.id, questionCount: 3, difficulty: 'easy', quizType: 'practice' }, { headers: { 'X-Guest-Token': guestToken } }); guestSessionId = guestStartRes.data.data.sessionId; console.log('✓ Guest session created\n'); await delay(500); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); throw error; } } // Test cases const tests = [ { name: 'Test 1: Get in-progress session details (user)', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { 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 { session, progress, questions } = response.data.data; // Validate session structure if (!session.id || session.id !== userSessionId) throw new Error('Invalid session id'); if (session.status !== 'in_progress') throw new Error('Expected in_progress status'); if (!session.category || !session.category.name) throw new Error('Missing category info'); if (typeof session.score.earned !== 'number') throw new Error('Score.earned should be number'); if (typeof session.score.total !== 'number') throw new Error('Score.total should be number'); // Validate progress if (progress.totalQuestions !== 3) throw new Error('Expected 3 total questions'); if (progress.answeredQuestions !== 1) throw new Error('Expected 1 answered question'); if (progress.unansweredQuestions !== 2) throw new Error('Expected 2 unanswered'); // Validate questions if (questions.length !== 3) throw new Error('Expected 3 questions'); const answeredQ = questions.find(q => q.isAnswered); if (!answeredQ) throw new Error('Expected at least one answered question'); if (!answeredQ.userAnswer) throw new Error('Answered question should have userAnswer'); if (answeredQ.isCorrect === null) throw new Error('Answered question should have isCorrect'); // In-progress session should not show correct answers for unanswered questions const unansweredQ = questions.find(q => !q.isAnswered); if (unansweredQ && unansweredQ.correctAnswer !== undefined) { throw new Error('Unanswered question should not show correct answer in in-progress session'); } return '✓ In-progress session details correct'; } }, { name: 'Test 2: Get completed session details (user)', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionIdCompleted}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); if (response.status !== 200) throw new Error('Expected 200 status'); const { session, progress, questions } = response.data.data; if (session.status !== 'completed') throw new Error('Expected completed status'); if (!session.completedAt) throw new Error('Should have completedAt timestamp'); if (typeof session.isPassed !== 'boolean') throw new Error('Should have isPassed boolean'); if (progress.totalQuestions !== 3) throw new Error('Expected 3 total questions'); if (progress.answeredQuestions !== 3) throw new Error('All questions should be answered'); // Completed session should show correct answers for all questions questions.forEach((q, idx) => { if (q.correctAnswer === undefined) { throw new Error(`Question ${idx + 1} should show correct answer in completed session`); } if (!q.isAnswered) { throw new Error(`All questions should be answered in completed session`); } }); return '✓ Completed session details correct'; } }, { name: 'Test 3: Get guest session details', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${guestSessionId}`, { headers: { 'X-Guest-Token': guestToken } }); if (response.status !== 200) throw new Error('Expected 200 status'); if (!response.data.success) throw new Error('Expected success true'); const { session, questions } = response.data.data; if (session.id !== guestSessionId) throw new Error('Invalid session id'); if (session.status !== 'in_progress') throw new Error('Expected in_progress status'); if (questions.length !== 3) throw new Error('Expected 3 questions'); return '✓ Guest session details retrieved'; } }, { name: 'Test 4: Missing session ID returns 400', run: async () => { try { await axios.get(`${API_URL}/quiz/session/`, { headers: { 'Authorization': `Bearer ${userToken}` } }); throw new Error('Should have failed with 400'); } catch (error) { if (error.response?.status === 404) { // Route not found is acceptable for empty path return '✓ Missing session ID handled'; } if (error.response?.status !== 400) { throw new Error(`Expected 400, got ${error.response?.status}`); } return '✓ Missing session ID returns 400'; } } }, { name: 'Test 5: Invalid UUID format returns 400', run: async () => { try { await axios.get(`${API_URL}/quiz/session/invalid-uuid`, { 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 6: Non-existent session returns 404', run: async () => { try { const fakeUuid = '00000000-0000-0000-0000-000000000000'; await axios.get(`${API_URL}/quiz/session/${fakeUuid}`, { 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 session returns 404'; } } }, { name: 'Test 7: Cannot access other user\'s session (403)', run: async () => { try { await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${secondUserToken}` } }); 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 8: Unauthenticated request returns 401', run: async () => { try { await axios.get(`${API_URL}/quiz/session/${userSessionId}`); 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 9: Response includes all required session fields', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { session } = response.data.data; const requiredFields = [ 'id', 'status', 'quizType', 'difficulty', 'category', 'score', 'isPassed', 'startedAt', 'timeSpent', 'timeLimit' ]; requiredFields.forEach(field => { if (!(field in session)) { throw new Error(`Missing required field: ${field}`); } }); // Category should have required fields const categoryFields = ['id', 'name', 'slug', 'icon', 'color']; categoryFields.forEach(field => { if (!(field in session.category)) { throw new Error(`Missing category field: ${field}`); } }); // Score should have required fields const scoreFields = ['earned', 'total', 'percentage']; scoreFields.forEach(field => { if (!(field in session.score)) { throw new Error(`Missing score field: ${field}`); } }); return '✓ All required session fields present'; } }, { name: 'Test 10: Response includes all required progress fields', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { progress } = response.data.data; const requiredFields = [ 'totalQuestions', 'answeredQuestions', 'correctAnswers', 'incorrectAnswers', 'unansweredQuestions', 'progressPercentage' ]; requiredFields.forEach(field => { if (!(field in progress)) { throw new Error(`Missing progress field: ${field}`); } if (typeof progress[field] !== 'number') { throw new Error(`Progress field ${field} should be a number`); } }); return '✓ All required progress fields present'; } }, { name: 'Test 11: Questions include all required fields', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { questions } = response.data.data; if (questions.length === 0) throw new Error('Should have questions'); const requiredFields = [ 'id', 'questionText', 'questionType', 'difficulty', 'points', 'order', 'userAnswer', 'isCorrect', 'pointsEarned', 'timeTaken', 'answeredAt', 'isAnswered' ]; questions.forEach((q, idx) => { requiredFields.forEach(field => { if (!(field in q)) { throw new Error(`Question ${idx + 1} missing field: ${field}`); } }); }); return '✓ Questions have all required fields'; } }, { name: 'Test 12: Time tracking calculations', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { session } = response.data.data; if (typeof session.timeSpent !== 'number') { throw new Error('timeSpent should be a number'); } if (session.timeSpent < 0) { throw new Error('timeSpent should not be negative'); } // Practice quiz should have null timeLimit if (session.quizType === 'practice' && session.timeLimit !== null) { throw new Error('Practice quiz should have null timeLimit'); } // timeRemaining should be null for practice or number for timed if (session.timeLimit !== null) { if (typeof session.timeRemaining !== 'number') { throw new Error('timeRemaining should be a number for timed quiz'); } } return '✓ Time tracking correct'; } }, { name: 'Test 13: Progress percentages are accurate', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { progress } = response.data.data; const expectedPercentage = Math.round( (progress.answeredQuestions / progress.totalQuestions) * 100 ); if (progress.progressPercentage !== expectedPercentage) { throw new Error( `Progress percentage incorrect: expected ${expectedPercentage}, got ${progress.progressPercentage}` ); } // Check totals add up const totalCheck = progress.correctAnswers + progress.incorrectAnswers + progress.unansweredQuestions; if (totalCheck !== progress.totalQuestions) { throw new Error('Question counts do not add up'); } return '✓ Progress calculations accurate'; } }, { name: 'Test 14: Answered questions show correct feedback', run: async () => { const response = await axios.get(`${API_URL}/quiz/session/${userSessionIdCompleted}`, { headers: { 'Authorization': `Bearer ${userToken}` } }); const { questions } = response.data.data; questions.forEach((q, idx) => { if (q.isAnswered) { if (!q.userAnswer) { throw new Error(`Question ${idx + 1} is answered but has no userAnswer`); } if (typeof q.isCorrect !== 'boolean') { throw new Error(`Question ${idx + 1} should have boolean isCorrect`); } if (typeof q.pointsEarned !== 'number') { throw new Error(`Question ${idx + 1} should have number pointsEarned`); } if (q.correctAnswer === undefined) { throw new Error(`Question ${idx + 1} should show correctAnswer in completed session`); } } }); return '✓ Answered questions have correct feedback'; } } ]; // Run all tests async function runTests() { console.log('='.repeat(60)); console.log('QUIZ SESSION DETAILS 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) { 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();