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

777 lines
25 KiB
JavaScript

const axios = require('axios');
const API_URL = 'http://localhost:3000/api';
// Admin credentials (from seeder)
const adminUser = {
email: 'admin@quiz.com',
password: 'Admin@123'
};
// Regular user credentials (with timestamp to avoid conflicts)
const timestamp = Date.now();
const regularUser = {
username: `testuser${timestamp}`,
email: `testuser${timestamp}@example.com`,
password: 'Test@123'
};
// ANSI color codes
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m',
magenta: '\x1b[35m'
};
let adminToken = null;
let regularUserToken = null;
let testCategoryId = null;
let testQuestionId = null;
// Test counters
let totalTests = 0;
let passedTests = 0;
let failedTests = 0;
/**
* Helper: Log test result
*/
function logTestResult(testName, passed, error = null) {
totalTests++;
if (passed) {
passedTests++;
console.log(`${colors.green}${testName}${colors.reset}`);
} else {
failedTests++;
console.log(`${colors.red}${testName}${colors.reset}`);
if (error) {
console.log(` ${colors.red}Error: ${error}${colors.reset}`);
}
}
}
/**
* Login as admin
*/
async function loginAdmin() {
try {
const response = await axios.post(`${API_URL}/auth/login`, adminUser);
adminToken = response.data.data.token;
console.log(`${colors.cyan}✓ Logged in as admin${colors.reset}`);
return adminToken;
} catch (error) {
console.error(`${colors.red}✗ Failed to login as admin:${colors.reset}`, error.response?.data || error.message);
throw error;
}
}
/**
* Create and login regular user
*/
async function createRegularUser() {
try {
const registerResponse = await axios.post(`${API_URL}/auth/register`, regularUser);
regularUserToken = registerResponse.data.data.token;
console.log(`${colors.cyan}✓ Created and logged in as regular user${colors.reset}`);
return regularUserToken;
} catch (error) {
console.error(`${colors.red}✗ Failed to create regular user:${colors.reset}`, error.response?.data || error.message);
throw error;
}
}
/**
* Get first active category
*/
async function getFirstCategory() {
try {
const response = await axios.get(`${API_URL}/categories`, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
if (response.data.data && response.data.data.length > 0) {
testCategoryId = response.data.data[0].id;
console.log(`${colors.cyan}✓ Got test category: ${testCategoryId}${colors.reset}`);
return testCategoryId;
}
throw new Error('No categories found');
} catch (error) {
console.error(`${colors.red}✗ Failed to get category:${colors.reset}`, error.response?.data || error.message);
throw error;
}
}
/**
* Create a test question
*/
async function createTestQuestion() {
try {
const questionData = {
questionText: 'What is the capital of France?',
questionType: 'multiple',
options: [
{ id: 'a', text: 'Paris' },
{ id: 'b', text: 'London' },
{ id: 'c', text: 'Berlin' },
{ id: 'd', text: 'Madrid' }
],
correctAnswer: 'a',
difficulty: 'easy',
points: 10,
explanation: 'Paris is the capital and largest city of France.',
categoryId: testCategoryId,
tags: ['geography', 'capitals'],
keywords: ['france', 'paris', 'capital']
};
const response = await axios.post(`${API_URL}/admin/questions`, questionData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
testQuestionId = response.data.data.id;
console.log(`${colors.cyan}✓ Created test question: ${testQuestionId}${colors.reset}`);
return testQuestionId;
} catch (error) {
console.error(`${colors.red}✗ Failed to create test question:${colors.reset}`);
console.error('Status:', error.response?.status);
console.error('Data:', JSON.stringify(error.response?.data, null, 2));
console.error('Message:', error.message);
throw error;
}
}
// ============================================================================
// TEST SUITE: UPDATE QUESTION ENDPOINT
// ============================================================================
/**
* Test 1: Unauthenticated request cannot update question (401)
*/
async function test01_UnauthenticatedCannotUpdate() {
console.log(`\n${colors.blue}Test 1: Unauthenticated request cannot update question${colors.reset}`);
try {
const updateData = {
questionText: 'Updated question text'
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData);
logTestResult('Test 1: Unauthenticated request cannot update question', false, 'Should have returned 401');
} catch (error) {
const status = error.response?.status;
const passed = status === 401;
logTestResult('Test 1: Unauthenticated request cannot update question', passed, passed ? null : `Expected 401, got ${status}`);
}
}
/**
* Test 2: Regular user cannot update question (403)
*/
async function test02_UserCannotUpdate() {
console.log(`\n${colors.blue}Test 2: Regular user cannot update question${colors.reset}`);
try {
const updateData = {
questionText: 'Updated question text'
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${regularUserToken}` }
});
logTestResult('Test 2: Regular user cannot update question', false, 'Should have returned 403');
} catch (error) {
const status = error.response?.status;
const passed = status === 403;
logTestResult('Test 2: Regular user cannot update question', passed, passed ? null : `Expected 403, got ${status}`);
}
}
/**
* Test 3: Admin can update question text
*/
async function test03_UpdateQuestionText() {
console.log(`\n${colors.blue}Test 3: Admin can update question text${colors.reset}`);
try {
const updateData = {
questionText: 'What is the capital city of France?'
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success &&
data.questionText === updateData.questionText &&
data.id === testQuestionId;
logTestResult('Test 3: Admin can update question text', passed,
passed ? null : 'Question text not updated correctly');
} catch (error) {
logTestResult('Test 3: Admin can update question text', false, error.response?.data?.message || error.message);
}
}
/**
* Test 4: Update difficulty level
*/
async function test04_UpdateDifficulty() {
console.log(`\n${colors.blue}Test 4: Update difficulty level${colors.reset}`);
try {
const updateData = {
difficulty: 'medium'
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success && data.difficulty === 'medium';
logTestResult('Test 4: Update difficulty level', passed,
passed ? null : 'Difficulty not updated correctly');
} catch (error) {
logTestResult('Test 4: Update difficulty level', false, error.response?.data?.message || error.message);
}
}
/**
* Test 5: Update points
*/
async function test05_UpdatePoints() {
console.log(`\n${colors.blue}Test 5: Update points${colors.reset}`);
try {
const updateData = {
points: 20
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success && data.points === 20;
logTestResult('Test 5: Update points', passed,
passed ? null : 'Points not updated correctly');
} catch (error) {
logTestResult('Test 5: Update points', false, error.response?.data?.message || error.message);
}
}
/**
* Test 6: Update explanation
*/
async function test06_UpdateExplanation() {
console.log(`\n${colors.blue}Test 6: Update explanation${colors.reset}`);
try {
const updateData = {
explanation: 'Paris has been the capital of France since the 12th century.'
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success && data.explanation === updateData.explanation;
logTestResult('Test 6: Update explanation', passed,
passed ? null : 'Explanation not updated correctly');
} catch (error) {
logTestResult('Test 6: Update explanation', false, error.response?.data?.message || error.message);
}
}
/**
* Test 7: Update tags
*/
async function test07_UpdateTags() {
console.log(`\n${colors.blue}Test 7: Update tags${colors.reset}`);
try {
const updateData = {
tags: ['geography', 'europe', 'france', 'capitals']
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success &&
Array.isArray(data.tags) &&
data.tags.length === 4 &&
data.tags.includes('europe');
logTestResult('Test 7: Update tags', passed,
passed ? null : 'Tags not updated correctly');
} catch (error) {
logTestResult('Test 7: Update tags', false, error.response?.data?.message || error.message);
}
}
/**
* Test 8: Update multiple choice options
*/
async function test08_UpdateOptions() {
console.log(`\n${colors.blue}Test 8: Update multiple choice options${colors.reset}`);
try {
const updateData = {
options: [
{ id: 'a', text: 'Paris' },
{ id: 'b', text: 'London' },
{ id: 'c', text: 'Berlin' },
{ id: 'd', text: 'Madrid' },
{ id: 'e', text: 'Rome' }
]
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success &&
Array.isArray(data.options) &&
data.options.length === 5 &&
data.options.some(opt => opt.text === 'Rome');
logTestResult('Test 8: Update multiple choice options', passed,
passed ? null : 'Options not updated correctly');
} catch (error) {
logTestResult('Test 8: Update multiple choice options', false, error.response?.data?.message || error.message);
}
}
/**
* Test 9: Update correct answer
*/
async function test09_UpdateCorrectAnswer() {
console.log(`\n${colors.blue}Test 9: Update correct answer${colors.reset}`);
try {
// First update to add 'Lyon' as an option
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, {
options: [
{ id: 'a', text: 'Paris' },
{ id: 'b', text: 'London' },
{ id: 'c', text: 'Berlin' },
{ id: 'd', text: 'Madrid' },
{ id: 'e', text: 'Lyon' }
]
}, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
// Note: correctAnswer is not returned in response for security
// We just verify the update succeeds
const updateData = {
correctAnswer: 'a' // Keep as 'a' (Paris) since it's still valid
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success } = response.data;
const passed = success;
logTestResult('Test 9: Update correct answer', passed,
passed ? null : 'Update failed');
} catch (error) {
logTestResult('Test 9: Update correct answer', false, error.response?.data?.message || error.message);
}
}
/**
* Test 10: Update isActive status
*/
async function test10_UpdateIsActive() {
console.log(`\n${colors.blue}Test 10: Update isActive status${colors.reset}`);
try {
const updateData = {
isActive: false
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success && data.isActive === false;
// Reactivate for other tests
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, { isActive: true }, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 10: Update isActive status', passed,
passed ? null : 'isActive not updated correctly');
} catch (error) {
logTestResult('Test 10: Update isActive status', false, error.response?.data?.message || error.message);
}
}
/**
* Test 11: Update multiple fields at once
*/
async function test11_UpdateMultipleFields() {
console.log(`\n${colors.blue}Test 11: Update multiple fields at once${colors.reset}`);
try {
const updateData = {
questionText: 'What is the capital and largest city of France?',
difficulty: 'hard',
points: 30,
explanation: 'Paris is both the capital and the most populous city of France.',
tags: ['geography', 'france', 'cities'],
keywords: ['france', 'paris', 'capital', 'city']
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success &&
data.questionText === updateData.questionText &&
data.difficulty === 'hard' &&
data.points === 30 &&
data.explanation === updateData.explanation &&
data.tags.length === 3 &&
data.keywords.length === 4;
logTestResult('Test 11: Update multiple fields at once', passed,
passed ? null : 'Multiple fields not updated correctly');
} catch (error) {
logTestResult('Test 11: Update multiple fields at once', false, error.response?.data?.message || error.message);
}
}
/**
* Test 12: Invalid question ID (400)
*/
async function test12_InvalidQuestionId() {
console.log(`\n${colors.blue}Test 12: Invalid question ID${colors.reset}`);
try {
const updateData = {
questionText: 'Updated text'
};
await axios.put(`${API_URL}/admin/questions/invalid-id`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 12: Invalid question ID', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 12: Invalid question ID', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 13: Non-existent question (404)
*/
async function test13_NonExistentQuestion() {
console.log(`\n${colors.blue}Test 13: Non-existent question${colors.reset}`);
try {
const fakeUuid = '00000000-0000-0000-0000-000000000000';
const updateData = {
questionText: 'Updated text'
};
await axios.put(`${API_URL}/admin/questions/${fakeUuid}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 13: Non-existent question', false, 'Should have returned 404');
} catch (error) {
const status = error.response?.status;
const passed = status === 404;
logTestResult('Test 13: Non-existent question', passed, passed ? null : `Expected 404, got ${status}`);
}
}
/**
* Test 14: Invalid difficulty value (400)
*/
async function test14_InvalidDifficulty() {
console.log(`\n${colors.blue}Test 14: Invalid difficulty value${colors.reset}`);
try {
const updateData = {
difficulty: 'super-hard'
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 14: Invalid difficulty value', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 14: Invalid difficulty value', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 15: Invalid points value (400)
*/
async function test15_InvalidPoints() {
console.log(`\n${colors.blue}Test 15: Invalid points value${colors.reset}`);
try {
const updateData = {
points: -10
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 15: Invalid points value', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 15: Invalid points value', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 16: Empty question text (400)
*/
async function test16_EmptyQuestionText() {
console.log(`\n${colors.blue}Test 16: Empty question text${colors.reset}`);
try {
const updateData = {
questionText: ''
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 16: Empty question text', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 16: Empty question text', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 17: Update with less than 2 options for multiple choice (400)
*/
async function test17_InsufficientOptions() {
console.log(`\n${colors.blue}Test 17: Insufficient options for multiple choice${colors.reset}`);
try {
const updateData = {
options: [{ id: 'a', text: 'Paris' }]
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 17: Insufficient options for multiple choice', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 17: Insufficient options for multiple choice', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 18: Correct answer not in options (400)
*/
async function test18_CorrectAnswerNotInOptions() {
console.log(`\n${colors.blue}Test 18: Correct answer not in options${colors.reset}`);
try {
const updateData = {
correctAnswer: 'z' // Invalid option ID
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 18: Correct answer not in options', false, 'Should have returned 400');
} catch (error) {
const status = error.response?.status;
const passed = status === 400;
logTestResult('Test 18: Correct answer not in options', passed, passed ? null : `Expected 400, got ${status}`);
}
}
/**
* Test 19: Update category to non-existent category (404)
*/
async function test19_NonExistentCategory() {
console.log(`\n${colors.blue}Test 19: Update to non-existent category${colors.reset}`);
try {
const fakeUuid = '00000000-0000-0000-0000-000000000000';
const updateData = {
categoryId: fakeUuid
};
await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
logTestResult('Test 19: Update to non-existent category', false, 'Should have returned 404');
} catch (error) {
const status = error.response?.status;
const passed = status === 404;
logTestResult('Test 19: Update to non-existent category', passed, passed ? null : `Expected 404, got ${status}`);
}
}
/**
* Test 20: Response doesn't include correctAnswer (security)
*/
async function test20_NoCorrectAnswerInResponse() {
console.log(`\n${colors.blue}Test 20: Response doesn't expose correct answer${colors.reset}`);
try {
const updateData = {
questionText: 'What is the capital of France? (Updated)'
};
const response = await axios.put(`${API_URL}/admin/questions/${testQuestionId}`, updateData, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
const { success, data } = response.data;
const passed = success && !data.hasOwnProperty('correctAnswer') && !data.hasOwnProperty('correct_answer');
logTestResult('Test 20: Response doesn\'t expose correct answer', passed,
passed ? null : 'correctAnswer should not be in response');
} catch (error) {
logTestResult('Test 20: Response doesn\'t expose correct answer', false, error.response?.data?.message || error.message);
}
}
// ============================================================================
// CLEANUP
// ============================================================================
/**
* Delete test question
*/
async function deleteTestQuestion() {
try {
if (testQuestionId) {
await axios.delete(`${API_URL}/admin/questions/${testQuestionId}`, {
headers: { 'Authorization': `Bearer ${adminToken}` }
});
console.log(`${colors.cyan}✓ Deleted test question${colors.reset}`);
}
} catch (error) {
console.error(`${colors.yellow}⚠ Failed to delete test question:${colors.reset}`, error.response?.data || error.message);
}
}
// ============================================================================
// MAIN TEST RUNNER
// ============================================================================
async function runAllTests() {
console.log(`${colors.magenta}
╔════════════════════════════════════════════════════════════╗
║ ADMIN UPDATE QUESTION ENDPOINT - TEST SUITE ║
╚════════════════════════════════════════════════════════════╝
${colors.reset}`);
try {
// Setup
console.log(`${colors.cyan}\n--- Setup Phase ---${colors.reset}`);
await loginAdmin();
await createRegularUser();
await getFirstCategory();
await createTestQuestion();
// Run tests
console.log(`${colors.cyan}\n--- Running Tests ---${colors.reset}`);
// Authorization tests
await test01_UnauthenticatedCannotUpdate();
await test02_UserCannotUpdate();
// Update field tests
await test03_UpdateQuestionText();
await test04_UpdateDifficulty();
await test05_UpdatePoints();
await test06_UpdateExplanation();
await test07_UpdateTags();
await test08_UpdateOptions();
await test09_UpdateCorrectAnswer();
await test10_UpdateIsActive();
await test11_UpdateMultipleFields();
// Error handling tests
await test12_InvalidQuestionId();
await test13_NonExistentQuestion();
await test14_InvalidDifficulty();
await test15_InvalidPoints();
await test16_EmptyQuestionText();
await test17_InsufficientOptions();
await test18_CorrectAnswerNotInOptions();
await test19_NonExistentCategory();
// Security tests
await test20_NoCorrectAnswerInResponse();
// Cleanup
console.log(`${colors.cyan}\n--- Cleanup Phase ---${colors.reset}`);
await deleteTestQuestion();
// Summary
console.log(`${colors.magenta}
╔════════════════════════════════════════════════════════════╗
║ TEST SUMMARY ║
╚════════════════════════════════════════════════════════════╝
${colors.reset}`);
console.log(`Total Tests: ${totalTests}`);
console.log(`${colors.green}Passed: ${passedTests}${colors.reset}`);
console.log(`${colors.red}Failed: ${failedTests}${colors.reset}`);
console.log(`Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%\n`);
if (failedTests === 0) {
console.log(`${colors.green}✓ All tests passed!${colors.reset}\n`);
process.exit(0);
} else {
console.log(`${colors.red}✗ Some tests failed${colors.reset}\n`);
process.exit(1);
}
} catch (error) {
console.error(`${colors.red}\n✗ Test suite failed:${colors.reset}`, error.message);
process.exit(1);
}
}
// Run the tests
runAllTests();