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