413 lines
13 KiB
JavaScript
413 lines
13 KiB
JavaScript
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);
|
|
});
|