const axios = require('axios'); const BASE_URL = 'http://localhost:3000/api'; // Test configuration const testConfig = { adminUser: { email: 'admin@example.com', password: 'Admin123!@#', username: 'adminuser' }, regularUser: { email: 'stattest@example.com', password: 'Test123!@#', username: 'stattest' } }; // Test state let adminToken = null; let regularToken = null; // Test results let passedTests = 0; let failedTests = 0; const results = []; // Helper function to log test results function logTest(name, passed, error = null) { results.push({ name, passed, error }); if (passed) { console.log(`✓ ${name}`); passedTests++; } else { console.log(`✗ ${name}`); if (error) console.log(` Error: ${error}`); failedTests++; } } // Setup function async function setup() { console.log('Setting up test data...\n'); try { // Register/Login admin user try { await axios.post(`${BASE_URL}/auth/register`, { email: testConfig.adminUser.email, password: testConfig.adminUser.password, username: testConfig.adminUser.username }); } catch (error) { if (error.response?.status === 409) { console.log('Admin user already registered'); } } const adminLoginRes = await axios.post(`${BASE_URL}/auth/login`, { email: testConfig.adminUser.email, password: testConfig.adminUser.password }); adminToken = adminLoginRes.data.data.token; console.log('✓ Admin user logged in'); // Manually set admin role in database if needed // This would typically be done through a database migration or admin tool // For testing, you may need to manually update the user role to 'admin' in the database // Register/Login regular user try { await axios.post(`${BASE_URL}/auth/register`, { email: testConfig.regularUser.email, password: testConfig.regularUser.password, username: testConfig.regularUser.username }); } catch (error) { if (error.response?.status === 409) { console.log('Regular user already registered'); } } const userLoginRes = await axios.post(`${BASE_URL}/auth/login`, { email: testConfig.regularUser.email, password: testConfig.regularUser.password }); regularToken = userLoginRes.data.data.token; console.log('✓ Regular user logged in'); console.log('\n============================================================'); console.log('ADMIN STATISTICS API TESTS'); console.log('============================================================\n'); console.log('NOTE: Admin user must have role="admin" in database'); console.log('If tests fail due to authorization, update user role manually:\n'); console.log(`UPDATE users SET role='admin' WHERE email='${testConfig.adminUser.email}';`); console.log('\n============================================================\n'); } catch (error) { console.error('Setup failed:', error.response?.data || error.message); process.exit(1); } } // Test functions async function testGetStatistics() { try { const response = await axios.get(`${BASE_URL}/admin/statistics`, { headers: { Authorization: `Bearer ${adminToken}` } }); const passed = response.status === 200 && response.data.success === true && response.data.data !== undefined; logTest('Get statistics successfully', passed); return response.data.data; } catch (error) { logTest('Get statistics successfully', false, error.response?.data?.message || error.message); return null; } } async function testStatisticsStructure(stats) { if (!stats) { logTest('Statistics structure validation', false, 'No statistics data available'); return; } try { // Check users section const hasUsers = stats.users && typeof stats.users.total === 'number' && typeof stats.users.active === 'number' && typeof stats.users.inactiveLast7Days === 'number'; // Check quizzes section const hasQuizzes = stats.quizzes && typeof stats.quizzes.totalSessions === 'number' && typeof stats.quizzes.averageScore === 'number' && typeof stats.quizzes.averageScorePercentage === 'number' && typeof stats.quizzes.passRate === 'number' && typeof stats.quizzes.passedQuizzes === 'number' && typeof stats.quizzes.failedQuizzes === 'number'; // Check content section const hasContent = stats.content && typeof stats.content.totalCategories === 'number' && typeof stats.content.totalQuestions === 'number' && stats.content.questionsByDifficulty && typeof stats.content.questionsByDifficulty.easy === 'number' && typeof stats.content.questionsByDifficulty.medium === 'number' && typeof stats.content.questionsByDifficulty.hard === 'number'; // Check popular categories const hasPopularCategories = Array.isArray(stats.popularCategories); // Check user growth const hasUserGrowth = Array.isArray(stats.userGrowth); // Check quiz activity const hasQuizActivity = Array.isArray(stats.quizActivity); const passed = hasUsers && hasQuizzes && hasContent && hasPopularCategories && hasUserGrowth && hasQuizActivity; logTest('Statistics structure validation', passed); } catch (error) { logTest('Statistics structure validation', false, error.message); } } async function testUsersSection(stats) { if (!stats) { logTest('Users section fields', false, 'No statistics data available'); return; } try { const users = stats.users; const passed = users.total >= 0 && users.active >= 0 && users.inactiveLast7Days >= 0 && users.active + users.inactiveLast7Days === users.total; logTest('Users section fields', passed); } catch (error) { logTest('Users section fields', false, error.message); } } async function testQuizzesSection(stats) { if (!stats) { logTest('Quizzes section fields', false, 'No statistics data available'); return; } try { const quizzes = stats.quizzes; const passed = quizzes.totalSessions >= 0 && quizzes.averageScore >= 0 && quizzes.averageScorePercentage >= 0 && quizzes.averageScorePercentage <= 100 && quizzes.passRate >= 0 && quizzes.passRate <= 100 && quizzes.passedQuizzes >= 0 && quizzes.failedQuizzes >= 0 && quizzes.passedQuizzes + quizzes.failedQuizzes === quizzes.totalSessions; logTest('Quizzes section fields', passed); } catch (error) { logTest('Quizzes section fields', false, error.message); } } async function testContentSection(stats) { if (!stats) { logTest('Content section fields', false, 'No statistics data available'); return; } try { const content = stats.content; const difficulty = content.questionsByDifficulty; const totalQuestionsByDifficulty = difficulty.easy + difficulty.medium + difficulty.hard; const passed = content.totalCategories >= 0 && content.totalQuestions >= 0 && totalQuestionsByDifficulty === content.totalQuestions; logTest('Content section fields', passed); } catch (error) { logTest('Content section fields', false, error.message); } } async function testPopularCategories(stats) { if (!stats) { logTest('Popular categories structure', false, 'No statistics data available'); return; } try { const categories = stats.popularCategories; if (categories.length === 0) { logTest('Popular categories structure', true); return; } const firstCategory = categories[0]; const passed = firstCategory.id !== undefined && firstCategory.name !== undefined && firstCategory.slug !== undefined && typeof firstCategory.quizCount === 'number' && typeof firstCategory.averageScore === 'number' && categories.length <= 5; // Max 5 categories logTest('Popular categories structure', passed); } catch (error) { logTest('Popular categories structure', false, error.message); } } async function testUserGrowth(stats) { if (!stats) { logTest('User growth data structure', false, 'No statistics data available'); return; } try { const growth = stats.userGrowth; if (growth.length === 0) { logTest('User growth data structure', true); return; } const firstEntry = growth[0]; const passed = firstEntry.date !== undefined && typeof firstEntry.newUsers === 'number' && growth.length <= 30; // Max 30 days logTest('User growth data structure', passed); } catch (error) { logTest('User growth data structure', false, error.message); } } async function testQuizActivity(stats) { if (!stats) { logTest('Quiz activity data structure', false, 'No statistics data available'); return; } try { const activity = stats.quizActivity; if (activity.length === 0) { logTest('Quiz activity data structure', true); return; } const firstEntry = activity[0]; const passed = firstEntry.date !== undefined && typeof firstEntry.quizzesCompleted === 'number' && activity.length <= 30; // Max 30 days logTest('Quiz activity data structure', passed); } catch (error) { logTest('Quiz activity data structure', false, error.message); } } async function testNonAdminBlocked() { try { await axios.get(`${BASE_URL}/admin/statistics`, { headers: { Authorization: `Bearer ${regularToken}` } }); logTest('Non-admin user blocked', false, 'Regular user should not have access'); } catch (error) { const passed = error.response?.status === 403; logTest('Non-admin user blocked', passed, !passed ? `Expected 403, got ${error.response?.status}` : null); } } async function testUnauthenticated() { try { await axios.get(`${BASE_URL}/admin/statistics`); logTest('Unauthenticated request blocked', false, 'Should require authentication'); } catch (error) { const passed = error.response?.status === 401; logTest('Unauthenticated request blocked', passed, !passed ? `Expected 401, got ${error.response?.status}` : null); } } async function testInvalidToken() { try { await axios.get(`${BASE_URL}/admin/statistics`, { headers: { Authorization: 'Bearer invalid-token-123' } }); logTest('Invalid token rejected', false, 'Invalid token should be rejected'); } catch (error) { const passed = error.response?.status === 401; logTest('Invalid token rejected', passed, !passed ? `Expected 401, got ${error.response?.status}` : null); } } // Main test runner async function runTests() { await setup(); console.log('Running tests...\n'); // Basic functionality tests const stats = await testGetStatistics(); await new Promise(resolve => setTimeout(resolve, 100)); // Structure validation tests await testStatisticsStructure(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testUsersSection(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testQuizzesSection(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testContentSection(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testPopularCategories(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testUserGrowth(stats); await new Promise(resolve => setTimeout(resolve, 100)); await testQuizActivity(stats); await new Promise(resolve => setTimeout(resolve, 100)); // Authorization tests await testNonAdminBlocked(); await new Promise(resolve => setTimeout(resolve, 100)); await testUnauthenticated(); await new Promise(resolve => setTimeout(resolve, 100)); await testInvalidToken(); // Print results console.log('\n============================================================'); console.log(`RESULTS: ${passedTests} passed, ${failedTests} failed out of ${passedTests + failedTests} tests`); console.log('============================================================\n'); if (failedTests > 0) { console.log('Failed tests:'); results.filter(r => !r.passed).forEach(r => { console.log(` - ${r.name}`); if (r.error) console.log(` ${r.error}`); }); } process.exit(failedTests > 0 ? 1 : 0); } // Run tests runTests().catch(error => { console.error('Test execution failed:', error); process.exit(1); });