webssh2-server
Version:
A Websocket to SSH2 gateway using xterm.js, socket.io, ssh2
244 lines (195 loc) • 7.13 kB
JavaScript
// server
// tests/config-async.test.js
import { test, describe, beforeEach, afterEach } from 'node:test'
import assert from 'node:assert/strict'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import fs from 'fs'
import { getConfig, loadConfigAsync, resetConfigForTesting } from '../app/config.js'
import { cleanupEnvironmentVariables, storeEnvironmentVariables, restoreEnvironmentVariables } from './test-helpers.js'
// Ensure clean state at module load
resetConfigForTesting()
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
describe('Config Module - Async Tests', () => {
const configPath = join(__dirname, '..', 'config.json')
const backupPath = join(__dirname, '..', 'config.json.backup')
let originalEnv
beforeEach(() => {
// Store original environment variables
originalEnv = storeEnvironmentVariables()
// Clean up all environment variables
cleanupEnvironmentVariables()
// Backup existing config if it exists
if (fs.existsSync(configPath)) {
fs.copyFileSync(configPath, backupPath)
}
// Reset config instance for fresh testing
resetConfigForTesting()
})
afterEach(() => {
// Restore original environment variables
restoreEnvironmentVariables(originalEnv)
// Restore original config
try {
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, configPath)
fs.unlinkSync(backupPath)
} else if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath)
}
} catch (error) {
// Ignore cleanup errors in tests
console.warn('Test cleanup warning:', error.message)
}
})
test('loadConfigAsync loads default config when config.json is missing', async () => {
// Ensure config.json doesn't exist
if (fs.existsSync(configPath)) {
fs.unlinkSync(configPath)
}
const config = await loadConfigAsync()
assert.equal(config.listen.ip, '0.0.0.0')
assert.equal(config.listen.port, 2222)
assert.equal(config.ssh.port, 22)
assert.equal(config.ssh.term, 'xterm-color')
assert.equal(config.session.name, 'webssh2.sid')
assert.ok(config.session.secret)
})
test('loadConfigAsync loads and merges custom config from config.json', async () => {
const customConfig = {
listen: {
port: 3333
},
ssh: {
host: 'test.example.com'
},
header: {
text: 'Test Header'
}
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
const config = await loadConfigAsync()
// Custom values should be merged
assert.equal(config.listen.port, 3333)
assert.equal(config.ssh.host, 'test.example.com')
assert.equal(config.header.text, 'Test Header')
// Default values should still be present
assert.equal(config.listen.ip, '0.0.0.0')
assert.equal(config.ssh.port, 22)
assert.equal(config.ssh.term, 'xterm-color')
})
test('loadConfigAsync overrides port with PORT environment variable', async () => {
const customConfig = {
listen: {
port: 3333
}
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
process.env.PORT = '4444'
const config = await loadConfigAsync()
assert.equal(config.listen.port, 4444)
})
test('loadConfigAsync handles malformed JSON gracefully', async () => {
// Write invalid JSON
fs.writeFileSync(configPath, '{ invalid json }')
const config = await loadConfigAsync()
// Should fall back to defaults
assert.equal(config.listen.ip, '0.0.0.0')
assert.equal(config.listen.port, 2222)
})
test('getConfig returns the same config instance on multiple calls', async () => {
const config1 = await getConfig()
const config2 = await getConfig()
assert.strictEqual(config1, config2)
assert.ok(typeof config1.getCorsConfig === 'function')
})
test('getConfig works with custom configuration file', async () => {
const customConfig = {
listen: {
port: 5555
},
ssh: {
algorithms: {
cipher: ['aes256-gcm@openssh.com']
}
}
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
const config = await getConfig()
assert.equal(config.listen.port, 5555)
assert.ok(config.ssh.algorithms.cipher.includes('aes256-gcm@openssh.com'))
assert.ok(typeof config.getCorsConfig === 'function')
})
test('async config loading uses literal JSON values (no env var substitution)', async () => {
// Native JSON parsing doesn't support environment variable substitution
// This tests that literal values are preserved
process.env.TEST_SECRET = 'test-secret-12345'
const customConfig = {
session: {
secret: '%TEST_SECRET%'
}
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
try {
const config = await loadConfigAsync()
// Native JSON parsing should preserve the literal string
assert.equal(config.session.secret, '%TEST_SECRET%')
} finally {
delete process.env.TEST_SECRET
}
})
test('async config loading validates configuration schema', async () => {
const validConfig = {
listen: {
ip: '127.0.0.1',
port: 3000
},
ssh: {
port: 22,
term: 'xterm-256color'
}
}
fs.writeFileSync(configPath, JSON.stringify(validConfig, null, 2))
const config = await loadConfigAsync()
// Should pass validation and merge successfully
assert.equal(config.listen.ip, '127.0.0.1')
assert.equal(config.listen.port, 3000)
assert.equal(config.ssh.term, 'xterm-256color')
})
test('async config preserves all SSH algorithms', async () => {
const customConfig = {
ssh: {
algorithms: {
cipher: ['aes256-gcm@openssh.com', 'aes128-ctr'],
kex: ['ecdh-sha2-nistp256'],
hmac: ['hmac-sha2-512']
}
}
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
const config = await loadConfigAsync()
assert.ok(config.ssh.algorithms.cipher.includes('aes256-gcm@openssh.com'))
assert.ok(config.ssh.algorithms.cipher.includes('aes128-ctr'))
assert.ok(config.ssh.algorithms.kex.includes('ecdh-sha2-nistp256'))
assert.ok(config.ssh.algorithms.hmac.includes('hmac-sha2-512'))
// Should still have other default algorithms
assert.ok(config.ssh.algorithms.serverHostKey.length > 0)
assert.ok(config.ssh.algorithms.compress.length > 0)
})
test('concurrent calls to getConfig return the same instance', async () => {
const customConfig = {
listen: { port: 6666 }
}
fs.writeFileSync(configPath, JSON.stringify(customConfig, null, 2))
// Make multiple concurrent calls
const [config1, config2, config3] = await Promise.all([
getConfig(),
getConfig(),
getConfig()
])
assert.strictEqual(config1, config2)
assert.strictEqual(config2, config3)
assert.equal(config1.listen.port, 6666)
})
})