add changes
This commit is contained in:
571
backend/test-category-admin.js
Normal file
571
backend/test-category-admin.js
Normal file
@@ -0,0 +1,571 @@
|
||||
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 (we'll create one for testing - 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'
|
||||
};
|
||||
|
||||
let adminToken = null;
|
||||
let regularUserToken = null;
|
||||
let testCategoryId = null;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
// Register
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 1: Create category as admin
|
||||
*/
|
||||
async function testCreateCategoryAsAdmin() {
|
||||
console.log(`\n${colors.blue}Test 1: Create category as admin${colors.reset}`);
|
||||
|
||||
try {
|
||||
const newCategory = {
|
||||
name: 'Test Category',
|
||||
description: 'A test category for admin operations',
|
||||
icon: 'test-icon',
|
||||
color: '#FF5733',
|
||||
guestAccessible: false,
|
||||
displayOrder: 10
|
||||
};
|
||||
|
||||
const response = await axios.post(`${API_URL}/categories`, newCategory, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const { success, data, message } = response.data;
|
||||
|
||||
if (!success) throw new Error('success should be true');
|
||||
if (!data.id) throw new Error('Missing category ID');
|
||||
if (data.name !== newCategory.name) throw new Error('Name mismatch');
|
||||
if (data.slug !== 'test-category') throw new Error('Slug should be auto-generated');
|
||||
if (data.color !== newCategory.color) throw new Error('Color mismatch');
|
||||
if (data.guestAccessible !== false) throw new Error('guestAccessible mismatch');
|
||||
if (data.questionCount !== 0) throw new Error('questionCount should be 0');
|
||||
if (data.isActive !== true) throw new Error('isActive should be true');
|
||||
|
||||
// Save for later tests
|
||||
testCategoryId = data.id;
|
||||
|
||||
console.log(`${colors.green}✓ Test 1 Passed${colors.reset}`);
|
||||
console.log(` Category ID: ${data.id}`);
|
||||
console.log(` Name: ${data.name}`);
|
||||
console.log(` Slug: ${data.slug}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}✗ Test 1 Failed:${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2: Create category without authentication
|
||||
*/
|
||||
async function testCreateCategoryNoAuth() {
|
||||
console.log(`\n${colors.blue}Test 2: Create category without authentication${colors.reset}`);
|
||||
|
||||
try {
|
||||
const newCategory = {
|
||||
name: 'Unauthorized Category',
|
||||
description: 'Should not be created'
|
||||
};
|
||||
|
||||
await axios.post(`${API_URL}/categories`, newCategory);
|
||||
|
||||
console.error(`${colors.red}✗ Test 2 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 401) {
|
||||
console.log(`${colors.green}✓ Test 2 Passed${colors.reset}`);
|
||||
console.log(` Status: 401 Unauthorized`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`${colors.red}✗ Test 2 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 3: Create category as regular user
|
||||
*/
|
||||
async function testCreateCategoryAsRegularUser() {
|
||||
console.log(`\n${colors.blue}Test 3: Create category as regular user (non-admin)${colors.reset}`);
|
||||
|
||||
try {
|
||||
const newCategory = {
|
||||
name: 'Regular User Category',
|
||||
description: 'Should not be created'
|
||||
};
|
||||
|
||||
await axios.post(`${API_URL}/categories`, newCategory, {
|
||||
headers: { 'Authorization': `Bearer ${regularUserToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 3 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
console.log(`${colors.green}✓ Test 3 Passed${colors.reset}`);
|
||||
console.log(` Status: 403 Forbidden`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`${colors.red}✗ Test 3 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4: Create category with duplicate name
|
||||
*/
|
||||
async function testCreateCategoryDuplicateName() {
|
||||
console.log(`\n${colors.blue}Test 4: Create category with duplicate name${colors.reset}`);
|
||||
|
||||
try {
|
||||
const duplicateCategory = {
|
||||
name: 'Test Category', // Same as test 1
|
||||
description: 'Duplicate name'
|
||||
};
|
||||
|
||||
await axios.post(`${API_URL}/categories`, duplicateCategory, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 4 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
const { message } = error.response.data;
|
||||
if (message.includes('already exists')) {
|
||||
console.log(`${colors.green}✓ Test 4 Passed${colors.reset}`);
|
||||
console.log(` Status: 400 Bad Request`);
|
||||
console.log(` Message: ${message}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.error(`${colors.red}✗ Test 4 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 5: Create category without required name
|
||||
*/
|
||||
async function testCreateCategoryMissingName() {
|
||||
console.log(`\n${colors.blue}Test 5: Create category without required name${colors.reset}`);
|
||||
|
||||
try {
|
||||
const invalidCategory = {
|
||||
description: 'No name provided'
|
||||
};
|
||||
|
||||
await axios.post(`${API_URL}/categories`, invalidCategory, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 5 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
const { message } = error.response.data;
|
||||
if (message.includes('required')) {
|
||||
console.log(`${colors.green}✓ Test 5 Passed${colors.reset}`);
|
||||
console.log(` Status: 400 Bad Request`);
|
||||
console.log(` Message: ${message}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.error(`${colors.red}✗ Test 5 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 6: Update category as admin
|
||||
*/
|
||||
async function testUpdateCategoryAsAdmin() {
|
||||
console.log(`\n${colors.blue}Test 6: Update category as admin${colors.reset}`);
|
||||
|
||||
try {
|
||||
const updates = {
|
||||
description: 'Updated description',
|
||||
guestAccessible: true,
|
||||
displayOrder: 20
|
||||
};
|
||||
|
||||
const response = await axios.put(`${API_URL}/categories/${testCategoryId}`, updates, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const { success, data } = response.data;
|
||||
|
||||
if (!success) throw new Error('success should be true');
|
||||
if (data.description !== updates.description) throw new Error('Description not updated');
|
||||
if (data.guestAccessible !== updates.guestAccessible) throw new Error('guestAccessible not updated');
|
||||
if (data.displayOrder !== updates.displayOrder) throw new Error('displayOrder not updated');
|
||||
|
||||
console.log(`${colors.green}✓ Test 6 Passed${colors.reset}`);
|
||||
console.log(` Updated description: ${data.description}`);
|
||||
console.log(` Updated guestAccessible: ${data.guestAccessible}`);
|
||||
console.log(` Updated displayOrder: ${data.displayOrder}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}✗ Test 6 Failed:${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 7: Update category as regular user
|
||||
*/
|
||||
async function testUpdateCategoryAsRegularUser() {
|
||||
console.log(`\n${colors.blue}Test 7: Update category as regular user (non-admin)${colors.reset}`);
|
||||
|
||||
try {
|
||||
const updates = {
|
||||
description: 'Should not update'
|
||||
};
|
||||
|
||||
await axios.put(`${API_URL}/categories/${testCategoryId}`, updates, {
|
||||
headers: { 'Authorization': `Bearer ${regularUserToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 7 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
console.log(`${colors.green}✓ Test 7 Passed${colors.reset}`);
|
||||
console.log(` Status: 403 Forbidden`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`${colors.red}✗ Test 7 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 8: Update non-existent category
|
||||
*/
|
||||
async function testUpdateNonExistentCategory() {
|
||||
console.log(`\n${colors.blue}Test 8: Update non-existent category${colors.reset}`);
|
||||
|
||||
try {
|
||||
const fakeId = '00000000-0000-0000-0000-000000000000';
|
||||
const updates = {
|
||||
description: 'Should not work'
|
||||
};
|
||||
|
||||
await axios.put(`${API_URL}/categories/${fakeId}`, updates, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 8 Failed: Should have returned 404${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
console.log(`${colors.green}✓ Test 8 Passed${colors.reset}`);
|
||||
console.log(` Status: 404 Not Found`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`${colors.red}✗ Test 8 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 9: Update category with duplicate name
|
||||
*/
|
||||
async function testUpdateCategoryDuplicateName() {
|
||||
console.log(`\n${colors.blue}Test 9: Update category with duplicate name${colors.reset}`);
|
||||
|
||||
try {
|
||||
const updates = {
|
||||
name: 'JavaScript' // Existing category from seed data
|
||||
};
|
||||
|
||||
await axios.put(`${API_URL}/categories/${testCategoryId}`, updates, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 9 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
const { message } = error.response.data;
|
||||
if (message.includes('already exists')) {
|
||||
console.log(`${colors.green}✓ Test 9 Passed${colors.reset}`);
|
||||
console.log(` Status: 400 Bad Request`);
|
||||
console.log(` Message: ${message}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.error(`${colors.red}✗ Test 9 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 10: Delete category as admin
|
||||
*/
|
||||
async function testDeleteCategoryAsAdmin() {
|
||||
console.log(`\n${colors.blue}Test 10: Delete category as admin (soft delete)${colors.reset}`);
|
||||
|
||||
try {
|
||||
const response = await axios.delete(`${API_URL}/categories/${testCategoryId}`, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const { success, data, message } = response.data;
|
||||
|
||||
if (!success) throw new Error('success should be true');
|
||||
if (data.id !== testCategoryId) throw new Error('ID mismatch');
|
||||
if (!message.includes('successfully')) throw new Error('Success message expected');
|
||||
|
||||
console.log(`${colors.green}✓ Test 10 Passed${colors.reset}`);
|
||||
console.log(` Category: ${data.name}`);
|
||||
console.log(` Message: ${message}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}✗ Test 10 Failed:${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 11: Verify deleted category is not in active list
|
||||
*/
|
||||
async function testDeletedCategoryNotInList() {
|
||||
console.log(`\n${colors.blue}Test 11: Verify deleted category not in active list${colors.reset}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/categories`, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const { data } = response.data;
|
||||
const deletedCategory = data.find(cat => cat.id === testCategoryId);
|
||||
|
||||
if (deletedCategory) {
|
||||
throw new Error('Deleted category should not appear in active list');
|
||||
}
|
||||
|
||||
console.log(`${colors.green}✓ Test 11 Passed${colors.reset}`);
|
||||
console.log(` Deleted category not in active list`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}✗ Test 11 Failed:${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 12: Delete already deleted category
|
||||
*/
|
||||
async function testDeleteAlreadyDeletedCategory() {
|
||||
console.log(`\n${colors.blue}Test 12: Delete already deleted category${colors.reset}`);
|
||||
|
||||
try {
|
||||
await axios.delete(`${API_URL}/categories/${testCategoryId}`, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 12 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 400) {
|
||||
const { message } = error.response.data;
|
||||
if (message.includes('already deleted')) {
|
||||
console.log(`${colors.green}✓ Test 12 Passed${colors.reset}`);
|
||||
console.log(` Status: 400 Bad Request`);
|
||||
console.log(` Message: ${message}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.error(`${colors.red}✗ Test 12 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 13: Delete category as regular user
|
||||
*/
|
||||
async function testDeleteCategoryAsRegularUser() {
|
||||
console.log(`\n${colors.blue}Test 13: Delete category as regular user (non-admin)${colors.reset}`);
|
||||
|
||||
try {
|
||||
// Create a new category for this test
|
||||
const newCategory = {
|
||||
name: 'Delete Test Category',
|
||||
description: 'For delete permissions test'
|
||||
};
|
||||
|
||||
const createResponse = await axios.post(`${API_URL}/categories`, newCategory, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const categoryId = createResponse.data.data.id;
|
||||
|
||||
// Try to delete as regular user
|
||||
await axios.delete(`${API_URL}/categories/${categoryId}`, {
|
||||
headers: { 'Authorization': `Bearer ${regularUserToken}` }
|
||||
});
|
||||
|
||||
console.error(`${colors.red}✗ Test 13 Failed: Should have been rejected${colors.reset}`);
|
||||
return false;
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
console.log(`${colors.green}✓ Test 13 Passed${colors.reset}`);
|
||||
console.log(` Status: 403 Forbidden`);
|
||||
return true;
|
||||
} else {
|
||||
console.error(`${colors.red}✗ Test 13 Failed: Wrong error${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 14: Create category with custom slug
|
||||
*/
|
||||
async function testCreateCategoryWithCustomSlug() {
|
||||
console.log(`\n${colors.blue}Test 14: Create category with custom slug${colors.reset}`);
|
||||
|
||||
try {
|
||||
const newCategory = {
|
||||
name: 'Custom Slug Category',
|
||||
slug: 'my-custom-slug',
|
||||
description: 'Testing custom slug'
|
||||
};
|
||||
|
||||
const response = await axios.post(`${API_URL}/categories`, newCategory, {
|
||||
headers: { 'Authorization': `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const { success, data } = response.data;
|
||||
|
||||
if (!success) throw new Error('success should be true');
|
||||
if (data.slug !== 'my-custom-slug') throw new Error('Custom slug not applied');
|
||||
|
||||
console.log(`${colors.green}✓ Test 14 Passed${colors.reset}`);
|
||||
console.log(` Custom slug: ${data.slug}`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}✗ Test 14 Failed:${colors.reset}`, error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all tests
|
||||
*/
|
||||
async function runAllTests() {
|
||||
console.log(`${colors.cyan}========================================${colors.reset}`);
|
||||
console.log(`${colors.cyan}Testing Category Admin API${colors.reset}`);
|
||||
console.log(`${colors.cyan}========================================${colors.reset}`);
|
||||
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
// Setup
|
||||
await loginAdmin();
|
||||
await createRegularUser();
|
||||
|
||||
// Run tests
|
||||
results.push(await testCreateCategoryAsAdmin());
|
||||
results.push(await testCreateCategoryNoAuth());
|
||||
results.push(await testCreateCategoryAsRegularUser());
|
||||
results.push(await testCreateCategoryDuplicateName());
|
||||
results.push(await testCreateCategoryMissingName());
|
||||
results.push(await testUpdateCategoryAsAdmin());
|
||||
results.push(await testUpdateCategoryAsRegularUser());
|
||||
results.push(await testUpdateNonExistentCategory());
|
||||
results.push(await testUpdateCategoryDuplicateName());
|
||||
results.push(await testDeleteCategoryAsAdmin());
|
||||
results.push(await testDeletedCategoryNotInList());
|
||||
results.push(await testDeleteAlreadyDeletedCategory());
|
||||
results.push(await testDeleteCategoryAsRegularUser());
|
||||
results.push(await testCreateCategoryWithCustomSlug());
|
||||
|
||||
// Summary
|
||||
console.log(`\n${colors.cyan}========================================${colors.reset}`);
|
||||
console.log(`${colors.cyan}Test Summary${colors.reset}`);
|
||||
console.log(`${colors.cyan}========================================${colors.reset}`);
|
||||
|
||||
const passed = results.filter(r => r === true).length;
|
||||
const failed = results.filter(r => r === false).length;
|
||||
|
||||
console.log(`${colors.green}Passed: ${passed}${colors.reset}`);
|
||||
console.log(`${colors.red}Failed: ${failed}${colors.reset}`);
|
||||
console.log(`Total: ${results.length}`);
|
||||
|
||||
if (failed === 0) {
|
||||
console.log(`\n${colors.green}✓ All tests passed!${colors.reset}`);
|
||||
} else {
|
||||
console.log(`\n${colors.red}✗ Some tests failed${colors.reset}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`${colors.red}Test execution error:${colors.reset}`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runAllTests();
|
||||
Reference in New Issue
Block a user