572 lines
18 KiB
JavaScript
572 lines
18 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 (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();
|