const axios = require('axios'); const BASE_URL = 'http://localhost:3000/api'; // Category UUIDs from database const CATEGORY_IDS = { JAVASCRIPT: '68b4c87f-db0b-48ea-b8a4-b2f4fce785a2', // Guest accessible ANGULAR: '0033017b-6ecb-4e9d-8f13-06fc222c1dfc', // Guest accessible REACT: 'd27e3412-6f2b-432d-8d63-1e165ea5fffd', // Guest accessible NODEJS: '5e3094ab-ab6d-4f8a-9261-8177b9c979ae', // Auth only TYPESCRIPT: '7a71ab02-a7e4-4a76-a364-de9d6e3f3411', // Auth only }; 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 Questions by Category API'); console.log('========================================\n'); await setup(); // Test 1: Get guest-accessible category questions without auth await runTest('Test 1: Get guest-accessible questions without auth', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}`); if (response.data.success !== true) throw new Error('Response success should be true'); if (!Array.isArray(response.data.data)) throw new Error('Response data should be an array'); if (response.data.count !== response.data.data.length) throw new Error('Count mismatch'); if (!response.data.category) throw new Error('Category info should be included'); if (response.data.category.name !== 'JavaScript') throw new Error('Wrong category'); console.log(` Retrieved ${response.data.count} questions from JavaScript (guest)`); }); // Test 2: Guest blocked from auth-only category await runTest('Test 2: Guest blocked from auth-only category', async () => { try { await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.NODEJS}`); 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 all categories await runTest('Test 3: Authenticated user can access auth-only category', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.NODEJS}`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); if (!Array.isArray(response.data.data)) throw new Error('Response data should be an array'); if (response.data.category.name !== 'Node.js') throw new Error('Wrong category'); console.log(` Retrieved ${response.data.count} questions from Node.js (authenticated)`); }); // Test 4: Invalid category UUID format await runTest('Test 4: Invalid category UUID format', async () => { try { await axios.get(`${BASE_URL}/questions/category/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 category ID')) { throw new Error('Should mention invalid ID format'); } console.log(` Correctly rejected invalid UUID`); } }); // Test 5: Non-existent category await runTest('Test 5: Non-existent category', async () => { const fakeUuid = '00000000-0000-0000-0000-000000000000'; try { await axios.get(`${BASE_URL}/questions/category/${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 category not found'); } console.log(` Correctly returned 404 for non-existent category`); } }); // Test 6: Filter by difficulty - easy await runTest('Test 6: Filter by difficulty (easy)', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?difficulty=easy`); if (response.data.success !== true) throw new Error('Response success should be true'); if (!Array.isArray(response.data.data)) throw new Error('Response data should be an array'); if (response.data.filters.difficulty !== 'easy') throw new Error('Filter not applied'); // Verify all questions are easy const allEasy = response.data.data.every(q => q.difficulty === 'easy'); if (!allEasy) throw new Error('Not all questions are easy difficulty'); console.log(` Retrieved ${response.data.count} easy questions`); }); // Test 7: Filter by difficulty - medium await runTest('Test 7: Filter by difficulty (medium)', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.ANGULAR}?difficulty=medium`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.filters.difficulty !== 'medium') throw new Error('Filter not applied'); // Verify all questions are medium const allMedium = response.data.data.every(q => q.difficulty === 'medium'); if (!allMedium) throw new Error('Not all questions are medium difficulty'); console.log(` Retrieved ${response.data.count} medium questions`); }); // Test 8: Filter by difficulty - hard await runTest('Test 8: Filter by difficulty (hard)', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.REACT}?difficulty=hard`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.filters.difficulty !== 'hard') throw new Error('Filter not applied'); // Verify all questions are hard const allHard = response.data.data.every(q => q.difficulty === 'hard'); if (!allHard) throw new Error('Not all questions are hard difficulty'); console.log(` Retrieved ${response.data.count} hard questions`); }); // Test 9: Limit parameter await runTest('Test 9: Limit parameter (limit=3)', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?limit=3`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.data.length > 3) throw new Error('Limit not respected'); if (response.data.filters.limit !== 3) throw new Error('Limit not reflected in filters'); console.log(` Retrieved ${response.data.count} questions (limited to 3)`); }); // Test 10: Random selection await runTest('Test 10: Random selection (random=true)', async () => { const response1 = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?random=true&limit=5`); const response2 = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?random=true&limit=5`); if (response1.data.success !== true) throw new Error('Response success should be true'); if (response1.data.filters.random !== true) throw new Error('Random flag not set'); if (response2.data.filters.random !== true) throw new Error('Random flag not set'); // Check that the order is different (may occasionally fail if random picks same order) const ids1 = response1.data.data.map(q => q.id); const ids2 = response2.data.data.map(q => q.id); const sameOrder = JSON.stringify(ids1) === JSON.stringify(ids2); console.log(` Random selection enabled (orders ${sameOrder ? 'same' : 'different'})`); }); // Test 11: Response structure validation await runTest('Test 11: Response structure validation', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?limit=1`); // Check top-level structure const requiredFields = ['success', 'count', 'total', 'category', 'filters', 'data', 'message']; for (const field of requiredFields) { if (!(field in response.data)) throw new Error(`Missing field: ${field}`); } // Check category structure const categoryFields = ['id', 'name', 'slug', 'icon', 'color']; for (const field of categoryFields) { if (!(field in response.data.category)) throw new Error(`Missing category field: ${field}`); } // Check filters structure const filterFields = ['difficulty', 'limit', 'random']; for (const field of filterFields) { if (!(field in response.data.filters)) throw new Error(`Missing filter field: ${field}`); } // Check question structure (if questions exist) if (response.data.data.length > 0) { const question = response.data.data[0]; const questionFields = ['id', 'questionText', 'questionType', 'options', 'difficulty', 'points', 'accuracy', 'tags']; for (const field of questionFields) { if (!(field in question)) throw new Error(`Missing question 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 12: Question accuracy calculation await runTest('Test 12: Question accuracy calculation', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?limit=5`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (response.data.success !== true) throw new Error('Response success should be true'); // Check each question has accuracy field for (const question of 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}`); } } console.log(` Accuracy calculated for all questions`); }); // Test 13: Combined filters (difficulty + limit) await runTest('Test 13: Combined filters (difficulty + limit)', async () => { const response = await axios.get( `${BASE_URL}/questions/category/${CATEGORY_IDS.TYPESCRIPT}?difficulty=easy&limit=2`, { headers: { Authorization: `Bearer ${regularUserToken}` } } ); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.data.length > 2) throw new Error('Limit not respected'); if (response.data.filters.difficulty !== 'easy') throw new Error('Difficulty filter not applied'); if (response.data.filters.limit !== 2) throw new Error('Limit filter not applied'); const allEasy = response.data.data.every(q => q.difficulty === 'easy'); if (!allEasy) throw new Error('Not all questions are easy difficulty'); console.log(` Retrieved ${response.data.count} easy questions (limited to 2)`); }); // Test 14: Max limit enforcement (50) await runTest('Test 14: Max limit enforcement (limit=100 should cap at 50)', async () => { const response = await axios.get(`${BASE_URL}/questions/category/${CATEGORY_IDS.JAVASCRIPT}?limit=100`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.data.length > 50) throw new Error('Max limit (50) not enforced'); if (response.data.filters.limit > 50) throw new Error('Limit should be capped at 50'); console.log(` Limit capped at ${response.data.filters.limit} (requested 100)`); }); // 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); });