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

333 lines
13 KiB
JavaScript

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