Upstream maildev/maildev state of the touched files before ac15c79ff9
This commit is contained in:
1 parent
ed77334df2
commit
7f6defcdc5
2 files changed
+370
No files matched your search
@@ -0,0 +1,204 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
import { loadEnvConfig, getEnvVarName } from '../../config/env.js'
|
||||
|
||||
describe('loadEnvConfig', () => {
|
||||
const originalEnv = process.env
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset process.env before each test
|
||||
process.env = { ...originalEnv }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv
|
||||
})
|
||||
|
||||
it('should return empty config when no env vars set', () => {
|
||||
// Clear relevant env vars
|
||||
delete process.env.MAILDEV_SMTP_PORT
|
||||
delete process.env.MAILDEV_WEB_PORT
|
||||
delete process.env.MAILDEV_IP
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.smtp).toBeUndefined()
|
||||
expect(config.web).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_SMTP_PORT as number', () => {
|
||||
process.env.MAILDEV_SMTP_PORT = '2025'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.smtp).toBe(2025)
|
||||
expect(typeof config.smtp).toBe('number')
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_WEB_PORT as number', () => {
|
||||
process.env.MAILDEV_WEB_PORT = '3080'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.web).toBe(3080)
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_MAX_MESSAGE_SIZE as number', () => {
|
||||
process.env.MAILDEV_MAX_MESSAGE_SIZE = '1048576'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.maxMessageSize).toBe(1048576)
|
||||
expect(typeof config.maxMessageSize).toBe('number')
|
||||
})
|
||||
|
||||
it('should load MAILDEV_IP as string', () => {
|
||||
process.env.MAILDEV_IP = '127.0.0.1'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.ip).toBe('127.0.0.1')
|
||||
})
|
||||
|
||||
it('should load MAILDEV_WEB_IP as string', () => {
|
||||
process.env.MAILDEV_WEB_IP = '192.168.1.1'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.webIp).toBe('192.168.1.1')
|
||||
})
|
||||
|
||||
it('should load authentication env vars', () => {
|
||||
process.env.MAILDEV_INCOMING_USER = 'smtp_user'
|
||||
process.env.MAILDEV_INCOMING_PASS = 'smtp_pass'
|
||||
process.env.MAILDEV_WEB_USER = 'web_user'
|
||||
process.env.MAILDEV_WEB_PASS = 'web_pass'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.incomingUser).toBe('smtp_user')
|
||||
expect(config.incomingPass).toBe('smtp_pass')
|
||||
expect(config.webUser).toBe('web_user')
|
||||
expect(config.webPass).toBe('web_pass')
|
||||
})
|
||||
|
||||
it('should load outgoing relay env vars', () => {
|
||||
process.env.MAILDEV_OUTGOING_HOST = 'smtp.example.com'
|
||||
process.env.MAILDEV_OUTGOING_PORT = '587'
|
||||
process.env.MAILDEV_OUTGOING_USER = 'relay_user'
|
||||
process.env.MAILDEV_OUTGOING_PASS = 'relay_pass'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.outgoingHost).toBe('smtp.example.com')
|
||||
expect(config.outgoingPort).toBe(587)
|
||||
expect(config.outgoingUser).toBe('relay_user')
|
||||
expect(config.outgoingPass).toBe('relay_pass')
|
||||
})
|
||||
|
||||
it('should load MAILDEV_MAIL_DIRECTORY', () => {
|
||||
process.env.MAILDEV_MAIL_DIRECTORY = '/var/mail'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.mailDirectory).toBe('/var/mail')
|
||||
})
|
||||
|
||||
it('should load MAILDEV_BASE_PATHNAME', () => {
|
||||
process.env.MAILDEV_BASE_PATHNAME = '/mail/'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.basePathname).toBe('/mail/')
|
||||
})
|
||||
|
||||
it('should ignore invalid port numbers', () => {
|
||||
process.env.MAILDEV_SMTP_PORT = 'not-a-number'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.smtp).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle multiple env vars simultaneously', () => {
|
||||
process.env.MAILDEV_SMTP_PORT = '2025'
|
||||
process.env.MAILDEV_WEB_PORT = '3080'
|
||||
process.env.MAILDEV_IP = '0.0.0.0'
|
||||
process.env.MAILDEV_MAIL_DIRECTORY = '/tmp/mail'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.smtp).toBe(2025)
|
||||
expect(config.web).toBe(3080)
|
||||
expect(config.ip).toBe('0.0.0.0')
|
||||
expect(config.mailDirectory).toBe('/tmp/mail')
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_AUTO_RELAY=true as boolean true', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = 'true'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe(true)
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_AUTO_RELAY=1 as boolean true', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = '1'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe(true)
|
||||
})
|
||||
|
||||
it('should parse present-but-empty MAILDEV_AUTO_RELAY as boolean true', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = ''
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe(true)
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_AUTO_RELAY=false as boolean false', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = 'false'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe(false)
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_AUTO_RELAY=0 as boolean false', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = '0'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe(false)
|
||||
})
|
||||
|
||||
it('should parse MAILDEV_AUTO_RELAY as a recipient email', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY = 'qa@example.com'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBe('qa@example.com')
|
||||
})
|
||||
|
||||
it('should load MAILDEV_AUTO_RELAY_RULES as a path string', () => {
|
||||
process.env.MAILDEV_AUTO_RELAY_RULES = '/config/rules.json'
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelayRules).toBe('/config/rules.json')
|
||||
})
|
||||
|
||||
it('should leave autoRelay and autoRelayRules unset when env vars are absent', () => {
|
||||
delete process.env.MAILDEV_AUTO_RELAY
|
||||
delete process.env.MAILDEV_AUTO_RELAY_RULES
|
||||
|
||||
const config = loadEnvConfig()
|
||||
expect(config.autoRelay).toBeUndefined()
|
||||
expect(config.autoRelayRules).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getEnvVarName', () => {
|
||||
it('should return env var name for smtp', () => {
|
||||
expect(getEnvVarName('smtp')).toBe('MAILDEV_SMTP_PORT')
|
||||
})
|
||||
|
||||
it('should return env var name for web', () => {
|
||||
expect(getEnvVarName('web')).toBe('MAILDEV_WEB_PORT')
|
||||
})
|
||||
|
||||
it('should return env var name for ip', () => {
|
||||
expect(getEnvVarName('ip')).toBe('MAILDEV_IP')
|
||||
})
|
||||
|
||||
it('should return env var name for mailDirectory', () => {
|
||||
expect(getEnvVarName('mailDirectory')).toBe('MAILDEV_MAIL_DIRECTORY')
|
||||
})
|
||||
|
||||
it('should return null for unmapped config keys', () => {
|
||||
expect(getEnvVarName('mcp')).toBeNull()
|
||||
expect(getEnvVarName('verbose')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* MailDev Environment Variable Configuration
|
||||
*
|
||||
* Maps MAILDEV_* environment variables to configuration options
|
||||
*/
|
||||
|
||||
import type { PartialMailDevConfig } from './types.js'
|
||||
|
||||
/**
|
||||
* Environment variable to config key mapping
|
||||
*/
|
||||
const ENV_MAPPING: Record<string, keyof PartialMailDevConfig> = {
|
||||
// SMTP
|
||||
MAILDEV_SMTP_PORT: 'smtp',
|
||||
MAILDEV_IP: 'ip',
|
||||
MAILDEV_INCOMING_USER: 'incomingUser',
|
||||
MAILDEV_INCOMING_PASS: 'incomingPass',
|
||||
MAILDEV_MAX_MESSAGE_SIZE: 'maxMessageSize',
|
||||
|
||||
// Web/API
|
||||
MAILDEV_WEB_PORT: 'web',
|
||||
MAILDEV_WEB_IP: 'webIp',
|
||||
MAILDEV_WEB_USER: 'webUser',
|
||||
MAILDEV_WEB_PASS: 'webPass',
|
||||
MAILDEV_BASE_PATHNAME: 'basePathname',
|
||||
MAILDEV_HTTPS: 'https',
|
||||
MAILDEV_HTTPS_CERT: 'httpsCert',
|
||||
MAILDEV_HTTPS_KEY: 'httpsKey',
|
||||
|
||||
// Outgoing/Relay
|
||||
MAILDEV_OUTGOING_HOST: 'outgoingHost',
|
||||
MAILDEV_OUTGOING_PORT: 'outgoingPort',
|
||||
MAILDEV_OUTGOING_USER: 'outgoingUser',
|
||||
MAILDEV_OUTGOING_PASS: 'outgoingPass',
|
||||
MAILDEV_AUTO_RELAY: 'autoRelay',
|
||||
MAILDEV_AUTO_RELAY_RULES: 'autoRelayRules',
|
||||
|
||||
// Storage
|
||||
MAILDEV_MAIL_DIRECTORY: 'mailDirectory',
|
||||
MAILDEV_MAX_EMAILS: 'maxEmails',
|
||||
|
||||
// API URL (for MCP client mode)
|
||||
MAILDEV_API_URL: 'webIp', // Special handling needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variables that should be parsed as numbers
|
||||
*/
|
||||
const NUMBER_VARS = new Set([
|
||||
'MAILDEV_SMTP_PORT',
|
||||
'MAILDEV_WEB_PORT',
|
||||
'MAILDEV_OUTGOING_PORT',
|
||||
'MAILDEV_MAX_MESSAGE_SIZE',
|
||||
'MAILDEV_MAX_EMAILS',
|
||||
])
|
||||
|
||||
/**
|
||||
* Environment variables that should be parsed as booleans
|
||||
*/
|
||||
const BOOLEAN_VARS = new Set([
|
||||
'MAILDEV_INCOMING_SECURE',
|
||||
'MAILDEV_OUTGOING_SECURE',
|
||||
'MAILDEV_HTTPS',
|
||||
'MAILDEV_DISABLE_WEB',
|
||||
'MAILDEV_MCP',
|
||||
'MAILDEV_VERBOSE',
|
||||
'MAILDEV_SILENT',
|
||||
])
|
||||
|
||||
/**
|
||||
* Parse boolean environment variable
|
||||
*/
|
||||
function parseBoolean(value: string): boolean {
|
||||
return value.toLowerCase() === 'true' || value === '1'
|
||||
}
|
||||
|
||||
/**
|
||||
* MAILDEV_AUTO_RELAY is boolean | string: true/1/empty enable relay to
|
||||
* each mail's own recipients; false/0 disable; any other value is the
|
||||
* override recipient.
|
||||
* Do not put this in BOOLEAN_VARS — that would parse an email as false.
|
||||
*/
|
||||
function parseAutoRelay(value: string): boolean | string {
|
||||
if (value === '' || value.toLowerCase() === 'true' || value === '1') {
|
||||
return true
|
||||
}
|
||||
if (value.toLowerCase() === 'false' || value === '0') {
|
||||
return false
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from environment variables
|
||||
*
|
||||
* @returns Partial configuration from environment
|
||||
*/
|
||||
export function loadEnvConfig(): PartialMailDevConfig {
|
||||
const config: PartialMailDevConfig = {}
|
||||
|
||||
for (const [envVar, configKey] of Object.entries(ENV_MAPPING)) {
|
||||
const value = process.env[envVar]
|
||||
if (value === undefined) continue
|
||||
|
||||
if (envVar === 'MAILDEV_AUTO_RELAY') {
|
||||
config.autoRelay = parseAutoRelay(value)
|
||||
} else if (NUMBER_VARS.has(envVar)) {
|
||||
const num = parseInt(value, 10)
|
||||
if (!isNaN(num)) {
|
||||
;(config as Record<string, unknown>)[configKey] = num
|
||||
}
|
||||
} else {
|
||||
;(config as Record<string, unknown>)[configKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
// Handle boolean environment variables
|
||||
for (const envVar of BOOLEAN_VARS) {
|
||||
const value = process.env[envVar]
|
||||
if (value !== undefined) {
|
||||
const key = envVarToConfigKey(envVar)
|
||||
if (key) {
|
||||
;(config as Record<string, unknown>)[key] = parseBoolean(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert environment variable name to config key
|
||||
*/
|
||||
function envVarToConfigKey(envVar: string): keyof PartialMailDevConfig | null {
|
||||
// Remove MAILDEV_ prefix and convert to camelCase
|
||||
const withoutPrefix = envVar.replace(/^MAILDEV_/, '')
|
||||
const parts = withoutPrefix.toLowerCase().split('_')
|
||||
const camelCase = parts
|
||||
.map((part, i) => (i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
|
||||
.join('')
|
||||
|
||||
// Map specific env vars to config keys
|
||||
const mapping: Record<string, keyof PartialMailDevConfig> = {
|
||||
smtpPort: 'smtp',
|
||||
webPort: 'web',
|
||||
incomingSecure: 'incomingSecure',
|
||||
outgoingSecure: 'outgoingSecure',
|
||||
disableWeb: 'disableWeb',
|
||||
verbose: 'verbose',
|
||||
silent: 'silent',
|
||||
mcp: 'mcp',
|
||||
}
|
||||
|
||||
return mapping[camelCase] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable name for a config key
|
||||
* (useful for error messages and documentation)
|
||||
*/
|
||||
export function getEnvVarName(configKey: keyof PartialMailDevConfig): string | null {
|
||||
for (const [envVar, key] of Object.entries(ENV_MAPPING)) {
|
||||
if (key === configKey) return envVar
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in new issue
Block a user