test-default-shell.js•14.8 kB
/**
* Test script for defaultShell configuration functionality
*
* This script tests how defaultShell settings affect command execution:
* 1. Testing execution with /bin/sh as default shell
* 2. Testing execution with bash as default shell
* 3. Testing shell changes are properly applied
* 4. Testing restoration of original configuration
*/
import { configManager } from '../dist/config-manager.js';
import { startProcess, forceTerminate } from '../dist/tools/improved-process-tools.js';
import assert from 'assert';
import os from 'os';
// We need a wrapper because startProcess in tools/improved-process-tools.js returns a ServerResult
// but our tests expect to receive the actual command result
async function executeCommand(command, timeout_ms = 2000, shell = null) {
const args = {
command: command,
timeout_ms: timeout_ms
};
if (shell) {
args.shell = shell;
}
return await startProcess(args);
}
/**
* Check if a shell is available on the system
*/
async function isShellAvailable(shellPath) {
try {
// For Windows shells, use different detection methods
if (shellPath === 'cmd' || shellPath === 'cmd.exe') {
// On Windows, cmd should always be available
if (os.platform() === 'win32') {
return true;
}
return false;
}
if (shellPath === 'pwsh' || shellPath === 'powershell') {
// Check if PowerShell is available
try {
const result = await executeCommand(`${shellPath} -Command "Get-Host"`, 2000);
return result.content && result.content[0] && !result.content[0].text.includes('not found');
} catch (error) {
return false;
}
}
// For Unix shells, check if the file exists and is executable
try {
const result = await executeCommand(`test -x "${shellPath}" && echo "available"`, 2000);
return result.content && result.content[0] && result.content[0].text.includes('available');
} catch (error) {
return false;
}
} catch (error) {
console.log(`Could not check availability of ${shellPath}: ${error.message}`);
return false;
}
}
/**
* Get expected shell output for a given shell path
*/
function getExpectedShellOutput(shellPath) {
switch (shellPath) {
case '/bin/sh':
return ['/bin/sh'];
case '/bin/bash':
return ['/bin/bash', 'bash'];
case 'cmd':
case 'cmd.exe':
return ['cmd', 'cmd.exe'];
case 'pwsh':
return ['pwsh'];
case 'powershell':
return ['powershell'];
default:
return [shellPath];
}
}
/**
* Execute echo $0 command and extract the shell name from the output
* For Windows shells, use appropriate commands
*/
async function getShellFromCommand(shellPath = null) {
try {
let command = 'echo $0';
// Use different commands for Windows shells
if (shellPath === 'cmd' || shellPath === 'cmd.exe') {
command = 'echo %0';
} else if (shellPath === 'pwsh' || shellPath === 'powershell') {
command = 'Write-Host $MyInvocation.MyCommand.Name';
}
const result = await executeCommand(command, 2000);
// Extract shell name from the result
if (result.content && result.content[0] && result.content[0].text) {
const output = result.content[0].text;
// Look for the shell name in the output, handling both PID line and actual output
const lines = output.split('\n').filter(line => line.trim() !== '');
// Find the line that contains the actual shell output (not the PID line, Command started, or Initial output)
for (const line of lines) {
if (!line.includes('PID') &&
!line.includes('Command started') &&
!line.includes('Initial output:') &&
line.trim() !== '') {
return line.trim();
}
}
}
throw new Error('Could not extract shell name from command output');
} catch (error) {
console.error('Error executing shell command:', error);
throw error;
}
}
/**
* Setup function to prepare the test environment
*/
async function setup() {
console.log('Setting up test environment...');
// Save original config to restore later
const originalConfig = await configManager.getConfig();
console.log(`✓ Setup: saved original configuration`);
console.log(` - Original defaultShell: ${originalConfig.defaultShell || 'not set'}`);
return originalConfig;
}
/**
* Teardown function to clean up after tests
*/
async function teardown(originalConfig) {
// Reset configuration to original
await configManager.updateConfig(originalConfig);
console.log('✓ Teardown: original configuration restored');
console.log(` - Restored defaultShell: ${originalConfig.defaultShell || 'not set'}`);
}
/**
* Test setting defaultShell to /bin/sh
*/
async function testDefaultShellSh() {
console.log('\nTest 1: Setting defaultShell to /bin/sh');
// Check if /bin/sh is available
const isAvailable = await isShellAvailable('/bin/sh');
if (!isAvailable) {
console.log('⚠️ Skipping /bin/sh test: shell not available on this system');
return;
}
// Set defaultShell to /bin/sh
await configManager.setValue('defaultShell', '/bin/sh');
// Verify config was set correctly
const config = await configManager.getConfig();
assert.strictEqual(config.defaultShell, '/bin/sh', 'defaultShell should be set to /bin/sh');
console.log(`✓ Configuration updated: defaultShell = ${config.defaultShell}`);
// Execute echo $0 to check the shell
const shellOutput = await getShellFromCommand(config.defaultShell);
console.log(`✓ Command output: ${shellOutput}`);
// Verify the shell is /bin/sh
const expectedOutputs = getExpectedShellOutput('/bin/sh');
const isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Shell should be one of ${expectedOutputs.join(', ')}, got: ${shellOutput}`);
console.log('✓ Test 1 passed: /bin/sh is correctly set as default shell');
}
/**
* Test setting defaultShell to bash
*/
async function testDefaultShellBash() {
console.log('\nTest 2: Setting defaultShell to /bin/bash');
// Check if /bin/bash is available
const isAvailable = await isShellAvailable('/bin/bash');
if (!isAvailable) {
console.log('⚠️ Skipping /bin/bash test: shell not available on this system');
return;
}
// Set defaultShell to /bin/bash (use full path)
await configManager.setValue('defaultShell', '/bin/bash');
// Verify config was set correctly
const config = await configManager.getConfig();
assert.strictEqual(config.defaultShell, '/bin/bash', 'defaultShell should be set to /bin/bash');
console.log(`✓ Configuration updated: defaultShell = ${config.defaultShell}`);
// Execute echo $0 to check the shell
const shellOutput = await getShellFromCommand(config.defaultShell);
console.log(`✓ Command output: ${shellOutput}`);
// Verify the shell is /bin/bash (note: bash may show as just "bash" or full path)
const expectedOutputs = getExpectedShellOutput('/bin/bash');
const isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Shell should be one of ${expectedOutputs.join(', ')}, got: ${shellOutput}`);
console.log('✓ Test 2 passed: /bin/bash is correctly set as default shell');
}
/**
* Test setting defaultShell to cmd (Windows Command Prompt)
*/
async function testDefaultShellCmd() {
console.log('\nTest 3: Setting defaultShell to cmd');
// Check if cmd is available (Windows only)
const isAvailable = await isShellAvailable('cmd');
if (!isAvailable) {
console.log('⚠️ Skipping cmd test: shell not available on this system (likely not Windows)');
return;
}
// Set defaultShell to cmd
await configManager.setValue('defaultShell', 'cmd');
// Verify config was set correctly
const config = await configManager.getConfig();
assert.strictEqual(config.defaultShell, 'cmd', 'defaultShell should be set to cmd');
console.log(`✓ Configuration updated: defaultShell = ${config.defaultShell}`);
// Execute echo %0 to check the shell (Windows command)
const shellOutput = await getShellFromCommand('cmd');
console.log(`✓ Command output: ${shellOutput}`);
// Verify the shell is cmd
const expectedOutputs = getExpectedShellOutput('cmd');
const isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Shell should be one of ${expectedOutputs.join(', ')}, got: ${shellOutput}`);
console.log('✓ Test 3 passed: cmd is correctly set as default shell');
}
/**
* Test setting defaultShell to pwsh (PowerShell Core)
*/
async function testDefaultShellPwsh() {
console.log('\nTest 4: Setting defaultShell to pwsh');
// Check if pwsh is available
const isAvailable = await isShellAvailable('pwsh');
if (!isAvailable) {
console.log('⚠️ Skipping pwsh test: PowerShell Core not available on this system');
return;
}
// Set defaultShell to pwsh
await configManager.setValue('defaultShell', 'pwsh');
// Verify config was set correctly
const config = await configManager.getConfig();
assert.strictEqual(config.defaultShell, 'pwsh', 'defaultShell should be set to pwsh');
console.log(`✓ Configuration updated: defaultShell = ${config.defaultShell}`);
// Execute PowerShell command to check the shell
const shellOutput = await getShellFromCommand('pwsh');
console.log(`✓ Command output: ${shellOutput}`);
// Verify the shell is pwsh
const expectedOutputs = getExpectedShellOutput('pwsh');
const isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Shell should be one of ${expectedOutputs.join(', ')}, got: ${shellOutput}`);
console.log('✓ Test 4 passed: pwsh is correctly set as default shell');
}
/**
* Test switching between different shells
*/
async function testShellSwitching() {
console.log('\nTest 5: Testing shell switching');
// Get available shells for switching test
const availableShells = [];
if (await isShellAvailable('/bin/sh')) availableShells.push('/bin/sh');
if (await isShellAvailable('/bin/bash')) availableShells.push('/bin/bash');
if (await isShellAvailable('cmd')) availableShells.push('cmd');
if (await isShellAvailable('pwsh')) availableShells.push('pwsh');
if (availableShells.length < 2) {
console.log('⚠️ Skipping shell switching test: need at least 2 available shells');
return;
}
console.log(`✓ Available shells for switching test: ${availableShells.join(', ')}`);
// Test switching between first two available shells
const shell1 = availableShells[0];
const shell2 = availableShells[1];
// Switch to first shell
await configManager.setValue('defaultShell', shell1);
let shellOutput = await getShellFromCommand(shell1);
let expectedOutputs = getExpectedShellOutput(shell1);
let isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Switch to ${shell1} should work, got: ${shellOutput}`);
console.log(`✓ Successfully switched to ${shell1}: ${shellOutput}`);
// Switch to second shell
await configManager.setValue('defaultShell', shell2);
shellOutput = await getShellFromCommand(shell2);
expectedOutputs = getExpectedShellOutput(shell2);
isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Switch to ${shell2} should work, got: ${shellOutput}`);
console.log(`✓ Successfully switched to ${shell2}: ${shellOutput}`);
// Switch back to first shell
await configManager.setValue('defaultShell', shell1);
shellOutput = await getShellFromCommand(shell1);
expectedOutputs = getExpectedShellOutput(shell1);
isValidOutput = expectedOutputs.includes(shellOutput);
assert(isValidOutput, `Switch back to ${shell1} should work, got: ${shellOutput}`);
console.log(`✓ Successfully switched back to ${shell1}: ${shellOutput}`);
console.log('✓ Test 5 passed: shell switching works correctly');
}
/**
* Test that configuration changes persist
*/
async function testConfigurationPersistence() {
console.log('\nTest 6: Testing configuration persistence');
// Find an available shell for testing
let testShell = '/bin/sh';
if (!(await isShellAvailable('/bin/sh'))) {
if (await isShellAvailable('/bin/bash')) {
testShell = '/bin/bash';
} else if (await isShellAvailable('cmd')) {
testShell = 'cmd';
} else if (await isShellAvailable('pwsh')) {
testShell = 'pwsh';
} else {
console.log('⚠️ Skipping persistence test: no available shells found');
return;
}
}
// Set defaultShell to the test shell
await configManager.setValue('defaultShell', testShell);
// Get config multiple times to ensure it persists
const config1 = await configManager.getConfig();
const config2 = await configManager.getConfig();
assert.strictEqual(config1.defaultShell, testShell, 'Configuration should persist on first read');
assert.strictEqual(config2.defaultShell, testShell, 'Configuration should persist on second read');
assert.strictEqual(config1.defaultShell, config2.defaultShell, 'Configuration should be consistent across reads');
console.log(`✓ Configuration persists correctly: ${config1.defaultShell}`);
console.log('✓ Test 6 passed: configuration persistence works correctly');
}
/**
* Main test function
*/
async function runDefaultShellTests() {
console.log('=== defaultShell Configuration Tests ===\n');
console.log(`Platform: ${os.platform()}`);
// Test 1: Setting defaultShell to /bin/sh
await testDefaultShellSh();
// Test 2: Setting defaultShell to /bin/bash
await testDefaultShellBash();
// Test 3: Setting defaultShell to cmd (Windows)
await testDefaultShellCmd();
// Test 4: Setting defaultShell to pwsh (PowerShell Core)
await testDefaultShellPwsh();
// Test 5: Testing shell switching
await testShellSwitching();
// Test 6: Testing configuration persistence
await testConfigurationPersistence();
console.log('\n✅ All defaultShell tests completed!');
}
// Export the main test function
export default async function runTests() {
let originalConfig;
try {
originalConfig = await setup();
await runDefaultShellTests();
} catch (error) {
console.error('❌ Test failed:', error.message);
console.error('Full error:', error);
return false;
} finally {
if (originalConfig) {
await teardown(originalConfig);
}
}
return true;
}
// If this file is run directly (not imported), execute the test
if (import.meta.url === `file://${process.argv[1]}`) {
runTests().catch(error => {
console.error('❌ Unhandled error:', error);
process.exit(1);
});
}