Skip to main content
Glama
story7-acceptance-criteria.test.ts20.9 kB
/** * Story 7: Flexible Command Configuration - Acceptance Criteria Validation * * Tests validate all acceptance criteria from Story 7 specification: * * Given I want to test different terminal scenarios * When I configure the testing framework * Then it should accept JSON configuration with preWebSocketCommands and postWebSocketCommands arrays * And support empty arrays for either phase * And validate command syntax before execution * And provide clear error messages for invalid configurations * And support complex command scenarios like multiple SSH sessions * * Tests follow TDD principles with comprehensive validation and no mocks in production code. */ import { FlexibleCommandConfiguration, CommandConfigurationJSON, ConfigurationValidationError } from './flexible-command-configuration'; import { ComprehensiveResponseCollector } from './comprehensive-response-collector'; describe('Story 7: Flexible Command Configuration - Acceptance Criteria', () => { describe('AC1: Accept JSON configuration with preWebSocketCommands and postWebSocketCommands arrays', () => { test('Given I want to test different terminal scenarios, When I provide JSON configuration with command arrays, Then it should accept the configuration', () => { const jsonConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect {"host": "localhost", "username": "testuser"}', 'ssh_exec {"command": "pwd"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"command": "whoami"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ], workflowTimeout: 30000, sessionName: 'acceptance-test-session' }; // Should not throw any errors expect(() => new FlexibleCommandConfiguration(jsonConfig)).not.toThrow(); const config = new FlexibleCommandConfiguration(jsonConfig); // Verify arrays are parsed correctly expect(config.getPreWebSocketCommands()).toHaveLength(2); expect(config.getPostWebSocketCommands()).toHaveLength(2); // Verify pre-commands converted to PreWebSocketCommand objects expect(config.getPreWebSocketCommands()[0]).toEqual({ tool: 'ssh_connect', args: { host: 'localhost', username: 'testuser' } }); // Verify post-commands remain as strings expect(config.getPostWebSocketCommands()[0]).toBe('ssh_exec {"command": "whoami"}'); }); test('Given I configure JSON with complex command structures, When I initialize FlexibleCommandConfiguration, Then it should handle nested parameters correctly', () => { const complexConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect {"host": "localhost", "port": 22, "options": {"compression": true, "timeout": 30000}}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"command": "ls", "options": {"env": {"PATH": "/usr/bin:/bin"}, "timeout": 5000}}'} ] }; const config = new FlexibleCommandConfiguration(complexConfig); const preCommands = config.getPreWebSocketCommands(); expect(preCommands[0]).toEqual({ tool: 'ssh_connect', args: { host: 'localhost', port: 22, options: { compression: true, timeout: 30000 } } }); }); }); describe('AC2: Support empty arrays for either phase', () => { test('Given I want to skip pre-WebSocket phase, When I provide empty preWebSocketCommands array, Then it should be accepted', () => { const configWithEmptyPre: CommandConfigurationJSON = { preWebSocketCommands: [], // Empty pre-WebSocket phase postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"command": "echo test"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ] }; expect(() => new FlexibleCommandConfiguration(configWithEmptyPre)).not.toThrow(); const config = new FlexibleCommandConfiguration(configWithEmptyPre); expect(config.getPreWebSocketCommands()).toHaveLength(0); expect(config.getPostWebSocketCommands()).toHaveLength(2); }); test('Given I want to skip post-WebSocket phase, When I provide empty postWebSocketCommands array, Then it should be accepted', () => { const configWithEmptyPost: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect {"host": "localhost", "username": "testuser"}' ], postWebSocketCommands: [] // Empty post-WebSocket phase }; expect(() => new FlexibleCommandConfiguration(configWithEmptyPost)).not.toThrow(); const config = new FlexibleCommandConfiguration(configWithEmptyPost); expect(config.getPreWebSocketCommands()).toHaveLength(1); expect(config.getPostWebSocketCommands()).toHaveLength(0); }); test('Given I want to test only WebSocket connection, When I provide both arrays empty, Then it should be accepted', () => { const configWithBothEmpty: CommandConfigurationJSON = { preWebSocketCommands: [], // Empty pre-WebSocket phase postWebSocketCommands: [] // Empty post-WebSocket phase }; expect(() => new FlexibleCommandConfiguration(configWithBothEmpty)).not.toThrow(); const config = new FlexibleCommandConfiguration(configWithBothEmpty); expect(config.getPreWebSocketCommands()).toHaveLength(0); expect(config.getPostWebSocketCommands()).toHaveLength(0); }); }); describe('AC3: Validate command syntax before execution', () => { test('Given I provide commands with valid MCP tool format, When FlexibleCommandConfiguration validates them, Then no errors should occur', () => { const validConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_create_session {"sessionName": "test"}', 'ssh_connect {"host": "localhost", "username": "user"}', 'ssh_exec {"command": "ls -la"}', 'ssh_disconnect {}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_status {}'}, {initiator: 'mcp-client', command: 'ssh_list_sessions {}'} ] }; expect(() => new FlexibleCommandConfiguration(validConfig)).not.toThrow(); const config = new FlexibleCommandConfiguration(validConfig); expect(config.getPreWebSocketCommands()).toHaveLength(4); expect(config.getPostWebSocketCommands()).toHaveLength(2); }); test('Given I provide commands with invalid JSON syntax, When FlexibleCommandConfiguration validates them, Then it should reject them before execution', () => { const invalidJsonConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect {invalid json syntax}' // Invalid JSON ], postWebSocketCommands: [] }; expect(() => new FlexibleCommandConfiguration(invalidJsonConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(invalidJsonConfig)) .toThrow('Invalid JSON in preWebSocketCommand at index 0'); }); test('Given I provide commands without JSON parameters, When FlexibleCommandConfiguration validates them, Then it should reject them before execution', () => { const missingParamsConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect' // Missing JSON parameters ], postWebSocketCommands: [] }; expect(() => new FlexibleCommandConfiguration(missingParamsConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(missingParamsConfig)) .toThrow('Command must have JSON parameters: ssh_connect'); }); }); describe('AC4: Provide clear error messages for invalid configurations', () => { test('Given I provide invalid workflow timeout, When FlexibleCommandConfiguration validates it, Then it should provide clear error message', () => { const invalidTimeoutConfig: CommandConfigurationJSON = { preWebSocketCommands: [], postWebSocketCommands: [], workflowTimeout: -5000 // Invalid negative timeout }; expect(() => new FlexibleCommandConfiguration(invalidTimeoutConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(invalidTimeoutConfig)) .toThrow('workflowTimeout must be positive'); }); test('Given I provide empty session name, When FlexibleCommandConfiguration validates it, Then it should provide clear error message', () => { const emptySessionConfig: CommandConfigurationJSON = { preWebSocketCommands: [], postWebSocketCommands: [], sessionName: ' ' // Whitespace-only session name }; expect(() => new FlexibleCommandConfiguration(emptySessionConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(emptySessionConfig)) .toThrow('sessionName cannot be empty'); }); test('Given I provide malformed JSON in commands, When FlexibleCommandConfiguration validates them, Then it should provide specific error with command index', () => { const malformedConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_connect {"host": "localhost"}', // Valid command 'ssh_exec {malformed: json syntax}', // Invalid JSON at index 1 'ssh_disconnect {}' // Valid command ], postWebSocketCommands: [] }; expect(() => new FlexibleCommandConfiguration(malformedConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(malformedConfig)) .toThrow('Invalid JSON in preWebSocketCommand at index 1'); }); test('Given I provide invalid JSON in post-WebSocket commands, When FlexibleCommandConfiguration validates them, Then it should provide specific error with command index and phase', () => { const invalidPostConfig: CommandConfigurationJSON = { preWebSocketCommands: [], postWebSocketCommands: [ 'ssh_exec {"command": "whoami"}', // Valid command 'ssh_disconnect {invalid json}' // Invalid JSON at index 1 ] }; expect(() => new FlexibleCommandConfiguration(invalidPostConfig)) .toThrow(ConfigurationValidationError); expect(() => new FlexibleCommandConfiguration(invalidPostConfig)) .toThrow('Invalid JSON in postWebSocketCommand at index 1'); }); }); describe('AC5: Support complex command scenarios like multiple SSH sessions', () => { test('Given I want to test multiple SSH sessions workflow, When I configure complex scenario, Then it should handle all session management commands', () => { const multiSessionConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_create_session {"sessionName": "server1"}', 'ssh_connect {"host": "server1.example.com", "port": 22, "username": "admin"}', 'ssh_exec {"command": "uptime"}', 'ssh_create_session {"sessionName": "server2"}', 'ssh_connect {"host": "server2.example.com", "port": 2222, "username": "deploy"}', 'ssh_exec {"command": "ps aux | grep nginx"}', 'ssh_create_session {"sessionName": "database"}', 'ssh_connect {"host": "db.example.com", "port": 22, "username": "dbadmin", "keyfile": "/keys/db.pem"}', 'ssh_exec {"command": "mysql -u root -e \'SHOW PROCESSLIST\'"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "server1"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "tail -n 10 /var/log/nginx/access.log"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'}, {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "server2"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "systemctl status nginx"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'}, {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "database"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "mysqladmin status"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ], workflowTimeout: 120000, // 2 minutes for complex workflow sessionName: 'multi-server-monitoring' }; expect(() => new FlexibleCommandConfiguration(multiSessionConfig)).not.toThrow(); const config = new FlexibleCommandConfiguration(multiSessionConfig); // Verify complex scenario is parsed correctly expect(config.getPreWebSocketCommands()).toHaveLength(9); expect(config.getPostWebSocketCommands()).toHaveLength(9); expect(config.getWorkflowTimeout()).toBe(120000); expect(config.getSessionName()).toBe('multi-server-monitoring'); // Verify specific commands are parsed correctly const preCommands = config.getPreWebSocketCommands(); expect(preCommands[0]).toEqual({ tool: 'ssh_create_session', args: { sessionName: 'server1' } }); expect(preCommands[7]).toEqual({ tool: 'ssh_connect', args: { host: 'db.example.com', port: 22, username: 'dbadmin', keyfile: '/keys/db.pem' } }); // Verify post commands preserved as strings const postCommands = config.getPostWebSocketCommands(); expect(postCommands[0]).toBe('ssh_switch_session {"sessionName": "server1"}'); expect(postCommands[6]).toBe('ssh_switch_session {"sessionName": "database"}'); }); test('Given I configure complex workflow with mixed command types, When FlexibleCommandConfiguration processes it, Then it should integrate with ComprehensiveResponseCollector correctly', async () => { const complexWorkflowConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_create_session {"sessionName": "load-test"}', 'ssh_connect {"host": "loadtest.example.com", "username": "testrunner"}', 'ssh_exec {"command": "ab -n 1000 -c 10 http://target.example.com/"}', 'ssh_create_session {"sessionName": "monitor"}', 'ssh_connect {"host": "monitor.example.com", "username": "monitor"}', 'ssh_exec {"command": "top -b -n 1"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "load-test"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "ps aux | grep ab"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "netstat -an | grep :80"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'}, {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "monitor"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "free -h"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "df -h"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ], workflowTimeout: 180000, sessionName: 'load-test-monitoring' }; const flexibleConfig = new FlexibleCommandConfiguration(complexWorkflowConfig); // Test integration with ComprehensiveResponseCollector const collector = new ComprehensiveResponseCollector( flexibleConfig.getComprehensiveResponseCollectorConfig() ); const collectorConfig = collector.getConfig(); expect(collectorConfig.preWebSocketCommands).toHaveLength(6); expect(collectorConfig.postWebSocketCommands).toHaveLength(8); expect(collectorConfig.workflowTimeout).toBe(180000); expect(collectorConfig.sessionName).toBe('load-test-monitoring'); // Cleanup await collector.cleanup(); }); }); describe('Full Integration: All Acceptance Criteria Together', () => { test('Given I want comprehensive terminal scenario testing, When I use all FlexibleCommandConfiguration features together, Then all acceptance criteria should work in harmony', async () => { // Comprehensive scenario that exercises all acceptance criteria const comprehensiveConfig: CommandConfigurationJSON = { // AC1 & AC5: JSON configuration with complex commands and multiple sessions preWebSocketCommands: [ 'ssh_create_session {"sessionName": "web-server"}', 'ssh_connect {"host": "web.example.com", "port": 22, "username": "webadmin", "options": {"compression": true}}', 'ssh_exec {"command": "nginx -t"}', 'ssh_create_session {"sessionName": "app-server"}', 'ssh_connect {"host": "app.example.com", "username": "appuser"}', 'ssh_exec {"command": "pm2 status"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "web-server"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "tail -f /var/log/nginx/error.log"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'}, {initiator: 'mcp-client', command: 'ssh_switch_session {"sessionName": "app-server"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"command": "pm2 logs --lines 50"}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ], // AC3: Valid configuration parameters workflowTimeout: 90000, sessionName: 'comprehensive-monitoring-test' }; // AC3: Syntax validation should pass expect(() => new FlexibleCommandConfiguration(comprehensiveConfig)).not.toThrow(); const config = new FlexibleCommandConfiguration(comprehensiveConfig); // AC1: Verify JSON configuration is parsed correctly expect(config.getPreWebSocketCommands()).toHaveLength(6); expect(config.getPostWebSocketCommands()).toHaveLength(6); // AC5: Verify complex multi-session commands are handled const preCommands = config.getPreWebSocketCommands(); expect(preCommands[1]).toEqual({ tool: 'ssh_connect', args: { host: 'web.example.com', port: 22, username: 'webadmin', options: { compression: true } } }); const postCommands = config.getPostWebSocketCommands(); expect(postCommands[0]).toBe('ssh_switch_session {"sessionName": "web-server"}'); // Verify integration with ComprehensiveResponseCollector const collector = new ComprehensiveResponseCollector( config.getComprehensiveResponseCollectorConfig() ); const collectorConfig = collector.getConfig(); expect(collectorConfig.sessionName).toBe('comprehensive-monitoring-test'); expect(collectorConfig.workflowTimeout).toBe(90000); expect(collectorConfig.preWebSocketCommands).toHaveLength(6); expect(collectorConfig.postWebSocketCommands).toHaveLength(6); // Cleanup await collector.cleanup(); }); test('Given I test error scenarios in comprehensive workflow, When invalid configurations are provided, Then clear error messages should be provided for all cases', () => { // Test AC4: Clear error messages for various invalid configurations // Invalid syntax in complex command const invalidSyntaxConfig: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_create_session {"sessionName": "test"}', 'ssh_connect {host: localhost, invalid json}', // Invalid JSON syntax 'ssh_exec {"command": "test"}' ], postWebSocketCommands: [] }; expect(() => new FlexibleCommandConfiguration(invalidSyntaxConfig)) .toThrow('Invalid JSON in preWebSocketCommand at index 1'); // Invalid configuration parameter with complex setup const invalidConfigParam: CommandConfigurationJSON = { preWebSocketCommands: [ 'ssh_create_session {"sessionName": "test1"}', 'ssh_create_session {"sessionName": "test2"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_disconnect {}'}, {initiator: 'mcp-client', command: 'ssh_disconnect {}'} ], workflowTimeout: 0, // Invalid timeout sessionName: 'test-session' }; expect(() => new FlexibleCommandConfiguration(invalidConfigParam)) .toThrow('workflowTimeout must be positive'); }); }); });

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