Files
Tasks/backend/tests/validate-env.js
2025-12-25 00:24:11 +02:00

339 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
};