add changes
This commit is contained in:
265
tests/test-question-model.js
Normal file
265
tests/test-question-model.js
Normal file
@@ -0,0 +1,265 @@
|
||||
// Question Model Tests
|
||||
const { sequelize, Question, Category, User } = require('../models');
|
||||
|
||||
async function runTests() {
|
||||
try {
|
||||
console.log('🧪 Running Question Model Tests\n');
|
||||
console.log('=====================================\n');
|
||||
|
||||
// Setup: Create test category and user
|
||||
console.log('Setting up test data...');
|
||||
const testCategory = await Category.create({
|
||||
name: 'Test Category',
|
||||
slug: 'test-category',
|
||||
description: 'Category for testing',
|
||||
isActive: true
|
||||
});
|
||||
|
||||
const testUser = await User.create({
|
||||
username: 'testadmin',
|
||||
email: 'admin@test.com',
|
||||
password: 'password123',
|
||||
role: 'admin'
|
||||
});
|
||||
|
||||
console.log('✅ Test category and user created\n');
|
||||
|
||||
// Test 1: Create a multiple choice question
|
||||
console.log('Test 1: Create a multiple choice question with JSON options');
|
||||
const question1 = await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
createdBy: testUser.id,
|
||||
questionText: 'What is the capital of France?',
|
||||
questionType: 'multiple',
|
||||
options: ['London', 'Berlin', 'Paris', 'Madrid'],
|
||||
correctAnswer: '2',
|
||||
explanation: 'Paris is the capital and largest city of France.',
|
||||
difficulty: 'easy',
|
||||
points: 10,
|
||||
keywords: ['geography', 'capital', 'france'],
|
||||
tags: ['geography', 'europe'],
|
||||
visibility: 'public',
|
||||
guestAccessible: true
|
||||
});
|
||||
console.log('✅ Multiple choice question created with ID:', question1.id);
|
||||
console.log(' Options:', question1.options);
|
||||
console.log(' Keywords:', question1.keywords);
|
||||
console.log(' Tags:', question1.tags);
|
||||
console.log(' Match:', Array.isArray(question1.options) ? '✅' : '❌');
|
||||
|
||||
// Test 2: Create a true/false question
|
||||
console.log('\nTest 2: Create a true/false question');
|
||||
const question2 = await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
createdBy: testUser.id,
|
||||
questionText: 'JavaScript is a compiled language.',
|
||||
questionType: 'trueFalse',
|
||||
correctAnswer: 'false',
|
||||
explanation: 'JavaScript is an interpreted language, not compiled.',
|
||||
difficulty: 'easy',
|
||||
visibility: 'registered'
|
||||
});
|
||||
console.log('✅ True/False question created with ID:', question2.id);
|
||||
console.log(' Correct answer:', question2.correctAnswer);
|
||||
console.log(' Match:', question2.correctAnswer === 'false' ? '✅' : '❌');
|
||||
|
||||
// Test 3: Create a written question
|
||||
console.log('\nTest 3: Create a written question');
|
||||
const question3 = await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
createdBy: testUser.id,
|
||||
questionText: 'Explain the concept of closure in JavaScript.',
|
||||
questionType: 'written',
|
||||
correctAnswer: 'A closure is a function that has access to variables in its outer scope',
|
||||
explanation: 'Closures allow functions to access variables from an enclosing scope.',
|
||||
difficulty: 'hard',
|
||||
points: 30,
|
||||
visibility: 'registered'
|
||||
});
|
||||
console.log('✅ Written question created with ID:', question3.id);
|
||||
console.log(' Points (auto-set):', question3.points);
|
||||
console.log(' Match:', question3.points === 30 ? '✅' : '❌');
|
||||
|
||||
// Test 4: Find active questions by category
|
||||
console.log('\nTest 4: Find active questions by category');
|
||||
const categoryQuestions = await Question.findActiveQuestions({
|
||||
categoryId: testCategory.id
|
||||
});
|
||||
console.log('✅ Found', categoryQuestions.length, 'questions in category');
|
||||
console.log(' Expected: 3');
|
||||
console.log(' Match:', categoryQuestions.length === 3 ? '✅' : '❌');
|
||||
|
||||
// Test 5: Filter by difficulty
|
||||
console.log('\nTest 5: Filter questions by difficulty');
|
||||
const easyQuestions = await Question.findActiveQuestions({
|
||||
categoryId: testCategory.id,
|
||||
difficulty: 'easy'
|
||||
});
|
||||
console.log('✅ Found', easyQuestions.length, 'easy questions');
|
||||
console.log(' Expected: 2');
|
||||
console.log(' Match:', easyQuestions.length === 2 ? '✅' : '❌');
|
||||
|
||||
// Test 6: Filter by guest accessibility
|
||||
console.log('\nTest 6: Filter questions by guest accessibility');
|
||||
const guestQuestions = await Question.findActiveQuestions({
|
||||
categoryId: testCategory.id,
|
||||
guestAccessible: true
|
||||
});
|
||||
console.log('✅ Found', guestQuestions.length, 'guest-accessible questions');
|
||||
console.log(' Expected: 1');
|
||||
console.log(' Match:', guestQuestions.length === 1 ? '✅' : '❌');
|
||||
|
||||
// Test 7: Get random questions
|
||||
console.log('\nTest 7: Get random questions from category');
|
||||
const randomQuestions = await Question.getRandomQuestions(testCategory.id, 2);
|
||||
console.log('✅ Retrieved', randomQuestions.length, 'random questions');
|
||||
console.log(' Expected: 2');
|
||||
console.log(' Match:', randomQuestions.length === 2 ? '✅' : '❌');
|
||||
|
||||
// Test 8: Increment attempted count
|
||||
console.log('\nTest 8: Increment attempted count');
|
||||
const beforeAttempted = question1.timesAttempted;
|
||||
await question1.incrementAttempted();
|
||||
await question1.reload();
|
||||
console.log('✅ Attempted count incremented');
|
||||
console.log(' Before:', beforeAttempted);
|
||||
console.log(' After:', question1.timesAttempted);
|
||||
console.log(' Match:', question1.timesAttempted === beforeAttempted + 1 ? '✅' : '❌');
|
||||
|
||||
// Test 9: Increment correct count
|
||||
console.log('\nTest 9: Increment correct count');
|
||||
const beforeCorrect = question1.timesCorrect;
|
||||
await question1.incrementCorrect();
|
||||
await question1.reload();
|
||||
console.log('✅ Correct count incremented');
|
||||
console.log(' Before:', beforeCorrect);
|
||||
console.log(' After:', question1.timesCorrect);
|
||||
console.log(' Match:', question1.timesCorrect === beforeCorrect + 1 ? '✅' : '❌');
|
||||
|
||||
// Test 10: Calculate accuracy
|
||||
console.log('\nTest 10: Calculate accuracy');
|
||||
const accuracy = question1.getAccuracy();
|
||||
console.log('✅ Accuracy calculated:', accuracy + '%');
|
||||
console.log(' Times attempted:', question1.timesAttempted);
|
||||
console.log(' Times correct:', question1.timesCorrect);
|
||||
console.log(' Expected accuracy: 100%');
|
||||
console.log(' Match:', accuracy === 100 ? '✅' : '❌');
|
||||
|
||||
// Test 11: toSafeJSON hides correct answer
|
||||
console.log('\nTest 11: toSafeJSON hides correct answer');
|
||||
const safeJSON = question1.toSafeJSON();
|
||||
console.log('✅ Safe JSON generated');
|
||||
console.log(' Has correctAnswer:', 'correctAnswer' in safeJSON ? '❌' : '✅');
|
||||
console.log(' Has questionText:', 'questionText' in safeJSON ? '✅' : '❌');
|
||||
|
||||
// Test 12: Validation - multiple choice needs options
|
||||
console.log('\nTest 12: Validation - multiple choice needs at least 2 options');
|
||||
try {
|
||||
await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
questionText: 'Invalid question',
|
||||
questionType: 'multiple',
|
||||
options: ['Only one option'],
|
||||
correctAnswer: '0',
|
||||
difficulty: 'easy'
|
||||
});
|
||||
console.log('❌ Should have thrown validation error');
|
||||
} catch (error) {
|
||||
console.log('✅ Validation error caught:', error.message.includes('at least 2 options') ? '✅' : '❌');
|
||||
}
|
||||
|
||||
// Test 13: Validation - trueFalse correct answer
|
||||
console.log('\nTest 13: Validation - trueFalse must have true/false answer');
|
||||
try {
|
||||
await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
questionText: 'Invalid true/false',
|
||||
questionType: 'trueFalse',
|
||||
correctAnswer: 'maybe',
|
||||
difficulty: 'easy'
|
||||
});
|
||||
console.log('❌ Should have thrown validation error');
|
||||
} catch (error) {
|
||||
console.log('✅ Validation error caught:', error.message.includes('true') || error.message.includes('false') ? '✅' : '❌');
|
||||
}
|
||||
|
||||
// Test 14: Points default based on difficulty
|
||||
console.log('\nTest 14: Points auto-set based on difficulty');
|
||||
const mediumQuestion = await Question.create({
|
||||
categoryId: testCategory.id,
|
||||
questionText: 'What is React?',
|
||||
questionType: 'multiple',
|
||||
options: ['Library', 'Framework', 'Language', 'Database'],
|
||||
correctAnswer: '0',
|
||||
difficulty: 'medium',
|
||||
explanation: 'React is a JavaScript library'
|
||||
});
|
||||
console.log('✅ Question created with medium difficulty');
|
||||
console.log(' Points auto-set:', mediumQuestion.points);
|
||||
console.log(' Expected: 20');
|
||||
console.log(' Match:', mediumQuestion.points === 20 ? '✅' : '❌');
|
||||
|
||||
// Test 15: Association with Category
|
||||
console.log('\nTest 15: Association with Category');
|
||||
const questionWithCategory = await Question.findByPk(question1.id, {
|
||||
include: [{ model: Category, as: 'category' }]
|
||||
});
|
||||
console.log('✅ Question loaded with category association');
|
||||
console.log(' Category name:', questionWithCategory.category.name);
|
||||
console.log(' Match:', questionWithCategory.category.id === testCategory.id ? '✅' : '❌');
|
||||
|
||||
// Test 16: Association with User (creator)
|
||||
console.log('\nTest 16: Association with User (creator)');
|
||||
const questionWithCreator = await Question.findByPk(question1.id, {
|
||||
include: [{ model: User, as: 'creator' }]
|
||||
});
|
||||
console.log('✅ Question loaded with creator association');
|
||||
console.log(' Creator username:', questionWithCreator.creator.username);
|
||||
console.log(' Match:', questionWithCreator.creator.id === testUser.id ? '✅' : '❌');
|
||||
|
||||
// Test 17: Get questions by category with options
|
||||
console.log('\nTest 17: Get questions by category with filtering options');
|
||||
const filteredQuestions = await Question.getQuestionsByCategory(testCategory.id, {
|
||||
difficulty: 'easy',
|
||||
limit: 2
|
||||
});
|
||||
console.log('✅ Retrieved filtered questions');
|
||||
console.log(' Count:', filteredQuestions.length);
|
||||
console.log(' Expected: 2');
|
||||
console.log(' Match:', filteredQuestions.length === 2 ? '✅' : '❌');
|
||||
|
||||
// Test 18: Full-text search (if supported)
|
||||
console.log('\nTest 18: Full-text search');
|
||||
try {
|
||||
const searchResults = await Question.searchQuestions('JavaScript', {
|
||||
limit: 10
|
||||
});
|
||||
console.log('✅ Full-text search executed');
|
||||
console.log(' Results found:', searchResults.length);
|
||||
console.log(' Contains JavaScript question:', searchResults.length > 0 ? '✅' : '❌');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Full-text search requires proper index setup');
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
console.log('\n=====================================');
|
||||
console.log('🧹 Cleaning up test data...');
|
||||
// Delete in correct order (children first, then parents)
|
||||
await Question.destroy({ where: {}, force: true });
|
||||
await Category.destroy({ where: {}, force: true });
|
||||
await User.destroy({ where: {}, force: true });
|
||||
console.log('✅ Test data deleted\n');
|
||||
|
||||
await sequelize.close();
|
||||
console.log('✅ All Question Model Tests Completed!\n');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Test failed with error:', error.message);
|
||||
console.error('Error details:', error);
|
||||
await sequelize.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runTests();
|
||||
Reference in New Issue
Block a user