const axios = require('axios'); const BASE_URL = 'http://localhost:3000/api'; // Question UUIDs from database const QUESTION_IDS = { GUEST_REACT_EASY: '0891122f-cf0f-4fdf-afd8-5bf0889851f7', // React - easy [GUEST] AUTH_TYPESCRIPT_HARD: '08aa3a33-46fa-4deb-994e-8a2799abcf9f', // TypeScript - hard [AUTH] GUEST_JS_EASY: '0c414118-fa32-407a-a9d9-4b9f85955e12', // JavaScript - easy [GUEST] AUTH_SYSTEM_DESIGN: '14ee37fe-061d-4677-b2a5-b092c711539f', // System Design - medium [AUTH] AUTH_NODEJS_HARD: '22df0824-43bd-48b3-9e1b-c8072ce5e5d5', // Node.js - hard [AUTH] GUEST_ANGULAR_EASY: '20d1f27b-5ab8-4027-9548-48def7dd9c3a', // Angular - easy [GUEST] }; let adminToken = ''; let regularUserToken = ''; let testResults = { passed: 0, failed: 0, total: 0 }; // Test helper async function runTest(testName, testFn) { testResults.total++; try { await testFn(); testResults.passed++; console.log(`✓ ${testName} - PASSED`); } catch (error) { testResults.failed++; console.log(`✗ ${testName} - FAILED`); console.log(` Error: ${error.message}`); } } // Setup: Login as admin and regular user async function setup() { try { // Login as admin const adminLogin = await axios.post(`${BASE_URL}/auth/login`, { email: 'admin@quiz.com', password: 'Admin@123' }); adminToken = adminLogin.data.data.token; console.log('✓ Logged in as admin'); // Create and login as regular user const timestamp = Date.now(); const regularUser = { username: `testuser${timestamp}`, email: `testuser${timestamp}@test.com`, password: 'Test@123' }; await axios.post(`${BASE_URL}/auth/register`, regularUser); const userLogin = await axios.post(`${BASE_URL}/auth/login`, { email: regularUser.email, password: regularUser.password }); regularUserToken = userLogin.data.data.token; console.log('✓ Created and logged in as regular user\n'); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); process.exit(1); } } // Tests async function runTests() { console.log('========================================'); console.log('Testing Get Question by ID API'); console.log('========================================\n'); await setup(); // Test 1: Get guest-accessible question without auth await runTest('Test 1: Get guest-accessible question without auth', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_REACT_EASY}`); if (response.data.success !== true) throw new Error('Response success should be true'); if (!response.data.data) throw new Error('Response should contain data'); if (response.data.data.id !== QUESTION_IDS.GUEST_REACT_EASY) throw new Error('Wrong question ID'); if (!response.data.data.category) throw new Error('Category info should be included'); if (response.data.data.category.name !== 'React') throw new Error('Wrong category'); console.log(` Retrieved question: "${response.data.data.questionText.substring(0, 50)}..."`); }); // Test 2: Guest blocked from auth-only question await runTest('Test 2: Guest blocked from auth-only question', async () => { try { await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.AUTH_TYPESCRIPT_HARD}`); throw new Error('Should have returned 403'); } catch (error) { if (error.response?.status !== 403) throw new Error(`Expected 403, got ${error.response?.status}`); if (!error.response.data.message.includes('authentication')) { throw new Error('Error message should mention authentication'); } console.log(` Correctly blocked with: ${error.response.data.message}`); } }); // Test 3: Authenticated user can access auth-only question await runTest('Test 3: Authenticated user can access auth-only question', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.AUTH_TYPESCRIPT_HARD}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); if (!response.data.data) throw new Error('Response should contain data'); if (response.data.data.category.name !== 'TypeScript') throw new Error('Wrong category'); if (response.data.data.difficulty !== 'hard') throw new Error('Wrong difficulty'); console.log(` Retrieved auth-only question from ${response.data.data.category.name}`); }); // Test 4: Invalid question UUID format await runTest('Test 4: Invalid question UUID format', async () => { try { await axios.get(`${BASE_URL}/questions/invalid-uuid-123`); throw new Error('Should have returned 400'); } catch (error) { if (error.response?.status !== 400) throw new Error(`Expected 400, got ${error.response?.status}`); if (!error.response.data.message.includes('Invalid question ID')) { throw new Error('Should mention invalid ID format'); } console.log(` Correctly rejected invalid UUID`); } }); // Test 5: Non-existent question await runTest('Test 5: Non-existent question', async () => { const fakeUuid = '00000000-0000-0000-0000-000000000000'; try { await axios.get(`${BASE_URL}/questions/${fakeUuid}`); throw new Error('Should have returned 404'); } catch (error) { if (error.response?.status !== 404) throw new Error(`Expected 404, got ${error.response?.status}`); if (!error.response.data.message.includes('not found')) { throw new Error('Should mention question not found'); } console.log(` Correctly returned 404 for non-existent question`); } }); // Test 6: Response structure validation await runTest('Test 6: Response structure validation', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_JS_EASY}`); // Check top-level structure const requiredTopFields = ['success', 'data', 'message']; for (const field of requiredTopFields) { if (!(field in response.data)) throw new Error(`Missing field: ${field}`); } // Check question data structure const question = response.data.data; const requiredQuestionFields = [ 'id', 'questionText', 'questionType', 'options', 'difficulty', 'points', 'explanation', 'tags', 'accuracy', 'statistics', 'category' ]; for (const field of requiredQuestionFields) { if (!(field in question)) throw new Error(`Missing question field: ${field}`); } // Check statistics structure const statsFields = ['timesAttempted', 'timesCorrect', 'accuracy']; for (const field of statsFields) { if (!(field in question.statistics)) throw new Error(`Missing statistics field: ${field}`); } // Check category structure const categoryFields = ['id', 'name', 'slug', 'icon', 'color', 'guestAccessible']; for (const field of categoryFields) { if (!(field in question.category)) throw new Error(`Missing category field: ${field}`); } // Verify correct_answer is NOT exposed if ('correctAnswer' in question || 'correct_answer' in question) { throw new Error('Correct answer should not be exposed'); } console.log(` Response structure validated`); }); // Test 7: Accuracy calculation present await runTest('Test 7: Accuracy calculation present', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_ANGULAR_EASY}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); const question = response.data.data; if (typeof question.accuracy !== 'number') throw new Error('Accuracy should be a number'); if (question.accuracy < 0 || question.accuracy > 100) { throw new Error(`Invalid accuracy: ${question.accuracy}`); } // Check statistics match if (question.accuracy !== question.statistics.accuracy) { throw new Error('Accuracy mismatch between root and statistics'); } console.log(` Accuracy: ${question.accuracy}% (${question.statistics.timesCorrect}/${question.statistics.timesAttempted})`); }); // Test 8: Multiple question types work await runTest('Test 8: Question type field present and valid', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_JS_EASY}`); if (response.data.success !== true) throw new Error('Response success should be true'); const question = response.data.data; if (!question.questionType) throw new Error('Question type should be present'); const validTypes = ['multiple', 'trueFalse', 'written']; if (!validTypes.includes(question.questionType)) { throw new Error(`Invalid question type: ${question.questionType}`); } console.log(` Question type: ${question.questionType}`); }); // Test 9: Options field present for multiple choice await runTest('Test 9: Options field present', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_REACT_EASY}`); if (response.data.success !== true) throw new Error('Response success should be true'); const question = response.data.data; if (question.questionType === 'multiple' && !question.options) { throw new Error('Options should be present for multiple choice questions'); } if (question.options && !Array.isArray(question.options)) { throw new Error('Options should be an array'); } console.log(` Options field validated (${question.options?.length || 0} options)`); }); // Test 10: Difficulty levels represented correctly await runTest('Test 10: Difficulty levels validated', async () => { const testQuestions = [ { id: QUESTION_IDS.GUEST_REACT_EASY, expected: 'easy' }, { id: QUESTION_IDS.AUTH_SYSTEM_DESIGN, expected: 'medium' }, { id: QUESTION_IDS.AUTH_NODEJS_HARD, expected: 'hard' }, ]; for (const testQ of testQuestions) { const response = await axios.get(`${BASE_URL}/questions/${testQ.id}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.data.difficulty !== testQ.expected) { throw new Error(`Expected difficulty ${testQ.expected}, got ${response.data.data.difficulty}`); } } console.log(` All difficulty levels validated (easy, medium, hard)`); }); // Test 11: Points based on difficulty await runTest('Test 11: Points correspond to difficulty', async () => { const response1 = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_REACT_EASY}`); const response2 = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.AUTH_SYSTEM_DESIGN}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); const response3 = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.AUTH_NODEJS_HARD}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); const easyPoints = response1.data.data.points; const mediumPoints = response2.data.data.points; const hardPoints = response3.data.data.points; // Actual point values from database: easy=5, medium=10, hard=15 if (easyPoints !== 5) throw new Error(`Easy should be 5 points, got ${easyPoints}`); if (mediumPoints !== 10) throw new Error(`Medium should be 10 points, got ${mediumPoints}`); if (hardPoints !== 15) throw new Error(`Hard should be 15 points, got ${hardPoints}`); console.log(` Points validated: easy=${easyPoints}, medium=${mediumPoints}, hard=${hardPoints}`); }); // Test 12: Tags and keywords present await runTest('Test 12: Tags and keywords fields present', async () => { const response = await axios.get(`${BASE_URL}/questions/${QUESTION_IDS.GUEST_JS_EASY}`); if (response.data.success !== true) throw new Error('Response success should be true'); const question = response.data.data; // Tags should be present (can be null or array) if (!('tags' in question)) throw new Error('Tags field should be present'); if (question.tags !== null && !Array.isArray(question.tags)) { throw new Error('Tags should be null or array'); } // Keywords should be present (can be null or array) if (!('keywords' in question)) throw new Error('Keywords field should be present'); if (question.keywords !== null && !Array.isArray(question.keywords)) { throw new Error('Keywords should be null or array'); } console.log(` Tags: ${question.tags?.length || 0}, Keywords: ${question.keywords?.length || 0}`); }); // Summary console.log('\n========================================'); console.log('Test Summary'); console.log('========================================'); console.log(`Passed: ${testResults.passed}`); console.log(`Failed: ${testResults.failed}`); console.log(`Total: ${testResults.total}`); console.log('========================================\n'); if (testResults.failed === 0) { console.log('✓ All tests passed!\n'); } else { console.log('✗ Some tests failed.\n'); process.exit(1); } } // Run tests runTests().catch(error => { console.error('Test execution failed:', error); process.exit(1); });