// 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();