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