add changes
This commit is contained in:
338
backend/tests/validate-env.js
Normal file
338
backend/tests/validate-env.js
Normal file
@@ -0,0 +1,338 @@
|
||||
require('dotenv').config();
|
||||
|
||||
/**
|
||||
* Environment Configuration Validator
|
||||
* Validates all required environment variables and their formats
|
||||
*/
|
||||
|
||||
const REQUIRED_VARS = {
|
||||
// Server Configuration
|
||||
NODE_ENV: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
allowedValues: ['development', 'test', 'production'],
|
||||
default: 'development'
|
||||
},
|
||||
PORT: {
|
||||
required: true,
|
||||
type: 'number',
|
||||
min: 1000,
|
||||
max: 65535,
|
||||
default: 3000
|
||||
},
|
||||
API_PREFIX: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
default: '/api'
|
||||
},
|
||||
|
||||
// Database Configuration
|
||||
DB_HOST: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
default: 'localhost'
|
||||
},
|
||||
DB_PORT: {
|
||||
required: true,
|
||||
type: 'number',
|
||||
default: 3306
|
||||
},
|
||||
DB_NAME: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
minLength: 3
|
||||
},
|
||||
DB_USER: {
|
||||
required: true,
|
||||
type: 'string'
|
||||
},
|
||||
DB_PASSWORD: {
|
||||
required: false, // Optional for development
|
||||
type: 'string',
|
||||
warning: 'Database password is not set. This is only acceptable in development.'
|
||||
},
|
||||
DB_DIALECT: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
allowedValues: ['mysql', 'postgres', 'sqlite', 'mssql'],
|
||||
default: 'mysql'
|
||||
},
|
||||
|
||||
// Database Pool Configuration
|
||||
DB_POOL_MAX: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 10
|
||||
},
|
||||
DB_POOL_MIN: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 0
|
||||
},
|
||||
DB_POOL_ACQUIRE: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 30000
|
||||
},
|
||||
DB_POOL_IDLE: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 10000
|
||||
},
|
||||
|
||||
// JWT Configuration
|
||||
JWT_SECRET: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
minLength: 32,
|
||||
warning: 'JWT_SECRET should be a long, random string (64+ characters recommended)'
|
||||
},
|
||||
JWT_EXPIRE: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
default: '24h'
|
||||
},
|
||||
|
||||
// Rate Limiting
|
||||
RATE_LIMIT_WINDOW_MS: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 900000
|
||||
},
|
||||
RATE_LIMIT_MAX_REQUESTS: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 100
|
||||
},
|
||||
|
||||
// CORS Configuration
|
||||
CORS_ORIGIN: {
|
||||
required: true,
|
||||
type: 'string',
|
||||
default: 'http://localhost:4200'
|
||||
},
|
||||
|
||||
// Guest Configuration
|
||||
GUEST_SESSION_EXPIRE_HOURS: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 24
|
||||
},
|
||||
GUEST_MAX_QUIZZES: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 3
|
||||
},
|
||||
|
||||
// Logging
|
||||
LOG_LEVEL: {
|
||||
required: false,
|
||||
type: 'string',
|
||||
allowedValues: ['error', 'warn', 'info', 'debug'],
|
||||
default: 'info'
|
||||
},
|
||||
|
||||
// Redis Configuration (Optional - for caching)
|
||||
REDIS_HOST: {
|
||||
required: false,
|
||||
type: 'string',
|
||||
default: 'localhost'
|
||||
},
|
||||
REDIS_PORT: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 6379
|
||||
},
|
||||
REDIS_PASSWORD: {
|
||||
required: false,
|
||||
type: 'string'
|
||||
},
|
||||
REDIS_DB: {
|
||||
required: false,
|
||||
type: 'number',
|
||||
default: 0
|
||||
}
|
||||
};
|
||||
|
||||
class ValidationError extends Error {
|
||||
constructor(variable, message) {
|
||||
super(`${variable}: ${message}`);
|
||||
this.variable = variable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single environment variable
|
||||
*/
|
||||
function validateVariable(name, config) {
|
||||
const value = process.env[name];
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
|
||||
// Check if required and missing
|
||||
if (config.required && !value) {
|
||||
if (config.default !== undefined) {
|
||||
warnings.push(`${name} is not set. Using default: ${config.default}`);
|
||||
process.env[name] = String(config.default);
|
||||
return { errors, warnings };
|
||||
}
|
||||
errors.push(`${name} is required but not set`);
|
||||
return { errors, warnings };
|
||||
}
|
||||
|
||||
// If not set and not required, use default if available
|
||||
if (!value && config.default !== undefined) {
|
||||
process.env[name] = String(config.default);
|
||||
return { errors, warnings };
|
||||
}
|
||||
|
||||
// Skip further validation if not set and not required
|
||||
if (!value && !config.required) {
|
||||
return { errors, warnings };
|
||||
}
|
||||
|
||||
// Type validation
|
||||
if (config.type === 'number') {
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
errors.push(`${name} must be a number. Got: ${value}`);
|
||||
} else {
|
||||
if (config.min !== undefined && numValue < config.min) {
|
||||
errors.push(`${name} must be >= ${config.min}. Got: ${numValue}`);
|
||||
}
|
||||
if (config.max !== undefined && numValue > config.max) {
|
||||
errors.push(`${name} must be <= ${config.max}. Got: ${numValue}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String length validation
|
||||
if (config.type === 'string' && config.minLength && value.length < config.minLength) {
|
||||
errors.push(`${name} must be at least ${config.minLength} characters. Got: ${value.length}`);
|
||||
}
|
||||
|
||||
// Allowed values validation
|
||||
if (config.allowedValues && !config.allowedValues.includes(value)) {
|
||||
errors.push(`${name} must be one of: ${config.allowedValues.join(', ')}. Got: ${value}`);
|
||||
}
|
||||
|
||||
// Custom warnings
|
||||
if (config.warning && value) {
|
||||
const needsWarning = config.minLength ? value.length < 64 : true;
|
||||
if (needsWarning) {
|
||||
warnings.push(`${name}: ${config.warning}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Warning for missing optional password in production
|
||||
if (name === 'DB_PASSWORD' && !value && process.env.NODE_ENV === 'production') {
|
||||
errors.push('DB_PASSWORD is required in production');
|
||||
}
|
||||
|
||||
return { errors, warnings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all environment variables
|
||||
*/
|
||||
function validateEnvironment() {
|
||||
console.log('\n🔍 Validating Environment Configuration...\n');
|
||||
|
||||
const allErrors = [];
|
||||
const allWarnings = [];
|
||||
let validCount = 0;
|
||||
|
||||
// Validate each variable
|
||||
Object.entries(REQUIRED_VARS).forEach(([name, config]) => {
|
||||
const { errors, warnings } = validateVariable(name, config);
|
||||
|
||||
if (errors.length > 0) {
|
||||
allErrors.push(...errors);
|
||||
console.log(`❌ ${name}: INVALID`);
|
||||
errors.forEach(err => console.log(` ${err}`));
|
||||
} else if (warnings.length > 0) {
|
||||
allWarnings.push(...warnings);
|
||||
console.log(`⚠️ ${name}: WARNING`);
|
||||
warnings.forEach(warn => console.log(` ${warn}`));
|
||||
validCount++;
|
||||
} else {
|
||||
console.log(`✅ ${name}: OK`);
|
||||
validCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('VALIDATION SUMMARY');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Total Variables: ${Object.keys(REQUIRED_VARS).length}`);
|
||||
console.log(`✅ Valid: ${validCount}`);
|
||||
console.log(`⚠️ Warnings: ${allWarnings.length}`);
|
||||
console.log(`❌ Errors: ${allErrors.length}`);
|
||||
console.log('='.repeat(60));
|
||||
|
||||
if (allWarnings.length > 0) {
|
||||
console.log('\n⚠️ WARNINGS:');
|
||||
allWarnings.forEach(warning => console.log(` - ${warning}`));
|
||||
}
|
||||
|
||||
if (allErrors.length > 0) {
|
||||
console.log('\n❌ ERRORS:');
|
||||
allErrors.forEach(error => console.log(` - ${error}`));
|
||||
console.log('\nPlease fix the above errors before starting the application.\n');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('\n✅ All environment variables are valid!\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current environment configuration summary
|
||||
*/
|
||||
function getEnvironmentSummary() {
|
||||
return {
|
||||
environment: process.env.NODE_ENV || 'development',
|
||||
server: {
|
||||
port: process.env.PORT || 3000,
|
||||
apiPrefix: process.env.API_PREFIX || '/api'
|
||||
},
|
||||
database: {
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
name: process.env.DB_NAME,
|
||||
dialect: process.env.DB_DIALECT || 'mysql',
|
||||
pool: {
|
||||
max: parseInt(process.env.DB_POOL_MAX) || 10,
|
||||
min: parseInt(process.env.DB_POOL_MIN) || 0
|
||||
}
|
||||
},
|
||||
security: {
|
||||
jwtExpire: process.env.JWT_EXPIRE || '24h',
|
||||
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:4200'
|
||||
},
|
||||
guest: {
|
||||
maxQuizzes: parseInt(process.env.GUEST_MAX_QUIZZES) || 3,
|
||||
sessionExpireHours: parseInt(process.env.GUEST_SESSION_EXPIRE_HOURS) || 24
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Run validation if called directly
|
||||
if (require.main === module) {
|
||||
const isValid = validateEnvironment();
|
||||
|
||||
if (isValid) {
|
||||
console.log('Current Configuration:');
|
||||
console.log(JSON.stringify(getEnvironmentSummary(), null, 2));
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
process.exit(isValid ? 0 : 1);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateEnvironment,
|
||||
getEnvironmentSummary,
|
||||
REQUIRED_VARS
|
||||
};
|
||||
Reference in New Issue
Block a user