Files
tasks-backend/tests/test-questions-by-category.js
2025-12-26 23:56:32 +02:00

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);
});