add changes
This commit is contained in:
@@ -7,7 +7,7 @@ const swaggerSpec = require('./config/swagger');
|
||||
const logger = require('./config/logger');
|
||||
const { errorHandler, notFoundHandler } = require('./middleware/errorHandler');
|
||||
const { testConnection, getDatabaseStats } = require('./config/db');
|
||||
const { validateEnvironment } = require('./validate-env');
|
||||
const { validateEnvironment } = require('./tests/validate-env');
|
||||
const { isRedisConnected } = require('./config/redis');
|
||||
|
||||
// Security middleware
|
||||
@@ -88,7 +88,7 @@ app.get('/api-docs.json', docsLimiter, (req, res) => {
|
||||
// Health check endpoint
|
||||
app.get('/health', async (req, res) => {
|
||||
const dbStats = await getDatabaseStats();
|
||||
|
||||
|
||||
res.status(200).json({
|
||||
status: 'OK',
|
||||
message: 'Interview Quiz API is running',
|
||||
@@ -133,9 +133,9 @@ app.use(errorHandler);
|
||||
// Start server
|
||||
app.listen(PORT, async () => {
|
||||
logger.info('Server starting up...');
|
||||
|
||||
|
||||
const redisStatus = isRedisConnected() ? '✅ Connected' : '⚠️ Not Connected (Optional)';
|
||||
|
||||
|
||||
console.log(`
|
||||
╔════════════════════════════════════════╗
|
||||
║ Interview Quiz API - MySQL Edition ║
|
||||
@@ -149,16 +149,16 @@ app.listen(PORT, async () => {
|
||||
📝 Logs: backend/logs/
|
||||
💾 Cache (Redis): ${redisStatus}
|
||||
`);
|
||||
|
||||
|
||||
logger.info(`Server started successfully on port ${PORT}`);
|
||||
|
||||
|
||||
// Test database connection on startup
|
||||
console.log('🔌 Testing database connection...');
|
||||
const connected = await testConnection();
|
||||
if (!connected) {
|
||||
console.warn('⚠️ Warning: Database connection failed. Server is running but database operations will fail.');
|
||||
}
|
||||
|
||||
|
||||
// Log Redis status
|
||||
if (isRedisConnected()) {
|
||||
console.log('💾 Redis cache connected and ready');
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
const { Category } = require('./models');
|
||||
const { Category } = require('../models');
|
||||
|
||||
async function checkCategories() {
|
||||
const allActive = await Category.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['displayOrder', 'ASC']]
|
||||
});
|
||||
|
||||
|
||||
console.log(`\nTotal active categories: ${allActive.length}\n`);
|
||||
|
||||
|
||||
allActive.forEach(cat => {
|
||||
console.log(`${cat.displayOrder}. ${cat.name}`);
|
||||
console.log(` Guest Accessible: ${cat.guestAccessible}`);
|
||||
console.log(` Question Count: ${cat.questionCount}\n`);
|
||||
});
|
||||
|
||||
|
||||
const guestOnly = allActive.filter(c => c.guestAccessible);
|
||||
const authOnly = allActive.filter(c => !c.guestAccessible);
|
||||
|
||||
|
||||
console.log(`Guest-accessible: ${guestOnly.length}`);
|
||||
console.log(`Auth-only: ${authOnly.length}`);
|
||||
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
const { Category } = require('./models');
|
||||
const { Category } = require('../models');
|
||||
|
||||
async function checkCategoryIds() {
|
||||
try {
|
||||
console.log('\n=== Checking Category IDs ===\n');
|
||||
|
||||
|
||||
const categories = await Category.findAll({
|
||||
attributes: ['id', 'name', 'isActive', 'guestAccessible'],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
|
||||
console.log(`Found ${categories.length} categories:\n`);
|
||||
|
||||
|
||||
categories.forEach(cat => {
|
||||
console.log(`ID: ${cat.id} (${typeof cat.id})`);
|
||||
console.log(` Name: ${cat.name}`);
|
||||
@@ -18,16 +18,16 @@ async function checkCategoryIds() {
|
||||
console.log(` guestAccessible: ${cat.guestAccessible}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
|
||||
// Try to find one by PK
|
||||
if (categories.length > 0) {
|
||||
const firstId = categories[0].id;
|
||||
console.log(`\nTrying findByPk with ID: ${firstId} (${typeof firstId})\n`);
|
||||
|
||||
|
||||
const found = await Category.findByPk(firstId);
|
||||
console.log('findByPk result:', found ? found.name : 'NOT FOUND');
|
||||
}
|
||||
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -1,4 +1,4 @@
|
||||
const { Question, Category } = require('./models');
|
||||
const { Question, Category } = require('../models');
|
||||
|
||||
async function checkQuestions() {
|
||||
try {
|
||||
@@ -12,9 +12,9 @@ async function checkQuestions() {
|
||||
attributes: ['id', 'questionText', 'categoryId', 'difficulty'],
|
||||
limit: 10
|
||||
});
|
||||
|
||||
|
||||
console.log(`\nTotal active questions: ${questions.length}\n`);
|
||||
|
||||
|
||||
if (questions.length === 0) {
|
||||
console.log('❌ No questions found in database!');
|
||||
console.log('\nYou need to run the questions seeder:');
|
||||
@@ -27,7 +27,7 @@ async function checkQuestions() {
|
||||
console.log(` Category: ${q.category?.name || 'N/A'} | Difficulty: ${q.difficulty}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -1,5 +1,5 @@
|
||||
// Script to drop categories table
|
||||
const { sequelize } = require('./models');
|
||||
const { sequelize } = require('../models');
|
||||
|
||||
async function dropCategoriesTable() {
|
||||
try {
|
||||
@@ -1,4 +1,4 @@
|
||||
const { Category } = require('./models');
|
||||
const { Category } = require('../models');
|
||||
|
||||
async function getCategoryMapping() {
|
||||
try {
|
||||
@@ -7,9 +7,9 @@ async function getCategoryMapping() {
|
||||
attributes: ['id', 'name', 'slug', 'guestAccessible'],
|
||||
order: [['displayOrder', 'ASC']]
|
||||
});
|
||||
|
||||
|
||||
console.log('\n=== Category ID Mapping ===\n');
|
||||
|
||||
|
||||
const mapping = {};
|
||||
categories.forEach(cat => {
|
||||
mapping[cat.slug] = {
|
||||
@@ -22,7 +22,7 @@ async function getCategoryMapping() {
|
||||
console.log(` Guest Accessible: ${cat.guestAccessible}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
|
||||
// Export for use in tests
|
||||
console.log('\nFor tests, use:');
|
||||
console.log('const CATEGORY_IDS = {');
|
||||
@@ -30,7 +30,7 @@ async function getCategoryMapping() {
|
||||
console.log(` ${slug.toUpperCase().replace(/-/g, '_')}: '${mapping[slug].id}',`);
|
||||
});
|
||||
console.log('};');
|
||||
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -1,4 +1,4 @@
|
||||
const { Question, Category } = require('./models');
|
||||
const { Question, Category } = require('../models');
|
||||
|
||||
async function getQuestionMapping() {
|
||||
try {
|
||||
@@ -14,7 +14,7 @@ async function getQuestionMapping() {
|
||||
});
|
||||
|
||||
console.log('=== Question ID Mapping ===\n');
|
||||
|
||||
|
||||
const mapping = {};
|
||||
questions.forEach((q, index) => {
|
||||
const key = `QUESTION_${index + 1}`;
|
||||
@@ -1,5 +1,5 @@
|
||||
// Category Model Tests
|
||||
const { sequelize, Category } = require('./models');
|
||||
const { sequelize, Category } = require('../models');
|
||||
|
||||
async function runTests() {
|
||||
try {
|
||||
@@ -1,9 +1,9 @@
|
||||
require('dotenv').config();
|
||||
const db = require('./models');
|
||||
const db = require('../models');
|
||||
|
||||
async function testDatabaseConnection() {
|
||||
console.log('\n🔍 Testing Database Connection...\n');
|
||||
|
||||
|
||||
console.log('Configuration:');
|
||||
console.log('- Host:', process.env.DB_HOST);
|
||||
console.log('- Port:', process.env.DB_PORT);
|
||||
@@ -24,10 +24,10 @@ async function testDatabaseConnection() {
|
||||
// Check if database exists
|
||||
const [databases] = await db.sequelize.query('SHOW DATABASES');
|
||||
const dbExists = databases.some(d => d.Database === process.env.DB_NAME);
|
||||
|
||||
|
||||
if (dbExists) {
|
||||
console.log(`✅ Database '${process.env.DB_NAME}' exists.\n`);
|
||||
|
||||
|
||||
// Show tables in database
|
||||
const [tables] = await db.sequelize.query(`SHOW TABLES FROM ${process.env.DB_NAME}`);
|
||||
console.log(`📋 Tables in '${process.env.DB_NAME}':`, tables.length > 0 ? tables.length : 'No tables yet');
|
||||
@@ -1,9 +1,9 @@
|
||||
const { Category } = require('./models');
|
||||
const { Category } = require('../models');
|
||||
|
||||
async function testFindByPk() {
|
||||
try {
|
||||
console.log('\n=== Testing Category.findByPk(1) ===\n');
|
||||
|
||||
|
||||
const category = await Category.findByPk(1, {
|
||||
attributes: [
|
||||
'id',
|
||||
@@ -18,9 +18,9 @@ async function testFindByPk() {
|
||||
'isActive'
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
console.log('Result:', JSON.stringify(category, null, 2));
|
||||
|
||||
|
||||
if (category) {
|
||||
console.log('\nCategory found:');
|
||||
console.log(' Name:', category.name);
|
||||
@@ -29,7 +29,7 @@ async function testFindByPk() {
|
||||
} else {
|
||||
console.log('Category not found!');
|
||||
}
|
||||
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
@@ -35,17 +35,17 @@ async function runTests() {
|
||||
const response = await axios.post(`${BASE_URL}/guest/start-session`, {
|
||||
deviceId: `test_device_${Date.now()}`
|
||||
});
|
||||
|
||||
|
||||
if (response.status === 201 && response.data.success) {
|
||||
testSession.guestId = response.data.data.guestId;
|
||||
testSession.sessionToken = response.data.data.sessionToken;
|
||||
printTestResult(1, 'Guest session created', true,
|
||||
printTestResult(1, 'Guest session created', true,
|
||||
`Guest ID: ${testSession.guestId}\nToken: ${testSession.sessionToken.substring(0, 50)}...`);
|
||||
} else {
|
||||
throw new Error('Failed to create session');
|
||||
}
|
||||
} catch (error) {
|
||||
printTestResult(1, 'Guest session creation', false,
|
||||
printTestResult(1, 'Guest session creation', false,
|
||||
`Error: ${error.response?.data?.message || error.message}`);
|
||||
return; // Can't continue without session
|
||||
}
|
||||
@@ -58,7 +58,7 @@ async function runTests() {
|
||||
'X-Guest-Token': testSession.sessionToken
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response.status === 200 && response.data.success) {
|
||||
const { quizLimit, session } = response.data.data;
|
||||
printTestResult(2, 'Quiz limit check with valid token', true,
|
||||
@@ -79,7 +79,7 @@ async function runTests() {
|
||||
printSection('Test 3: Check quiz limit without token (should fail with 401)');
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL}/guest/quiz-limit`);
|
||||
printTestResult(3, 'No token provided', false,
|
||||
printTestResult(3, 'No token provided', false,
|
||||
'Should have returned 401 but got: ' + response.status);
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
@@ -125,13 +125,13 @@ async function runTests() {
|
||||
try {
|
||||
// Create a token with fake guest ID
|
||||
const jwt = require('jsonwebtoken');
|
||||
const config = require('./config/config');
|
||||
const config = require('../config/config');
|
||||
const fakeToken = jwt.sign(
|
||||
{ guestId: 'guest_fake_12345' },
|
||||
config.jwt.secret,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
|
||||
const response = await axios.get(`${BASE_URL}/guest/quiz-limit`, {
|
||||
headers: {
|
||||
'X-Guest-Token': fakeToken
|
||||
@@ -157,9 +157,9 @@ async function runTests() {
|
||||
'X-Guest-Token': testSession.sessionToken
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const { data } = response.data;
|
||||
const hasCorrectStructure =
|
||||
const hasCorrectStructure =
|
||||
data.guestId &&
|
||||
data.quizLimit &&
|
||||
typeof data.quizLimit.maxQuizzes === 'number' &&
|
||||
@@ -169,7 +169,7 @@ async function runTests() {
|
||||
data.session &&
|
||||
data.session.expiresAt &&
|
||||
data.session.timeRemaining;
|
||||
|
||||
|
||||
if (hasCorrectStructure) {
|
||||
printTestResult(7, 'Response structure verification', true,
|
||||
'All required fields present with correct types');
|
||||
@@ -190,10 +190,10 @@ async function runTests() {
|
||||
'X-Guest-Token': testSession.sessionToken
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const { quizLimit } = response.data.data;
|
||||
const expectedRemaining = quizLimit.maxQuizzes - quizLimit.quizzesAttempted;
|
||||
|
||||
|
||||
if (quizLimit.quizzesRemaining === expectedRemaining) {
|
||||
printTestResult(8, 'Quiz remaining calculation', true,
|
||||
`Calculation correct: ${quizLimit.maxQuizzes} - ${quizLimit.quizzesAttempted} = ${quizLimit.quizzesRemaining}`);
|
||||
@@ -1,5 +1,5 @@
|
||||
// GuestSession Model Tests
|
||||
const { sequelize, GuestSession, User } = require('./models');
|
||||
const { sequelize, GuestSession, User } = require('../models');
|
||||
|
||||
async function runTests() {
|
||||
try {
|
||||
@@ -119,7 +119,7 @@ async function runTests() {
|
||||
password: 'password123',
|
||||
role: 'user'
|
||||
});
|
||||
|
||||
|
||||
await session1.convertToUser(testUser.id);
|
||||
await session1.reload();
|
||||
console.log('✅ Session converted to user');
|
||||
@@ -191,12 +191,12 @@ async function runTests() {
|
||||
console.log('\nTest 20: Cleanup expired sessions');
|
||||
// Create an expired session by creating a valid one then updating it
|
||||
const tempSession = await GuestSession.createSession({ maxQuizzes: 3 });
|
||||
await tempSession.update({
|
||||
await tempSession.update({
|
||||
expiresAt: new Date(Date.now() - 1000) // Set to expired
|
||||
}, {
|
||||
}, {
|
||||
validate: false // Skip validation
|
||||
});
|
||||
|
||||
|
||||
const cleanedCount = await GuestSession.cleanupExpiredSessions();
|
||||
console.log('✅ Expired sessions cleaned');
|
||||
console.log(' Sessions deleted:', cleanedCount);
|
||||
@@ -1,5 +1,5 @@
|
||||
const { sequelize } = require('./models');
|
||||
const { User, Category, Question, GuestSession, QuizSession } = require('./models');
|
||||
const { sequelize } = require('../models');
|
||||
const { User, Category, Question, GuestSession, QuizSession } = require('../models');
|
||||
const { QueryTypes } = require('sequelize');
|
||||
|
||||
async function runTests() {
|
||||
@@ -162,7 +162,7 @@ async function runTests() {
|
||||
// Test 10: Test user_achievements junction
|
||||
console.log('\nTest 10: Test user_achievements junction table');
|
||||
const achievementId = achievements[0].id;
|
||||
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO user_achievements (id, user_id, achievement_id, notified)
|
||||
VALUES (UUID(), ?, ?, 0)`,
|
||||
@@ -224,9 +224,9 @@ async function runTests() {
|
||||
"SELECT COUNT(*) as count FROM quiz_answers WHERE quiz_session_id = ?",
|
||||
{ replacements: [testQuizSession.id], type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
|
||||
await QuizSession.destroy({ where: { id: testQuizSession.id } });
|
||||
|
||||
|
||||
const answersAfter = await sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM quiz_answers WHERE quiz_session_id = ?",
|
||||
{ replacements: [testQuizSession.id], type: QueryTypes.SELECT }
|
||||
@@ -243,9 +243,9 @@ async function runTests() {
|
||||
"SELECT COUNT(*) as count FROM user_bookmarks WHERE user_id = ?",
|
||||
{ replacements: [testUser.id], type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
|
||||
await User.destroy({ where: { id: testUser.id } });
|
||||
|
||||
|
||||
const bookmarksAfter = await sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM user_bookmarks WHERE user_id = ?",
|
||||
{ replacements: [testUser.id], type: QueryTypes.SELECT }
|
||||
@@ -258,7 +258,7 @@ async function runTests() {
|
||||
|
||||
// Test 16: Verify all indexes exist
|
||||
console.log('\nTest 16: Verify indexes on all tables');
|
||||
|
||||
|
||||
const quizAnswersIndexes = await sequelize.query(
|
||||
"SHOW INDEX FROM quiz_answers",
|
||||
{ type: QueryTypes.SELECT }
|
||||
@@ -292,7 +292,7 @@ async function runTests() {
|
||||
|
||||
console.log('\n=====================================');
|
||||
console.log('🧹 Cleaning up test data...');
|
||||
|
||||
|
||||
// Clean up remaining test data
|
||||
await sequelize.query("DELETE FROM user_achievements");
|
||||
await sequelize.query("DELETE FROM achievements");
|
||||
@@ -303,7 +303,7 @@ async function runTests() {
|
||||
await sequelize.query("DELETE FROM questions");
|
||||
await sequelize.query("DELETE FROM categories");
|
||||
await sequelize.query("DELETE FROM users");
|
||||
|
||||
|
||||
console.log('✅ Test data deleted');
|
||||
console.log('\n✅ All Junction Tables Tests Completed!');
|
||||
|
||||
@@ -13,8 +13,8 @@ async function testLimitReached() {
|
||||
|
||||
try {
|
||||
// First, update the guest session to simulate reaching limit
|
||||
const { GuestSession } = require('./models');
|
||||
|
||||
const { GuestSession } = require('../models');
|
||||
|
||||
console.log('Step 1: Updating guest session to simulate limit reached...');
|
||||
const guestSession = await GuestSession.findOne({
|
||||
where: { guestId: GUEST_ID }
|
||||
@@ -47,7 +47,7 @@ async function testLimitReached() {
|
||||
console.log(`✅ Has Reached Limit: ${data.quizLimit.hasReachedLimit}`);
|
||||
console.log(`✅ Quizzes Attempted: ${data.quizLimit.quizzesAttempted}`);
|
||||
console.log(`✅ Quizzes Remaining: ${data.quizLimit.quizzesRemaining}`);
|
||||
|
||||
|
||||
if (data.upgradePrompt) {
|
||||
console.log('\n✅ Upgrade Prompt Present:');
|
||||
console.log(` Message: ${data.upgradePrompt.message}`);
|
||||
@@ -1,5 +1,5 @@
|
||||
// Question Model Tests
|
||||
const { sequelize, Question, Category, User } = require('./models');
|
||||
const { sequelize, Question, Category, User } = require('../models');
|
||||
|
||||
async function runTests() {
|
||||
try {
|
||||
@@ -1,5 +1,5 @@
|
||||
const { sequelize } = require('./models');
|
||||
const { User, Category, GuestSession, QuizSession } = require('./models');
|
||||
const { sequelize } = require('../models');
|
||||
const { User, Category, GuestSession, QuizSession } = require('../models');
|
||||
|
||||
async function runTests() {
|
||||
console.log('🧪 Running QuizSession Model Tests\n');
|
||||
@@ -80,8 +80,8 @@ async function runTests() {
|
||||
console.log(' Questions answered:', userQuiz.questionsAnswered);
|
||||
console.log(' Correct answers:', userQuiz.correctAnswers);
|
||||
console.log(' Total points:', userQuiz.totalPoints);
|
||||
console.log(' Match:', userQuiz.questionsAnswered === beforeAnswers + 1 &&
|
||||
userQuiz.correctAnswers === beforeCorrect + 1 ? '✅' : '❌');
|
||||
console.log(' Match:', userQuiz.questionsAnswered === beforeAnswers + 1 &&
|
||||
userQuiz.correctAnswers === beforeCorrect + 1 ? '✅' : '❌');
|
||||
|
||||
// Test 5: Record incorrect answer
|
||||
console.log('\nTest 5: Record incorrect answer');
|
||||
@@ -92,8 +92,8 @@ async function runTests() {
|
||||
console.log('✅ Incorrect answer recorded');
|
||||
console.log(' Questions answered:', userQuiz.questionsAnswered);
|
||||
console.log(' Correct answers:', userQuiz.correctAnswers);
|
||||
console.log(' Match:', userQuiz.questionsAnswered === beforeAnswers2 + 1 &&
|
||||
userQuiz.correctAnswers === beforeCorrect2 ? '✅' : '❌');
|
||||
console.log(' Match:', userQuiz.questionsAnswered === beforeAnswers2 + 1 &&
|
||||
userQuiz.correctAnswers === beforeCorrect2 ? '✅' : '❌');
|
||||
|
||||
// Test 6: Get quiz progress
|
||||
console.log('\nTest 6: Get quiz progress');
|
||||
@@ -204,7 +204,7 @@ async function runTests() {
|
||||
totalQuestions: 10
|
||||
});
|
||||
await activeQuiz.start();
|
||||
|
||||
|
||||
const foundActive = await QuizSession.findActiveForUser(testUser.id);
|
||||
console.log('✅ Active session found');
|
||||
console.log(' Found ID:', foundActive.id);
|
||||
@@ -360,13 +360,13 @@ async function runTests() {
|
||||
|
||||
console.log('\n=====================================');
|
||||
console.log('🧹 Cleaning up test data...');
|
||||
|
||||
|
||||
// Clean up test data
|
||||
await QuizSession.destroy({ where: {} });
|
||||
await GuestSession.destroy({ where: {} });
|
||||
await Category.destroy({ where: {} });
|
||||
await User.destroy({ where: {} });
|
||||
|
||||
|
||||
console.log('✅ Test data deleted');
|
||||
console.log('\n✅ All QuizSession Model Tests Completed!');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
require('dotenv').config();
|
||||
const db = require('./models');
|
||||
const db = require('../models');
|
||||
const { User } = db;
|
||||
|
||||
async function testUserModel() {
|
||||
@@ -1,5 +1,5 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
const config = require('./config/database');
|
||||
const config = require('../config/database');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
config.development.database,
|
||||
Reference in New Issue
Block a user