add changes
This commit is contained in:
585
tests/test-session-details.js
Normal file
585
tests/test-session-details.js
Normal file
@@ -0,0 +1,585 @@
|
||||
const axios = require('axios');
|
||||
|
||||
const API_URL = 'http://localhost:3000/api';
|
||||
|
||||
// Test data
|
||||
let testUser = {
|
||||
email: 'sessiontest@example.com',
|
||||
password: 'Test@123',
|
||||
username: 'sessiontester'
|
||||
};
|
||||
|
||||
let secondUser = {
|
||||
email: 'otheruser@example.com',
|
||||
password: 'Test@123',
|
||||
username: 'otheruser'
|
||||
};
|
||||
|
||||
let userToken = null;
|
||||
let secondUserToken = null;
|
||||
let guestToken = null;
|
||||
let guestId = null;
|
||||
let testCategory = null;
|
||||
let userSessionId = null;
|
||||
let userSessionIdCompleted = null;
|
||||
let guestSessionId = null;
|
||||
|
||||
// Helper to add delay between tests
|
||||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
// Helper to create and complete a quiz
|
||||
async function createAndCompleteQuiz(token, isGuest = false) {
|
||||
const headers = isGuest
|
||||
? { 'X-Guest-Token': token }
|
||||
: { 'Authorization': `Bearer ${token}` };
|
||||
|
||||
// Get categories
|
||||
const categoriesRes = await axios.get(`${API_URL}/categories`, { headers });
|
||||
const categories = categoriesRes.data.data;
|
||||
const category = categories.find(c => c.questionCount >= 3);
|
||||
|
||||
if (!category) {
|
||||
throw new Error('No category with enough questions found');
|
||||
}
|
||||
|
||||
// Start quiz
|
||||
const startRes = await axios.post(`${API_URL}/quiz/start`, {
|
||||
categoryId: category.id,
|
||||
questionCount: 3,
|
||||
difficulty: 'mixed',
|
||||
quizType: 'practice'
|
||||
}, { headers });
|
||||
|
||||
const sessionId = startRes.data.data.sessionId;
|
||||
const questions = startRes.data.data.questions;
|
||||
|
||||
// Submit answers for all questions
|
||||
for (const question of questions) {
|
||||
let answer;
|
||||
if (question.questionType === 'multiple') {
|
||||
answer = question.options[0].id;
|
||||
} else if (question.questionType === 'trueFalse') {
|
||||
answer = 'true';
|
||||
} else {
|
||||
answer = 'Sample answer';
|
||||
}
|
||||
|
||||
await axios.post(`${API_URL}/quiz/submit`, {
|
||||
quizSessionId: sessionId,
|
||||
questionId: question.id,
|
||||
userAnswer: answer,
|
||||
timeTaken: 10
|
||||
}, { headers });
|
||||
|
||||
await delay(100);
|
||||
}
|
||||
|
||||
// Complete quiz
|
||||
await axios.post(`${API_URL}/quiz/complete`, {
|
||||
sessionId
|
||||
}, { headers });
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
// Test setup
|
||||
async function setup() {
|
||||
console.log('Setting up test data...\n');
|
||||
|
||||
try {
|
||||
// Register first user
|
||||
try {
|
||||
const registerRes = await axios.post(`${API_URL}/auth/register`, testUser);
|
||||
userToken = registerRes.data.data.token;
|
||||
console.log('✓ First user registered');
|
||||
} catch (error) {
|
||||
// User might already exist, try login
|
||||
const loginRes = await axios.post(`${API_URL}/auth/login`, {
|
||||
email: testUser.email,
|
||||
password: testUser.password
|
||||
});
|
||||
userToken = loginRes.data.data.token;
|
||||
console.log('✓ First user logged in');
|
||||
}
|
||||
|
||||
// Register second user
|
||||
try {
|
||||
const registerRes = await axios.post(`${API_URL}/auth/register`, secondUser);
|
||||
secondUserToken = registerRes.data.data.token;
|
||||
console.log('✓ Second user registered');
|
||||
} catch (error) {
|
||||
const loginRes = await axios.post(`${API_URL}/auth/login`, {
|
||||
email: secondUser.email,
|
||||
password: secondUser.password
|
||||
});
|
||||
secondUserToken = loginRes.data.data.token;
|
||||
console.log('✓ Second user logged in');
|
||||
}
|
||||
|
||||
// Create guest session
|
||||
const guestRes = await axios.post(`${API_URL}/guest/start-session`);
|
||||
guestToken = guestRes.data.data.sessionToken;
|
||||
guestId = guestRes.data.data.guestId;
|
||||
console.log('✓ Guest session created');
|
||||
|
||||
// Get a test category
|
||||
const categoriesRes = await axios.get(`${API_URL}/categories`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
const categories = categoriesRes.data.data;
|
||||
testCategory = categories.find(c => c.questionCount >= 3);
|
||||
|
||||
if (!testCategory) {
|
||||
throw new Error('No category with enough questions found');
|
||||
}
|
||||
console.log(`✓ Test category selected: ${testCategory.name}`);
|
||||
|
||||
// Create in-progress quiz for user
|
||||
const startRes = await axios.post(`${API_URL}/quiz/start`, {
|
||||
categoryId: testCategory.id,
|
||||
questionCount: 3,
|
||||
difficulty: 'mixed',
|
||||
quizType: 'practice'
|
||||
}, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
userSessionId = startRes.data.data.sessionId;
|
||||
|
||||
// Submit one answer
|
||||
const questions = startRes.data.data.questions;
|
||||
let answer = questions[0].questionType === 'multiple'
|
||||
? questions[0].options[0].id
|
||||
: 'true';
|
||||
|
||||
await axios.post(`${API_URL}/quiz/submit`, {
|
||||
quizSessionId: userSessionId,
|
||||
questionId: questions[0].id,
|
||||
userAnswer: answer,
|
||||
timeTaken: 10
|
||||
}, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
console.log('✓ User in-progress session created');
|
||||
|
||||
await delay(500);
|
||||
|
||||
// Create completed quiz for user
|
||||
userSessionIdCompleted = await createAndCompleteQuiz(userToken, false);
|
||||
console.log('✓ User completed session created');
|
||||
|
||||
await delay(500);
|
||||
|
||||
// Create in-progress quiz for guest
|
||||
const guestStartRes = await axios.post(`${API_URL}/quiz/start`, {
|
||||
categoryId: testCategory.id,
|
||||
questionCount: 3,
|
||||
difficulty: 'easy',
|
||||
quizType: 'practice'
|
||||
}, {
|
||||
headers: { 'X-Guest-Token': guestToken }
|
||||
});
|
||||
guestSessionId = guestStartRes.data.data.sessionId;
|
||||
console.log('✓ Guest session created\n');
|
||||
|
||||
await delay(500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Setup failed:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Test cases
|
||||
const tests = [
|
||||
{
|
||||
name: 'Test 1: Get in-progress session details (user)',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
if (response.status !== 200) throw new Error('Expected 200 status');
|
||||
if (!response.data.success) throw new Error('Expected success true');
|
||||
|
||||
const { session, progress, questions } = response.data.data;
|
||||
|
||||
// Validate session structure
|
||||
if (!session.id || session.id !== userSessionId) throw new Error('Invalid session id');
|
||||
if (session.status !== 'in_progress') throw new Error('Expected in_progress status');
|
||||
if (!session.category || !session.category.name) throw new Error('Missing category info');
|
||||
if (typeof session.score.earned !== 'number') throw new Error('Score.earned should be number');
|
||||
if (typeof session.score.total !== 'number') throw new Error('Score.total should be number');
|
||||
|
||||
// Validate progress
|
||||
if (progress.totalQuestions !== 3) throw new Error('Expected 3 total questions');
|
||||
if (progress.answeredQuestions !== 1) throw new Error('Expected 1 answered question');
|
||||
if (progress.unansweredQuestions !== 2) throw new Error('Expected 2 unanswered');
|
||||
|
||||
// Validate questions
|
||||
if (questions.length !== 3) throw new Error('Expected 3 questions');
|
||||
const answeredQ = questions.find(q => q.isAnswered);
|
||||
if (!answeredQ) throw new Error('Expected at least one answered question');
|
||||
if (!answeredQ.userAnswer) throw new Error('Answered question should have userAnswer');
|
||||
if (answeredQ.isCorrect === null) throw new Error('Answered question should have isCorrect');
|
||||
|
||||
// In-progress session should not show correct answers for unanswered questions
|
||||
const unansweredQ = questions.find(q => !q.isAnswered);
|
||||
if (unansweredQ && unansweredQ.correctAnswer !== undefined) {
|
||||
throw new Error('Unanswered question should not show correct answer in in-progress session');
|
||||
}
|
||||
|
||||
return '✓ In-progress session details correct';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 2: Get completed session details (user)',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionIdCompleted}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
if (response.status !== 200) throw new Error('Expected 200 status');
|
||||
|
||||
const { session, progress, questions } = response.data.data;
|
||||
|
||||
if (session.status !== 'completed') throw new Error('Expected completed status');
|
||||
if (!session.completedAt) throw new Error('Should have completedAt timestamp');
|
||||
if (typeof session.isPassed !== 'boolean') throw new Error('Should have isPassed boolean');
|
||||
if (progress.totalQuestions !== 3) throw new Error('Expected 3 total questions');
|
||||
if (progress.answeredQuestions !== 3) throw new Error('All questions should be answered');
|
||||
|
||||
// Completed session should show correct answers for all questions
|
||||
questions.forEach((q, idx) => {
|
||||
if (q.correctAnswer === undefined) {
|
||||
throw new Error(`Question ${idx + 1} should show correct answer in completed session`);
|
||||
}
|
||||
if (!q.isAnswered) {
|
||||
throw new Error(`All questions should be answered in completed session`);
|
||||
}
|
||||
});
|
||||
|
||||
return '✓ Completed session details correct';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 3: Get guest session details',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${guestSessionId}`, {
|
||||
headers: { 'X-Guest-Token': guestToken }
|
||||
});
|
||||
|
||||
if (response.status !== 200) throw new Error('Expected 200 status');
|
||||
if (!response.data.success) throw new Error('Expected success true');
|
||||
|
||||
const { session, questions } = response.data.data;
|
||||
|
||||
if (session.id !== guestSessionId) throw new Error('Invalid session id');
|
||||
if (session.status !== 'in_progress') throw new Error('Expected in_progress status');
|
||||
if (questions.length !== 3) throw new Error('Expected 3 questions');
|
||||
|
||||
return '✓ Guest session details retrieved';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 4: Missing session ID returns 400',
|
||||
run: async () => {
|
||||
try {
|
||||
await axios.get(`${API_URL}/quiz/session/`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
throw new Error('Should have failed with 400');
|
||||
} catch (error) {
|
||||
if (error.response?.status === 404) {
|
||||
// Route not found is acceptable for empty path
|
||||
return '✓ Missing session ID handled';
|
||||
}
|
||||
if (error.response?.status !== 400) {
|
||||
throw new Error(`Expected 400, got ${error.response?.status}`);
|
||||
}
|
||||
return '✓ Missing session ID returns 400';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 5: Invalid UUID format returns 400',
|
||||
run: async () => {
|
||||
try {
|
||||
await axios.get(`${API_URL}/quiz/session/invalid-uuid`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
throw new Error('Should have failed with 400');
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 400) {
|
||||
throw new Error(`Expected 400, got ${error.response?.status}`);
|
||||
}
|
||||
return '✓ Invalid UUID returns 400';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 6: Non-existent session returns 404',
|
||||
run: async () => {
|
||||
try {
|
||||
const fakeUuid = '00000000-0000-0000-0000-000000000000';
|
||||
await axios.get(`${API_URL}/quiz/session/${fakeUuid}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
throw new Error('Should have failed with 404');
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 404) {
|
||||
throw new Error(`Expected 404, got ${error.response?.status}`);
|
||||
}
|
||||
return '✓ Non-existent session returns 404';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 7: Cannot access other user\'s session (403)',
|
||||
run: async () => {
|
||||
try {
|
||||
await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${secondUserToken}` }
|
||||
});
|
||||
throw new Error('Should have failed with 403');
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 403) {
|
||||
throw new Error(`Expected 403, got ${error.response?.status}`);
|
||||
}
|
||||
return '✓ Cross-user access blocked';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 8: Unauthenticated request returns 401',
|
||||
run: async () => {
|
||||
try {
|
||||
await axios.get(`${API_URL}/quiz/session/${userSessionId}`);
|
||||
throw new Error('Should have failed with 401');
|
||||
} catch (error) {
|
||||
if (error.response?.status !== 401) {
|
||||
throw new Error(`Expected 401, got ${error.response?.status}`);
|
||||
}
|
||||
return '✓ Unauthenticated request blocked';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 9: Response includes all required session fields',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { session } = response.data.data;
|
||||
|
||||
const requiredFields = [
|
||||
'id', 'status', 'quizType', 'difficulty', 'category',
|
||||
'score', 'isPassed', 'startedAt', 'timeSpent', 'timeLimit'
|
||||
];
|
||||
|
||||
requiredFields.forEach(field => {
|
||||
if (!(field in session)) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Category should have required fields
|
||||
const categoryFields = ['id', 'name', 'slug', 'icon', 'color'];
|
||||
categoryFields.forEach(field => {
|
||||
if (!(field in session.category)) {
|
||||
throw new Error(`Missing category field: ${field}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Score should have required fields
|
||||
const scoreFields = ['earned', 'total', 'percentage'];
|
||||
scoreFields.forEach(field => {
|
||||
if (!(field in session.score)) {
|
||||
throw new Error(`Missing score field: ${field}`);
|
||||
}
|
||||
});
|
||||
|
||||
return '✓ All required session fields present';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 10: Response includes all required progress fields',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { progress } = response.data.data;
|
||||
|
||||
const requiredFields = [
|
||||
'totalQuestions', 'answeredQuestions', 'correctAnswers',
|
||||
'incorrectAnswers', 'unansweredQuestions', 'progressPercentage'
|
||||
];
|
||||
|
||||
requiredFields.forEach(field => {
|
||||
if (!(field in progress)) {
|
||||
throw new Error(`Missing progress field: ${field}`);
|
||||
}
|
||||
if (typeof progress[field] !== 'number') {
|
||||
throw new Error(`Progress field ${field} should be a number`);
|
||||
}
|
||||
});
|
||||
|
||||
return '✓ All required progress fields present';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 11: Questions include all required fields',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { questions } = response.data.data;
|
||||
|
||||
if (questions.length === 0) throw new Error('Should have questions');
|
||||
|
||||
const requiredFields = [
|
||||
'id', 'questionText', 'questionType', 'difficulty',
|
||||
'points', 'order', 'userAnswer', 'isCorrect',
|
||||
'pointsEarned', 'timeTaken', 'answeredAt', 'isAnswered'
|
||||
];
|
||||
|
||||
questions.forEach((q, idx) => {
|
||||
requiredFields.forEach(field => {
|
||||
if (!(field in q)) {
|
||||
throw new Error(`Question ${idx + 1} missing field: ${field}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return '✓ Questions have all required fields';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 12: Time tracking calculations',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { session } = response.data.data;
|
||||
|
||||
if (typeof session.timeSpent !== 'number') {
|
||||
throw new Error('timeSpent should be a number');
|
||||
}
|
||||
if (session.timeSpent < 0) {
|
||||
throw new Error('timeSpent should not be negative');
|
||||
}
|
||||
|
||||
// Practice quiz should have null timeLimit
|
||||
if (session.quizType === 'practice' && session.timeLimit !== null) {
|
||||
throw new Error('Practice quiz should have null timeLimit');
|
||||
}
|
||||
|
||||
// timeRemaining should be null for practice or number for timed
|
||||
if (session.timeLimit !== null) {
|
||||
if (typeof session.timeRemaining !== 'number') {
|
||||
throw new Error('timeRemaining should be a number for timed quiz');
|
||||
}
|
||||
}
|
||||
|
||||
return '✓ Time tracking correct';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 13: Progress percentages are accurate',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionId}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { progress } = response.data.data;
|
||||
|
||||
const expectedPercentage = Math.round(
|
||||
(progress.answeredQuestions / progress.totalQuestions) * 100
|
||||
);
|
||||
|
||||
if (progress.progressPercentage !== expectedPercentage) {
|
||||
throw new Error(
|
||||
`Progress percentage incorrect: expected ${expectedPercentage}, got ${progress.progressPercentage}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check totals add up
|
||||
const totalCheck = progress.correctAnswers + progress.incorrectAnswers + progress.unansweredQuestions;
|
||||
if (totalCheck !== progress.totalQuestions) {
|
||||
throw new Error('Question counts do not add up');
|
||||
}
|
||||
|
||||
return '✓ Progress calculations accurate';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Test 14: Answered questions show correct feedback',
|
||||
run: async () => {
|
||||
const response = await axios.get(`${API_URL}/quiz/session/${userSessionIdCompleted}`, {
|
||||
headers: { 'Authorization': `Bearer ${userToken}` }
|
||||
});
|
||||
|
||||
const { questions } = response.data.data;
|
||||
|
||||
questions.forEach((q, idx) => {
|
||||
if (q.isAnswered) {
|
||||
if (!q.userAnswer) {
|
||||
throw new Error(`Question ${idx + 1} is answered but has no userAnswer`);
|
||||
}
|
||||
if (typeof q.isCorrect !== 'boolean') {
|
||||
throw new Error(`Question ${idx + 1} should have boolean isCorrect`);
|
||||
}
|
||||
if (typeof q.pointsEarned !== 'number') {
|
||||
throw new Error(`Question ${idx + 1} should have number pointsEarned`);
|
||||
}
|
||||
if (q.correctAnswer === undefined) {
|
||||
throw new Error(`Question ${idx + 1} should show correctAnswer in completed session`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return '✓ Answered questions have correct feedback';
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Run all tests
|
||||
async function runTests() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('QUIZ SESSION DETAILS API TESTS');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
await setup();
|
||||
|
||||
console.log('Running tests...\n');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const test of tests) {
|
||||
try {
|
||||
const result = await test.run();
|
||||
console.log(`${result}`);
|
||||
passed++;
|
||||
await delay(500); // Delay between tests
|
||||
} catch (error) {
|
||||
console.log(`✗ ${test.name}`);
|
||||
console.log(` Error: ${error.response?.data?.message || error.message}`);
|
||||
if (error.response?.data) {
|
||||
console.log(` Response:`, JSON.stringify(error.response.data, null, 2));
|
||||
}
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log(`RESULTS: ${passed} passed, ${failed} failed out of ${tests.length} tests`);
|
||||
console.log('='.repeat(60));
|
||||
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
runTests();
|
||||
Reference in New Issue
Block a user