add changes
This commit is contained in:
379
backend/test-guest-analytics.js
Normal file
379
backend/test-guest-analytics.js
Normal file
@@ -0,0 +1,379 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const BASE_URL = 'http://localhost:3000/api';
|
||||
|
||||
// Test configuration
|
||||
const testConfig = {
|
||||
adminUser: {
|
||||
email: 'admin@example.com',
|
||||
password: 'Admin123!@#'
|
||||
},
|
||||
regularUser: {
|
||||
email: 'stattest@example.com',
|
||||
password: 'Test123!@#'
|
||||
}
|
||||
};
|
||||
|
||||
// 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 {
|
||||
// Login admin user
|
||||
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');
|
||||
|
||||
// Login regular user
|
||||
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('GUEST ANALYTICS API TESTS');
|
||||
console.log('============================================================\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Setup failed:', error.response?.data || error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Test functions
|
||||
async function testGetGuestAnalytics() {
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/admin/guest-analytics`, {
|
||||
headers: { Authorization: `Bearer ${adminToken}` }
|
||||
});
|
||||
|
||||
const passed = response.status === 200 &&
|
||||
response.data.success === true &&
|
||||
response.data.data !== undefined;
|
||||
|
||||
logTest('Get guest analytics', passed);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
logTest('Get guest analytics', false, error.response?.data?.message || error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function testOverviewStructure(data) {
|
||||
if (!data) {
|
||||
logTest('Overview section structure', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const overview = data.overview;
|
||||
const passed = overview !== undefined &&
|
||||
typeof overview.totalGuestSessions === 'number' &&
|
||||
typeof overview.activeGuestSessions === 'number' &&
|
||||
typeof overview.expiredGuestSessions === 'number' &&
|
||||
typeof overview.convertedGuestSessions === 'number' &&
|
||||
typeof overview.conversionRate === 'number';
|
||||
|
||||
logTest('Overview section structure', passed);
|
||||
} catch (error) {
|
||||
logTest('Overview section structure', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testQuizActivityStructure(data) {
|
||||
if (!data) {
|
||||
logTest('Quiz activity section structure', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const quizActivity = data.quizActivity;
|
||||
const passed = quizActivity !== undefined &&
|
||||
typeof quizActivity.totalGuestQuizzes === 'number' &&
|
||||
typeof quizActivity.completedGuestQuizzes === 'number' &&
|
||||
typeof quizActivity.guestQuizCompletionRate === 'number' &&
|
||||
typeof quizActivity.avgQuizzesPerGuest === 'number' &&
|
||||
typeof quizActivity.avgQuizzesBeforeConversion === 'number';
|
||||
|
||||
logTest('Quiz activity section structure', passed);
|
||||
} catch (error) {
|
||||
logTest('Quiz activity section structure', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testBehaviorStructure(data) {
|
||||
if (!data) {
|
||||
logTest('Behavior section structure', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const behavior = data.behavior;
|
||||
const passed = behavior !== undefined &&
|
||||
typeof behavior.bounceRate === 'number' &&
|
||||
typeof behavior.avgSessionDurationMinutes === 'number';
|
||||
|
||||
logTest('Behavior section structure', passed);
|
||||
} catch (error) {
|
||||
logTest('Behavior section structure', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testRecentActivityStructure(data) {
|
||||
if (!data) {
|
||||
logTest('Recent activity section structure', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const recentActivity = data.recentActivity;
|
||||
const passed = recentActivity !== undefined &&
|
||||
recentActivity.last30Days !== undefined &&
|
||||
typeof recentActivity.last30Days.newGuestSessions === 'number' &&
|
||||
typeof recentActivity.last30Days.conversions === 'number';
|
||||
|
||||
logTest('Recent activity section structure', passed);
|
||||
} catch (error) {
|
||||
logTest('Recent activity section structure', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testConversionRateCalculation(data) {
|
||||
if (!data) {
|
||||
logTest('Conversion rate calculation', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const overview = data.overview;
|
||||
const expectedRate = overview.totalGuestSessions > 0
|
||||
? ((overview.convertedGuestSessions / overview.totalGuestSessions) * 100)
|
||||
: 0;
|
||||
|
||||
// Allow small floating point difference
|
||||
const passed = Math.abs(overview.conversionRate - expectedRate) < 0.01 &&
|
||||
overview.conversionRate >= 0 &&
|
||||
overview.conversionRate <= 100;
|
||||
|
||||
logTest('Conversion rate calculation', passed);
|
||||
} catch (error) {
|
||||
logTest('Conversion rate calculation', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testQuizCompletionRateCalculation(data) {
|
||||
if (!data) {
|
||||
logTest('Quiz completion rate calculation', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const quizActivity = data.quizActivity;
|
||||
const expectedRate = quizActivity.totalGuestQuizzes > 0
|
||||
? ((quizActivity.completedGuestQuizzes / quizActivity.totalGuestQuizzes) * 100)
|
||||
: 0;
|
||||
|
||||
// Allow small floating point difference
|
||||
const passed = Math.abs(quizActivity.guestQuizCompletionRate - expectedRate) < 0.01 &&
|
||||
quizActivity.guestQuizCompletionRate >= 0 &&
|
||||
quizActivity.guestQuizCompletionRate <= 100;
|
||||
|
||||
logTest('Quiz completion rate calculation', passed);
|
||||
} catch (error) {
|
||||
logTest('Quiz completion rate calculation', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testBounceRateRange(data) {
|
||||
if (!data) {
|
||||
logTest('Bounce rate in valid range', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const bounceRate = data.behavior.bounceRate;
|
||||
const passed = bounceRate >= 0 && bounceRate <= 100;
|
||||
|
||||
logTest('Bounce rate in valid range', passed);
|
||||
} catch (error) {
|
||||
logTest('Bounce rate in valid range', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testAveragesAreNonNegative(data) {
|
||||
if (!data) {
|
||||
logTest('Average values are non-negative', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const passed = data.quizActivity.avgQuizzesPerGuest >= 0 &&
|
||||
data.quizActivity.avgQuizzesBeforeConversion >= 0 &&
|
||||
data.behavior.avgSessionDurationMinutes >= 0;
|
||||
|
||||
logTest('Average values are non-negative', passed);
|
||||
} catch (error) {
|
||||
logTest('Average values are non-negative', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testSessionCounts(data) {
|
||||
if (!data) {
|
||||
logTest('Session counts are consistent', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const overview = data.overview;
|
||||
// Total should be >= sum of active, expired, and converted (some might be both expired and converted)
|
||||
const passed = overview.totalGuestSessions >= 0 &&
|
||||
overview.activeGuestSessions >= 0 &&
|
||||
overview.expiredGuestSessions >= 0 &&
|
||||
overview.convertedGuestSessions >= 0 &&
|
||||
overview.convertedGuestSessions <= overview.totalGuestSessions;
|
||||
|
||||
logTest('Session counts are consistent', passed);
|
||||
} catch (error) {
|
||||
logTest('Session counts are consistent', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testQuizCounts(data) {
|
||||
if (!data) {
|
||||
logTest('Quiz counts are consistent', false, 'No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const quizActivity = data.quizActivity;
|
||||
const passed = quizActivity.totalGuestQuizzes >= 0 &&
|
||||
quizActivity.completedGuestQuizzes >= 0 &&
|
||||
quizActivity.completedGuestQuizzes <= quizActivity.totalGuestQuizzes;
|
||||
|
||||
logTest('Quiz counts are consistent', passed);
|
||||
} catch (error) {
|
||||
logTest('Quiz counts are consistent', false, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testNonAdminBlocked() {
|
||||
try {
|
||||
await axios.get(`${BASE_URL}/admin/guest-analytics`, {
|
||||
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/guest-analytics`);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Main test runner
|
||||
async function runTests() {
|
||||
await setup();
|
||||
|
||||
console.log('Running tests...\n');
|
||||
|
||||
// Get analytics data
|
||||
const data = await testGetGuestAnalytics();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Structure validation tests
|
||||
await testOverviewStructure(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testQuizActivityStructure(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testBehaviorStructure(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testRecentActivityStructure(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Calculation validation tests
|
||||
await testConversionRateCalculation(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testQuizCompletionRateCalculation(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testBounceRateRange(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testAveragesAreNonNegative(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Consistency tests
|
||||
await testSessionCounts(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testQuizCounts(data);
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Authorization tests
|
||||
await testNonAdminBlocked();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await testUnauthenticated();
|
||||
|
||||
// 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