Skip to main content
Glama
story6-cross-environment-e2e.test.ts15.9 kB
/** * Story 06: Cross-Environment E2E Tests for Dynamic Expected Value Construction * * Comprehensive end-to-end validation of dynamic expected value construction across * different environments, user accounts, and system configurations. * * Tests all 18 acceptance criteria in real-world scenarios with actual SSH commands * and terminal history validation using the complete Villenele framework. */ import { JestTestUtilities } from './jest-test-utilities'; import { DynamicExpectedValueConstructor } from './dynamic-expected-value-constructor'; import './jest-matchers'; // Load custom matchers import * as os from 'os'; describe('Story 06: Cross-Environment E2E Tests - Dynamic Expected Value Construction', () => { let testUtils: JestTestUtilities; // let dynamicConstructor: DynamicExpectedValueConstructor; beforeAll(() => { // Initialize test utilities with dynamic value construction enabled testUtils = new JestTestUtilities({ enableDetailedLogging: false, enableErrorDiagnostics: true, testTimeout: 45000, enableDynamicValueConstruction: true, dynamicValueConfig: { customVariables: { 'TEST_MARKER': 'villenele-e2e-test', 'CURRENT_TIME': 'exec:date +%s' } } }); // dynamicConstructor = testUtils.getDynamicValueConstructor()!; }); beforeEach(async () => { await testUtils.setupTest(); }); afterEach(async () => { await testUtils.cleanupTest(); }); describe('AC 6.18: Cross-environment test portability validation', () => { it('should work across different user accounts and environments', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "e2e-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "e2e-test", "command": "whoami"}', 'ssh_exec {"sessionName": "e2e-test", "command": "pwd"}' ], postWebSocketCommands: [ {initiator: 'browser', command: 'hostname'}, {initiator: 'mcp-client', command: 'echo "User: $(whoami), Host: $(hostname), PWD: $(pwd)"'} ], workflowTimeout: 30000, sessionName: 'cross-env-test' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Validate dynamic templates work with actual terminal output const template = 'User: ${USER}, Host: ${HOSTNAME}, PWD: ${PWD}'; const resolvedTemplate = await testUtils.resolveDynamicTemplate(template); expect(resolvedTemplate).toContain('User:'); expect(resolvedTemplate).toContain('Host:'); expect(resolvedTemplate).toContain('PWD:'); // Verify terminal output contains expected patterns testUtils.expectWebSocketMessagesWithDynamicSupport(result.concatenatedResponses) .toContainCRLF() .toHavePrompts() .toHaveMinimumLength(10) .validate(); // Test that dynamic template matching works with real terminal output expect(result.concatenatedResponses).toMatchDynamicPattern('.*${USER}.*'); expect(result.concatenatedResponses).toMatchDynamicPattern('.*${HOSTNAME}.*'); }); it('should handle different directory locations consistently', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "dir-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "dir-test", "command": "cd /tmp && pwd"}', 'ssh_exec {"sessionName": "dir-test", "command": "cd ~ && pwd"}' ], postWebSocketCommands: [ {initiator: 'browser', command: 'pwd'} ], workflowTimeout: 25000, sessionName: 'directory-test' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Validate that directory paths are correctly resolved const homeTemplate = '${HOME}'; const resolvedHome = await testUtils.resolveDynamicTemplate(homeTemplate); expect(resolvedHome).toBe(os.homedir()); expect(result.concatenatedResponses).toContain('/tmp'); expect(result.concatenatedResponses).toContain(os.homedir()); }); it('should validate hostname consistency across different environments', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "hostname-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "hostname-test", "command": "hostname"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'hostname -f || hostname'} ], workflowTimeout: 20000, sessionName: 'hostname-test' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Verify hostname resolution consistency const hostnameTemplate = '${HOSTNAME}'; const resolvedHostname = await testUtils.resolveDynamicTemplate(hostnameTemplate); expect(resolvedHostname).toBe(os.hostname()); expect(result.concatenatedResponses).toContain(os.hostname()); }); }); describe('AC 6.16: Multi-command expected value coordination', () => { it('should maintain value consistency across command sequences', async () => { const timestamp = Date.now().toString(); const testConstructor = new DynamicExpectedValueConstructor({ customVariables: { 'TEST_TIMESTAMP': timestamp, 'SEQUENCE_MARKER': 'seq-test-' + timestamp } }); const config = { preWebSocketCommands: [ 'ssh_connect {"name": "sequence-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', `ssh_exec {"sessionName": "sequence-test", "command": "echo seq-test-${timestamp}"}` ], postWebSocketCommands: [ {initiator: 'browser', command: `echo "marker: seq-test-${timestamp}"`}, {initiator: 'mcp-client', command: `echo "count: $(echo seq-test-${timestamp} | wc -c)"`} ], workflowTimeout: 25000, sessionName: 'sequence-coordination' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Verify command sequence coordination const markerTemplate = 'seq-test-${TEST_TIMESTAMP}'; const resolvedMarker = await testConstructor.resolveTemplate(markerTemplate); expect(resolvedMarker).toBe(`seq-test-${timestamp}`); expect(result.concatenatedResponses).toContain(`seq-test-${timestamp}`); // Count occurrences to ensure consistency const occurrences = (result.concatenatedResponses.match(new RegExp(`seq-test-${timestamp}`, 'g')) || []).length; expect(occurrences).toBeGreaterThanOrEqual(3); // Should appear in all commands }); }); describe('AC 6.7: Volatile output handling with regex patterns', () => { it('should handle timestamp patterns in real terminal output', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "volatile-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "volatile-test", "command": "date"}' ], postWebSocketCommands: [ {initiator: 'browser', command: 'date +%s'}, {initiator: 'mcp-client', command: 'date "+%Y-%m-%d %H:%M:%S"'} ], workflowTimeout: 25000, sessionName: 'volatile-output' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Test volatile pattern creation and matching const timestampPattern = testUtils.createVolatilePattern('timestamp'); // const datePattern = testUtils.createVolatilePattern('date'); // Verify patterns match expected formats in output const timestampMatches = result.concatenatedResponses.match(timestampPattern); expect(timestampMatches).not.toBeNull(); // Test dynamic pattern with regex const regexTemplate = '${pattern:/\\d{10}/}'; // Unix timestamp expect(result.concatenatedResponses).toMatchDynamicPattern(regexTemplate); }); it('should handle process ID patterns', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "pid-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "pid-test", "command": "echo $$"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ps -o pid,comm | head -5'} ], workflowTimeout: 20000, sessionName: 'process-id-test' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Test PID pattern const pidPattern = testUtils.createVolatilePattern('pid'); const pidMatches = result.concatenatedResponses.match(pidPattern); expect(pidMatches).not.toBeNull(); expect(pidMatches!.length).toBeGreaterThan(0); }); }); describe('AC 6.8: Directory listing dynamic validation', () => { it('should dynamically validate directory contents', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "ls-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "ls-test", "command": "ls -la"}' ], postWebSocketCommands: [ {initiator: 'browser', command: 'ls'}, {initiator: 'mcp-client', command: 'find . -maxdepth 1 -type f | wc -l'} ], workflowTimeout: 25000, sessionName: 'directory-listing' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Get environment values for directory listing validation const envValues = await testUtils.getEnvironmentValues(); expect(Array.isArray(envValues.LS_OUTPUT)).toBe(true); expect(envValues.LS_OUTPUT.length).toBeGreaterThan(0); // Verify some common files exist in directory listing const commonFiles = ['package.json', 'README.md', 'src']; const foundFiles = envValues.LS_OUTPUT.filter(file => commonFiles.includes(file)); expect(foundFiles.length).toBeGreaterThan(0); }); }); describe('AC 6.15: Performance optimization through caching', () => { it('should demonstrate caching performance benefits', async () => { const template = 'User: ${USER}, Host: ${HOSTNAME}, Directory: ${PWD}, Home: ${HOME}'; // First resolution (should populate cache) const start1 = process.hrtime(); const resolved1 = await testUtils.resolveDynamicTemplate(template); const [s1, ns1] = process.hrtime(start1); const duration1 = s1 * 1000 + ns1 / 1000000; // Second resolution (should use cache) const start2 = process.hrtime(); const resolved2 = await testUtils.resolveDynamicTemplate(template); const [s2, ns2] = process.hrtime(start2); const duration2 = s2 * 1000 + ns2 / 1000000; // Results should be identical expect(resolved1).toBe(resolved2); // Second call should be faster or similar (allowing for measurement variance) expect(duration2).toBeLessThanOrEqual(duration1 + 10); // 10ms tolerance // Test cache invalidation testUtils.invalidateDynamicValueCache(); // Third resolution after cache invalidation const resolved3 = await testUtils.resolveDynamicTemplate(template); expect(resolved3).toBe(resolved1); // Should still be same values }); }); describe('Integration with existing Villenele framework', () => { it('should integrate seamlessly with terminal history testing', async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "integration-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "integration-test", "command": "whoami && hostname && pwd"}' ], postWebSocketCommands: [ {initiator: 'browser', command: 'echo "Integration test complete"'}, {initiator: 'mcp-client', command: 'echo "Current user: $(whoami)"'} ], workflowTimeout: 30000, sessionName: 'framework-integration' }; const result = await testUtils.runTerminalHistoryTest(config); expect(result.success).toBe(true); // Validate using traditional Villenele assertions testUtils.expectWebSocketMessagesWithDynamicSupport(result.concatenatedResponses) .toContainCRLF() .toHavePrompts() .toContainText('Integration test complete') .toHaveMinimumLength(20) .validate(); // Validate using dynamic template matching expect(result.concatenatedResponses).toMatchDynamicTemplate('*${USER}*'); expect(result.concatenatedResponses).toMatchDynamicTemplate('*${HOSTNAME}*'); // Verify environment-independent behavior const currentUser = process.env.USER || os.userInfo().username; const currentHost = os.hostname(); expect(result.concatenatedResponses).toContain(currentUser); expect(result.concatenatedResponses).toContain(currentHost); expect(result.concatenatedResponses).toContain(process.cwd()); }); it('should demonstrate error diagnostics with dynamic context', async () => { try { // This should provide detailed error diagnostics expect('wrong_output').toMatchDynamicTemplate('${USER}@${HOSTNAME}'); // Should not reach here expect(false).toBe(true); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Verify comprehensive error diagnostics (AC 6.14) expect(errorMessage).toContain('Expected output to match dynamic template'); expect(errorMessage).toContain('Template:'); expect(errorMessage).toContain('Resolved:'); expect(errorMessage).toContain('Actual:'); expect(errorMessage).toContain('Environment Context:'); expect(errorMessage).toContain('USER:'); expect(errorMessage).toContain('HOSTNAME:'); } }); }); describe('Cross-platform and environment adaptability', () => { it('should adapt to different system configurations', async () => { const envValues = await testUtils.getEnvironmentValues(); // Verify all required environment values are present expect(envValues.USER).toBeDefined(); expect(envValues.PWD).toBeDefined(); expect(envValues.HOSTNAME).toBeDefined(); expect(envValues.HOME).toBeDefined(); expect(Array.isArray(envValues.LS_OUTPUT)).toBe(true); expect(envValues.TIMESTAMP).toBeDefined(); // Verify values are consistent with system expect(envValues.USER).toBe(process.env.USER || os.userInfo().username); expect(envValues.PWD).toBe(process.cwd()); expect(envValues.HOSTNAME).toBe(os.hostname()); expect(envValues.HOME).toBe(os.homedir()); // Test dynamic template resolution with all standard variables const complexTemplate = '[${USER}@${HOSTNAME} ${PWD}]$ cd ${HOME} && ls'; const resolved = await testUtils.resolveDynamicTemplate(complexTemplate); expect(resolved).toContain(envValues.USER); expect(resolved).toContain(envValues.HOSTNAME); expect(resolved).toContain(envValues.PWD); expect(resolved).toContain(envValues.HOME); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server