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 Question Search API'); console.log('========================================\n'); await setup(); // Test 1: Basic search without auth (guest accessible only) await runTest('Test 1: Basic search without auth', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=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.query !== 'javascript') throw new Error('Query not reflected in response'); if (typeof response.data.total !== 'number') throw new Error('Total should be a number'); console.log(` Found ${response.data.total} results for "javascript" (guest)`); }); // Test 2: Missing search query await runTest('Test 2: Missing search query returns 400', async () => { try { await axios.get(`${BASE_URL}/questions/search`); 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('required')) { throw new Error('Error message should mention required query'); } console.log(` Correctly rejected missing query`); } }); // Test 3: Empty search query await runTest('Test 3: Empty search query returns 400', async () => { try { await axios.get(`${BASE_URL}/questions/search?q=`); throw new Error('Should have returned 400'); } catch (error) { if (error.response?.status !== 400) throw new Error(`Expected 400, got ${error.response?.status}`); console.log(` Correctly rejected empty query`); } }); // Test 4: Authenticated user sees more results await runTest('Test 4: Authenticated user sees more results', async () => { const guestResponse = await axios.get(`${BASE_URL}/questions/search?q=node`); const authResponse = await axios.get(`${BASE_URL}/questions/search?q=node`, { headers: { Authorization: `Bearer ${regularUserToken}` } }); if (authResponse.data.total < guestResponse.data.total) { throw new Error('Authenticated user should see at least as many results as guest'); } console.log(` Guest: ${guestResponse.data.total} results, Auth: ${authResponse.data.total} results`); }); // Test 5: Search with category filter await runTest('Test 5: Search with category filter', async () => { const response = await axios.get( `${BASE_URL}/questions/search?q=what&category=${CATEGORY_IDS.JAVASCRIPT}` ); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.filters.category !== CATEGORY_IDS.JAVASCRIPT) { throw new Error('Category filter not applied'); } // Verify all results are from JavaScript category const allFromCategory = response.data.data.every(q => q.category.name === 'JavaScript'); if (!allFromCategory) throw new Error('Not all results are from JavaScript category'); console.log(` Found ${response.data.count} JavaScript questions matching "what"`); }); // Test 6: Search with difficulty filter await runTest('Test 6: Search with difficulty filter', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=what&difficulty=easy`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.filters.difficulty !== 'easy') { throw new Error('Difficulty filter not applied'); } // Verify all results are easy difficulty const allEasy = response.data.data.every(q => q.difficulty === 'easy'); if (!allEasy) throw new Error('Not all results are easy difficulty'); console.log(` Found ${response.data.count} easy questions matching "what"`); }); // Test 7: Search with combined filters await runTest('Test 7: Search with combined filters', async () => { const response = await axios.get( `${BASE_URL}/questions/search?q=what&category=${CATEGORY_IDS.REACT}&difficulty=easy`, { headers: { Authorization: `Bearer ${regularUserToken}` } } ); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.filters.category !== CATEGORY_IDS.REACT) { throw new Error('Category filter not applied'); } if (response.data.filters.difficulty !== 'easy') { throw new Error('Difficulty filter not applied'); } console.log(` Found ${response.data.count} easy React questions matching "what"`); }); // Test 8: Invalid category UUID await runTest('Test 8: Invalid category UUID returns 400', async () => { try { await axios.get(`${BASE_URL}/questions/search?q=javascript&category=invalid-uuid`); 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 category ID'); } console.log(` Correctly rejected invalid category UUID`); } }); // Test 9: Pagination - page 1 await runTest('Test 9: Pagination support (page 1)', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=what&limit=3&page=1`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.page !== 1) throw new Error('Page should be 1'); if (response.data.limit !== 3) throw new Error('Limit should be 3'); if (response.data.data.length > 3) throw new Error('Should return max 3 results'); if (typeof response.data.totalPages !== 'number') throw new Error('totalPages should be present'); console.log(` Page 1: ${response.data.count} results (total: ${response.data.total}, pages: ${response.data.totalPages})`); }); // Test 10: Pagination - page 2 await runTest('Test 10: Pagination (page 2)', async () => { const page1 = await axios.get(`${BASE_URL}/questions/search?q=what&limit=2&page=1`); const page2 = await axios.get(`${BASE_URL}/questions/search?q=what&limit=2&page=2`); if (page2.data.page !== 2) throw new Error('Page should be 2'); // Verify different results on page 2 const page1Ids = page1.data.data.map(q => q.id); const page2Ids = page2.data.data.map(q => q.id); const hasDifferentIds = page2Ids.some(id => !page1Ids.includes(id)); if (!hasDifferentIds && page2.data.data.length > 0) { throw new Error('Page 2 should have different results than page 1'); } console.log(` Page 2: ${page2.data.count} results`); }); // Test 11: Response structure validation await runTest('Test 11: Response structure validation', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=javascript&limit=1`); // Check top-level structure const requiredFields = ['success', 'count', 'total', 'page', 'totalPages', 'limit', 'query', 'filters', 'data', 'message']; for (const field of requiredFields) { if (!(field in response.data)) throw new Error(`Missing field: ${field}`); } // Check filters structure if (!('category' in response.data.filters)) throw new Error('Missing filters.category'); if (!('difficulty' in response.data.filters)) throw new Error('Missing filters.difficulty'); // Check question structure (if results exist) if (response.data.data.length > 0) { const question = response.data.data[0]; const questionFields = ['id', 'questionText', 'highlightedText', 'questionType', 'difficulty', 'points', 'accuracy', 'relevance', 'category']; for (const field of questionFields) { if (!(field in question)) throw new Error(`Missing question field: ${field}`); } // Check category structure const categoryFields = ['id', 'name', 'slug', 'icon', 'color']; 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 12: Text highlighting present await runTest('Test 12: Text highlighting in results', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=javascript`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.data.length > 0) { const question = response.data.data[0]; // Check that highlightedText exists if (!('highlightedText' in question)) throw new Error('highlightedText should be present'); // Check if highlighting was applied (basic check for ** markers) const hasHighlight = question.highlightedText && question.highlightedText.includes('**'); console.log(` Highlighting ${hasHighlight ? 'applied' : 'not applied (no match in this result)'}`); } else { console.log(` No results to check highlighting`); } }); // Test 13: Relevance scoring await runTest('Test 13: Relevance scoring present', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=react hooks`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.data.length > 0) { // Check that relevance field exists for (const question of response.data.data) { if (!('relevance' in question)) throw new Error('relevance should be present'); if (typeof question.relevance !== 'number') throw new Error('relevance should be a number'); } // Check that results are ordered by relevance (descending) for (let i = 0; i < response.data.data.length - 1; i++) { if (response.data.data[i].relevance < response.data.data[i + 1].relevance) { throw new Error('Results should be ordered by relevance (descending)'); } } console.log(` Relevance scores: ${response.data.data.map(q => q.relevance.toFixed(2)).join(', ')}`); } else { console.log(` No results to check relevance`); } }); // Test 14: Max limit enforcement (100) await runTest('Test 14: Max limit enforcement (limit=200 should cap at 100)', async () => { const response = await axios.get(`${BASE_URL}/questions/search?q=what&limit=200`); if (response.data.success !== true) throw new Error('Response success should be true'); if (response.data.limit > 100) throw new Error('Limit should be capped at 100'); if (response.data.data.length > 100) throw new Error('Should return max 100 results'); console.log(` Limit capped at ${response.data.limit} (requested 200)`); }); // 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); });