Upstream maildev/maildev state of the touched files before ac15c79ff9

This commit is contained in:
upstream committed 2026-10-02 15:52:08 +02:00
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()
})
})
+166
View File
@@ -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
}