add changes
This commit is contained in:
776
backend/tests/test-admin-update-question.js
Normal file
776
backend/tests/test-admin-update-question.js
Normal file
@@ -0,0 +1,776 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user