330 lines
14 KiB
JavaScript
330 lines
14 KiB
JavaScript
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);
|
|
});
|