Skip to main content
Glama
ooples

MCP Console Automation Server

aws-ssm.test.ts28.3 kB
/** * AWS Systems Manager (SSM) Protocol Integration Tests * * Comprehensive testing suite for AWS SSM protocol implementation including: * - Session Manager connections * - Run Command operations * - Parameter Store integration * - CloudWatch logging * - IAM role assumptions * - Cross-region operations * - Port forwarding * - Hybrid activation */ import { AWSSSMProtocol } from '../../../src/protocols/AWSSSMProtocol.js'; import { SessionOptions } from '../../../src/types/index.js'; import { MockTestServerFactory } from '../../utils/protocol-mocks.js'; import { TestServerManager } from '../../utils/test-servers.js'; import { PerformanceBenchmark } from '../../performance/protocol-benchmarks.js'; import { SecurityTester } from '../../security/protocol-security.js'; // Mock AWS SDK jest.mock('@aws-sdk/client-ssm'); jest.mock('@aws-sdk/client-ec2'); jest.mock('@aws-sdk/client-sts'); jest.mock('@aws-sdk/client-cloudwatch-logs'); const mockSSMClient = { send: jest.fn<any>(), config: { region: 'us-east-1', credentials: { accessKeyId: 'test-key', secretAccessKey: 'test-secret' } } }; const mockEC2Client = { send: jest.fn<any>() }; const mockSTSClient = { send: jest.fn<any>() }; const mockCloudWatchLogsClient = { send: jest.fn<any>() }; // Mock WebSocket for Session Manager class MockWebSocket { private eventListeners: { [key: string]: Function[] } = {}; constructor(public url: string, public protocols?: string[]) { setTimeout(() => this.dispatchEvent({ type: 'open' }), 10); } addEventListener(event: string, handler: Function): void { if (!this.eventListeners[event]) { this.eventListeners[event] = []; } this.eventListeners[event].push(handler); } send(data: string | ArrayBuffer): void { // Mock sending data setTimeout(() => { this.dispatchEvent({ type: 'message', data: `Echo: ${data}` }); }, 10); } close(): void { this.dispatchEvent({ type: 'close' }); } private dispatchEvent(event: any): void { const handlers = this.eventListeners[event.type] || []; handlers.forEach(handler => handler(event)); } } // Skip cloud protocol tests if SKIP_HARDWARE_TESTS is set (requires AWS infrastructure) const describeIfCloud = process.env.SKIP_HARDWARE_TESTS ? describe.skip : describe; describeIfCloud('AWSSSMProtocol Integration Tests', () => { let protocol: AWSSSMProtocol; let mockFactory: MockTestServerFactory; let testServerManager: TestServerManager; let performanceBenchmark: PerformanceBenchmark; let securityTester: SecurityTester; beforeAll(async () => { // Setup test infrastructure mockFactory = new MockTestServerFactory(); testServerManager = new TestServerManager(); performanceBenchmark = new PerformanceBenchmark(); securityTester = new SecurityTester(); // Setup AWS SDK mocks jest.doMock('@aws-sdk/client-ssm', () => ({ SSMClient: jest.fn(() => mockSSMClient), StartSessionCommand: jest.fn<any>(), TerminateSessionCommand: jest.fn<any>(), SendCommandCommand: jest.fn<any>(), GetCommandInvocationCommand: jest.fn<any>(), DescribeInstanceInformationCommand: jest.fn<any>(), GetParameterCommand: jest.fn<any>(), PutParameterCommand: jest.fn<any>() })); jest.doMock('@aws-sdk/client-ec2', () => ({ EC2Client: jest.fn(() => mockEC2Client), DescribeInstancesCommand: jest.fn<any>() })); jest.doMock('@aws-sdk/client-sts', () => ({ STSClient: jest.fn(() => mockSTSClient), AssumeRoleCommand: jest.fn<any>() })); jest.doMock('@aws-sdk/client-cloudwatch-logs', () => ({ CloudWatchLogsClient: jest.fn(() => mockCloudWatchLogsClient), CreateLogGroupCommand: jest.fn<any>(), CreateLogStreamCommand: jest.fn<any>(), PutLogEventsCommand: jest.fn<any>() })); // Mock WebSocket for Session Manager (global as any).WebSocket = MockWebSocket; }); beforeEach(async () => { protocol = new AWSSSMProtocol('aws-ssm'); // Setup successful mock responses setupSuccessfulMockResponses(); await protocol.initialize(); }); afterEach(async () => { await protocol.dispose(); jest.clearAllMocks(); }); afterAll(async () => { await testServerManager.stopAllServers(); }); describe('Initialization and Configuration', () => { it('should initialize with proper AWS configuration', async () => { expect(protocol.type).toBe('aws-ssm'); expect(protocol.capabilities.supportsAuthentication).toBe(true); expect(protocol.capabilities.supportsEncryption).toBe(true); expect(protocol.capabilities.maxConcurrentSessions).toBe(10); }); it('should validate AWS credentials during initialization', async () => { mockSTSClient.send.mockResolvedValueOnce({ Account: '123456789012', UserId: 'AIDACKCEVSQ6C2EXAMPLE', Arn: 'arn:aws:sts::123456789012:assumed-role/test-role/test-session' }); const newProtocol = new AWSSSMProtocol('aws-ssm'); await expect(newProtocol.initialize()).resolves.not.toThrow(); }); it('should handle missing AWS credentials gracefully', async () => { mockSTSClient.send.mockRejectedValueOnce(new Error('No credentials found')); const newProtocol = new AWSSSMProtocol('aws-ssm'); await expect(newProtocol.initialize()).rejects.toThrow('AWS credentials validation failed'); }); it('should support multiple AWS regions', async () => { const regions = ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-southeast-1']; for (const region of regions) { const regionProtocol = new AWSSSMProtocol('aws-ssm', { region }); await regionProtocol.initialize(); expect(regionProtocol.healthStatus.isHealthy).toBe(true); await regionProtocol.dispose(); } }); it('should validate IAM permissions', async () => { mockSSMClient.send.mockResolvedValueOnce({ InstanceInformationList: [ { InstanceId: 'i-1234567890abcdef0', IPAddress: '10.0.0.100', PlatformType: 'Linux', PlatformName: 'Amazon Linux', PlatformVersion: '2', AgentVersion: '3.1.1732.0', IsLatestVersion: true, PingStatus: 'Online', LastPingDateTime: new Date() } ] }); const healthStatus = await protocol.getHealthStatus(); expect(healthStatus.isHealthy).toBe(true); expect(mockSSMClient.send).toHaveBeenCalled(); }); }); describe('Session Manager Operations', () => { const testInstanceId = 'i-1234567890abcdef0'; it('should create Session Manager session successfully', async () => { const sessionOptions: SessionOptions = { command: 'session-manager', args: [testInstanceId], streaming: true, sshOptions: { host: testInstanceId } }; mockSSMClient.send.mockResolvedValueOnce({ SessionId: 'test-session-12345', TokenValue: 'test-token-67890', StreamUrl: 'wss://ssmmessages.us-east-1.amazonaws.com/v1/data-channel/test-session-12345' }); const session = await protocol.createSession(sessionOptions); expect(session).toBeDefined(); expect(session.id).toBeDefined(); expect(session.command).toBe('session-manager'); expect(session.status).toBe('running'); expect(session.type).toBe('aws-ssm'); }); it('should handle Session Manager connection failures', async () => { mockSSMClient.send.mockRejectedValueOnce(new Error('InvalidInstanceId')); const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-invalid'], streaming: true }; await expect(protocol.createSession(sessionOptions)).rejects.toThrow('InvalidInstanceId'); }); it('should support interactive shell sessions', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'ls -la'); await protocol.sendInput(sessionId, 'echo "Hello World"'); await new Promise(resolve => setTimeout(resolve, 100)); const output = await protocol.getOutput(sessionId); expect(output).toContain('Echo:'); }); it('should manage session lifecycle properly', async () => { const sessionId = await createTestSession(); // Session should be running const sessions = await protocol.getActiveSessions(); expect(sessions).toContain(sessionId); // Close session await protocol.closeSession(sessionId); // Verify session is terminated mockSSMClient.send.mockResolvedValueOnce({}); expect(mockSSMClient.send).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ SessionId: expect.any(String) }) }) ); }); it('should support port forwarding through Session Manager', async () => { const sessionOptions: SessionOptions = { command: 'port-forward', args: [testInstanceId, '3389'], streaming: true, sshOptions: { host: testInstanceId, localForwards: [{ local: 13389, remote: 3389 }] } }; mockSSMClient.send.mockResolvedValueOnce({ SessionId: 'port-forward-session-12345', TokenValue: 'port-forward-token-67890', StreamUrl: 'wss://ssmmessages.us-east-1.amazonaws.com/v1/data-channel/port-forward-session-12345' }); const session = await protocol.createSession(sessionOptions); expect(session.command).toBe('port-forward'); expect(session.args).toEqual([testInstanceId, '3389']); }); }); describe('Run Command Operations', () => { const testInstanceId = 'i-1234567890abcdef0'; it('should execute commands via SSM Run Command', async () => { const sessionOptions: SessionOptions = { command: 'run-command', args: ['AWS-RunShellScript', '--parameters', 'commands=["ls -la"]'], streaming: false, env: { AWS_SSM_INSTANCE_ID: testInstanceId } }; mockSSMClient.send .mockResolvedValueOnce({ Command: { CommandId: 'cmd-12345', Status: 'InProgress', InstanceIds: [testInstanceId] } }) .mockResolvedValueOnce({ Status: 'Success', StandardOutputContent: 'total 4\ndrwxr-xr-x 2 ec2-user ec2-user 4096 Jan 1 00:00 test', StandardErrorContent: '', ResponseCode: 0 }); const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'check-status'); const output = await protocol.getOutput(session.id); expect(output).toContain('total 4'); }); it('should handle Run Command failures', async () => { const sessionOptions: SessionOptions = { command: 'run-command', args: ['AWS-RunShellScript', '--parameters', 'commands=["invalid-command"]'], streaming: false, env: { AWS_SSM_INSTANCE_ID: testInstanceId } }; mockSSMClient.send .mockResolvedValueOnce({ Command: { CommandId: 'cmd-12345', Status: 'InProgress' } }) .mockResolvedValueOnce({ Status: 'Failed', StandardOutputContent: '', StandardErrorContent: 'invalid-command: command not found', ResponseCode: 127 }); const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'check-status'); const output = await protocol.getOutput(session.id); expect(output).toContain('command not found'); }); it('should support custom document execution', async () => { const sessionOptions: SessionOptions = { command: 'run-command', args: ['Custom-Document', '--parameters', 'param1=value1'], streaming: false, env: { AWS_SSM_INSTANCE_ID: testInstanceId } }; mockSSMClient.send.mockResolvedValueOnce({ Command: { CommandId: 'cmd-custom-12345', DocumentName: 'Custom-Document', Status: 'Success' } }); const session = await protocol.createSession(sessionOptions); expect(session.args).toContain('Custom-Document'); }); it('should handle multi-instance command execution', async () => { const instanceIds = ['i-1234567890abcdef0', 'i-0987654321fedcba0']; const sessionOptions: SessionOptions = { command: 'run-command', args: ['AWS-RunShellScript', '--parameters', 'commands=["uptime"]'], streaming: false, env: { AWS_SSM_INSTANCE_IDS: instanceIds.join(',') } }; mockSSMClient.send.mockResolvedValueOnce({ Command: { CommandId: 'cmd-multi-12345', Status: 'InProgress', InstanceIds: instanceIds } }); const session = await protocol.createSession(sessionOptions); expect(session.env?.AWS_SSM_INSTANCE_IDS).toBe(instanceIds.join(',')); }); }); describe('Parameter Store Integration', () => { it('should retrieve parameters from Parameter Store', async () => { const sessionOptions: SessionOptions = { command: 'parameter-store', args: ['get-parameter', '/app/database/password'], streaming: false }; mockSSMClient.send.mockResolvedValueOnce({ Parameter: { Name: '/app/database/password', Value: 'encrypted-password-value', Type: 'SecureString', Version: 1 } }); const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'get-value'); const output = await protocol.getOutput(session.id); expect(output).toContain('encrypted-password-value'); }); it('should put parameters to Parameter Store', async () => { const sessionOptions: SessionOptions = { command: 'parameter-store', args: ['put-parameter', '/app/config/setting', 'new-value'], streaming: false }; mockSSMClient.send.mockResolvedValueOnce({ Version: 2, Tier: 'Standard' }); const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'put-value'); expect(mockSSMClient.send).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ Name: '/app/config/setting', Value: 'new-value' }) }) ); }); it('should handle parameter hierarchies', async () => { const sessionOptions: SessionOptions = { command: 'parameter-store', args: ['get-parameters-by-path', '/app/', '--recursive'], streaming: false }; mockSSMClient.send.mockResolvedValueOnce({ Parameters: [ { Name: '/app/database/host', Value: 'db.example.com', Type: 'String' }, { Name: '/app/database/password', Value: 'encrypted-value', Type: 'SecureString' } ] }); const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'get-hierarchy'); const output = await protocol.getOutput(session.id); expect(output).toContain('db.example.com'); expect(output).toContain('encrypted-value'); }); }); describe('CloudWatch Integration', () => { it('should create CloudWatch log groups for sessions', async () => { const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true, env: { AWS_SSM_LOG_GROUP: '/aws/ssm/test-sessions' } }; mockCloudWatchLogsClient.send.mockResolvedValueOnce({}); await createTestSessionWithLogging(sessionOptions); expect(mockCloudWatchLogsClient.send).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ logGroupName: '/aws/ssm/test-sessions' }) }) ); }); it('should stream session logs to CloudWatch', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'echo "test log message"'); // Simulate log streaming setTimeout(() => { expect(mockCloudWatchLogsClient.send).toHaveBeenCalledWith( expect.objectContaining({ input: expect.objectContaining({ logEvents: expect.arrayContaining([ expect.objectContaining({ message: expect.stringContaining('test log message') }) ]) }) }) ); }, 100); }); }); describe('Security and Compliance', () => { it('should enforce IAM role-based access', async () => { const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true, env: { AWS_ROLE_ARN: 'arn:aws:iam::123456789012:role/SSMRole' } }; mockSTSClient.send.mockResolvedValueOnce({ Credentials: { AccessKeyId: 'ASIATEMP123', SecretAccessKey: 'tempSecret', SessionToken: 'tempToken' } }); const session = await protocol.createSession(sessionOptions); expect(session.env?.AWS_ROLE_ARN).toBe('arn:aws:iam::123456789012:role/SSMRole'); }); it('should validate instance permissions', async () => { mockSSMClient.send.mockRejectedValueOnce(new Error('AccessDenied')); const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-unauthorized'], streaming: true }; await expect(protocol.createSession(sessionOptions)).rejects.toThrow('AccessDenied'); }); it('should encrypt session data in transit', async () => { const sessionId = await createTestSession(); await protocol.sendInput(sessionId, 'sensitive data'); // Verify WebSocket connection uses secure protocol const mockWS = (global as any).WebSocket; const lastCreatedWS = mockWS.mock.instances[mockWS.mock.instances.length - 1]; expect(lastCreatedWS.url).toMatch(/^wss:/); }); it('should audit session activities', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'whoami'); await protocol.executeCommand(sessionId, 'pwd'); const auditLogs = await protocol.getAuditLogs(sessionId); expect(auditLogs).toContain('whoami'); expect(auditLogs).toContain('pwd'); }); }); describe('Performance Testing', () => { it('should handle concurrent session creation efficiently', async () => { const benchmark = await performanceBenchmark.measureOperation( 'concurrent-session-creation', async () => { const sessionPromises = Array.from({ length: 5 }, () => createTestSession() ); const sessionIds = await Promise.all(sessionPromises); // Cleanup await Promise.all(sessionIds.map(id => protocol.closeSession(id))); return sessionIds.length; }, { iterations: 3, timeout: 30000 } ); expect(benchmark.averageTime).toBeLessThan(5000); expect(benchmark.successRate).toBe(100); }); it('should maintain performance under load', async () => { const sessionId = await createTestSession(); const benchmark = await performanceBenchmark.measureOperation( 'command-execution-load', async () => { const commands = Array.from({ length: 10 }, (_, i) => `echo "Command ${i}"` ); for (const command of commands) { await protocol.executeCommand(sessionId, command); } return commands.length; }, { iterations: 3 } ); expect(benchmark.averageTime).toBeLessThan(2000); await protocol.closeSession(sessionId); }); it('should monitor memory usage during long sessions', async () => { const sessionId = await createTestSession(); const initialMemory = process.memoryUsage().heapUsed; // Simulate long-running session for (let i = 0; i < 100; i++) { await protocol.sendInput(sessionId, `echo "Message ${i}"`); await new Promise(resolve => setTimeout(resolve, 10)); } const finalMemory = process.memoryUsage().heapUsed; const memoryGrowth = finalMemory - initialMemory; // Memory growth should be reasonable (less than 50MB) expect(memoryGrowth).toBeLessThan(50 * 1024 * 1024); await protocol.closeSession(sessionId); }); }); describe('Error Handling and Recovery', () => { it('should handle AWS service outages gracefully', async () => { mockSSMClient.send.mockRejectedValueOnce(new Error('ServiceUnavailable')); const healthStatus = await protocol.getHealthStatus(); expect(healthStatus.isHealthy).toBe(false); expect(healthStatus.errors).toContain('ServiceUnavailable'); }); it('should retry failed operations with exponential backoff', async () => { let attemptCount = 0; mockSSMClient.send.mockImplementation(() => { attemptCount++; if (attemptCount < 3) { return Promise.reject(new Error('ThrottlingException')); } return Promise.resolve({ SessionId: 'retry-success' }); }); const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true }; const session = await protocol.createSession(sessionOptions); expect(session).toBeDefined(); expect(attemptCount).toBe(3); }); it('should handle network connectivity issues', async () => { // Mock network error const networkError = new Error('ECONNREFUSED'); mockSSMClient.send.mockRejectedValueOnce(networkError); const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true }; await expect(protocol.createSession(sessionOptions)).rejects.toThrow('ECONNREFUSED'); }); it('should recover from WebSocket disconnections', async () => { const sessionId = await createTestSession(); // Simulate WebSocket disconnection const mockWS = (global as any).WebSocket; const wsInstance = mockWS.mock.instances[mockWS.mock.instances.length - 1]; wsInstance.dispatchEvent({ type: 'close', code: 1006 }); await new Promise(resolve => setTimeout(resolve, 100)); // Should attempt to reconnect await protocol.executeCommand(sessionId, 'test reconnect'); expect(mockSSMClient.send).toHaveBeenCalledWith( expect.objectContaining({ constructor: { name: 'StartSessionCommand' } }) ); }); }); describe('Cross-Region Operations', () => { it('should support operations across multiple AWS regions', async () => { const regions = ['us-east-1', 'us-west-2', 'eu-west-1']; const sessions: string[] = []; for (const region of regions) { const regionProtocol = new AWSSSMProtocol('aws-ssm', { region }); await regionProtocol.initialize(); const sessionId = await createTestSessionWithProtocol(regionProtocol); sessions.push(sessionId); await regionProtocol.dispose(); } expect(sessions).toHaveLength(3); }); it('should handle region-specific failures', async () => { mockSSMClient.send.mockRejectedValueOnce(new Error('RegionDisabledException')); const regionProtocol = new AWSSSMProtocol('aws-ssm', { region: 'ap-northeast-3' }); await expect(regionProtocol.initialize()).rejects.toThrow('RegionDisabledException'); }); }); describe('Integration with Other AWS Services', () => { it('should integrate with EC2 for instance discovery', async () => { mockEC2Client.send.mockResolvedValueOnce({ Reservations: [ { Instances: [ { InstanceId: 'i-1234567890abcdef0', State: { Name: 'running' }, PublicIpAddress: '203.0.113.12', PrivateIpAddress: '10.0.1.12', Tags: [ { Key: 'Name', Value: 'Test Instance' } ] } ] } ] }); const instances = await protocol.discoverInstances(); expect(instances).toHaveLength(1); expect(instances[0].InstanceId).toBe('i-1234567890abcdef0'); }); it('should support hybrid activation for on-premises servers', async () => { const sessionOptions: SessionOptions = { command: 'session-manager', args: ['mi-1234567890abcdef0'], // Managed instance ID streaming: true, env: { AWS_SSM_HYBRID: 'true' } }; mockSSMClient.send.mockResolvedValueOnce({ SessionId: 'hybrid-session-12345', TokenValue: 'hybrid-token-67890', StreamUrl: 'wss://ssmmessages.us-east-1.amazonaws.com/v1/data-channel/hybrid-session-12345' }); const session = await protocol.createSession(sessionOptions); expect(session.args?.[0]).toBe('mi-1234567890abcdef0'); }); }); // Helper functions function setupSuccessfulMockResponses(): void { mockSSMClient.send.mockImplementation((command: any) => { const commandName = command.constructor.name; switch (commandName) { case 'StartSessionCommand': return Promise.resolve({ SessionId: 'test-session-12345', TokenValue: 'test-token-67890', StreamUrl: 'wss://ssmmessages.us-east-1.amazonaws.com/v1/data-channel/test-session-12345' }); case 'DescribeInstanceInformationCommand': return Promise.resolve({ InstanceInformationList: [ { InstanceId: 'i-1234567890abcdef0', PingStatus: 'Online', PlatformType: 'Linux' } ] }); case 'GetParameterCommand': return Promise.resolve({ Parameter: { Name: command.input.Name, Value: 'test-value', Type: 'String' } }); default: return Promise.resolve({}); } }); mockSTSClient.send.mockResolvedValue({ Account: '123456789012', UserId: 'AIDACKCEVSQ6C2EXAMPLE' }); mockEC2Client.send.mockResolvedValue({ Reservations: [] }); mockCloudWatchLogsClient.send.mockResolvedValue({}); } async function createTestSession(): Promise<string> { const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true }; const session = await protocol.createSession(sessionOptions); return session.id; } async function createTestSessionWithProtocol(testProtocol: AWSSSMProtocol): Promise<string> { const sessionOptions: SessionOptions = { command: 'session-manager', args: ['i-1234567890abcdef0'], streaming: true }; const session = await testProtocol.createSession(sessionOptions); return session.id; } async function createTestSessionWithLogging(options: SessionOptions): Promise<string> { const session = await protocol.createSession(options); return session.id; } });

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/ooples/mcp-console-automation'

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