Skip to main content
Glama
ooples

MCP Console Automation Server

azure-shell.test.ts28 kB
/** * Azure Cloud Shell Protocol Integration Tests * * Comprehensive testing suite for Azure Cloud Shell protocol implementation including: * - Azure CLI integration * - PowerShell Core sessions * - Resource management operations * - Authentication with Azure AD * - Subscription switching * - File storage integration * - ARM template deployments * - Azure DevOps integration */ import { AzureProtocol } from '../../../src/protocols/AzureProtocol.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 Azure SDK jest.mock('@azure/identity'); jest.mock('@azure/arm-resources'); jest.mock('@azure/arm-compute'); jest.mock('@azure/arm-storage'); jest.mock('@azure/storage-file-share'); const mockCredential = { getToken: jest.fn<any>().mockResolvedValue({ token: 'mock-azure-token', expiresOnTimestamp: Date.now() + 3600000 }) }; const mockResourcesClient = { resourceGroups: { list: jest.fn<any>(), get: jest.fn<any>(), createOrUpdate: jest.fn<any>(), delete: jest.fn<any>() }, resources: { listByResourceGroup: jest.fn<any>() } }; const mockComputeClient = { virtualMachines: { list: jest.fn<any>(), get: jest.fn<any>(), createOrUpdate: jest.fn<any>(), delete: jest.fn<any>(), powerOff: jest.fn<any>(), start: jest.fn<any>() } }; const mockStorageClient = { storageAccounts: { list: jest.fn<any>(), create: jest.fn<any>() } }; const mockFileShareClient = { getDirectoryClient: jest.fn<any>(), create: jest.fn<any>(), delete: jest.fn<any>() }; // Mock Azure Cloud Shell WebSocket API class MockAzureCloudShellWebSocket { 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 { const message = typeof data === 'string' ? data : new TextDecoder().decode(data); setTimeout(() => { // Simulate Azure CLI responses if (message.includes('az account list')) { this.dispatchEvent({ type: 'message', data: JSON.stringify([{ id: '12345678-1234-1234-1234-123456789012', name: 'Test Subscription', state: 'Enabled', isDefault: true }]) }); } else if (message.includes('az resource list')) { this.dispatchEvent({ type: 'message', data: JSON.stringify([ { id: '/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm', name: 'test-vm', type: 'Microsoft.Compute/virtualMachines', location: 'eastus' } ]) }); } else { this.dispatchEvent({ type: 'message', data: `Azure Shell: ${message}` }); } }, 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 Azure infrastructure) const describeIfCloud = process.env.SKIP_HARDWARE_TESTS ? describe.skip : describe; describeIfCloud('AzureShellProtocol Integration Tests', () => { let protocol: AzureProtocol; 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 Azure SDK mocks jest.doMock('@azure/identity', () => ({ DefaultAzureCredential: jest.fn(() => mockCredential), ClientSecretCredential: jest.fn(() => mockCredential), InteractiveBrowserCredential: jest.fn(() => mockCredential) })); jest.doMock('@azure/arm-resources', () => ({ ResourceManagementClient: jest.fn(() => mockResourcesClient) })); jest.doMock('@azure/arm-compute', () => ({ ComputeManagementClient: jest.fn(() => mockComputeClient) })); jest.doMock('@azure/arm-storage', () => ({ StorageManagementClient: jest.fn(() => mockStorageClient) })); // Mock WebSocket for Azure Cloud Shell (global as any).WebSocket = MockAzureCloudShellWebSocket; }); beforeEach(async () => { protocol = new AzureProtocol('azure-shell'); // Setup successful mock responses setupSuccessfulMockResponses(); await protocol.initialize(); }); afterEach(async () => { await protocol.dispose(); jest.clearAllMocks(); }); afterAll(async () => { await testServerManager.stopAllServers(); }); describe('Initialization and Authentication', () => { it('should initialize with proper Azure credentials', async () => { expect(protocol.type).toBe('azure-shell'); expect(protocol.capabilities.supportsAuthentication).toBe(true); expect(protocol.capabilities.supportsEncryption).toBe(true); expect(protocol.capabilities.maxConcurrentSessions).toBe(50); }); it('should authenticate with Azure AD', async () => { mockCredential.getToken.mockResolvedValueOnce({ token: 'valid-azure-token', expiresOnTimestamp: Date.now() + 3600000 }); const newProtocol = new AzureProtocol('azure-shell'); await expect(newProtocol.initialize()).resolves.not.toThrow(); }); it('should handle authentication failures gracefully', async () => { mockCredential.getToken.mockRejectedValueOnce(new Error('Authentication failed')); const newProtocol = new AzureProtocol('azure-shell'); await expect(newProtocol.initialize()).rejects.toThrow('Azure authentication failed'); }); it('should support different authentication methods', async () => { const authMethods = [ { method: 'default', config: {} }, { method: 'interactive', config: {} }, { method: 'service-principal', config: { clientId: 'test', clientSecret: 'secret', tenantId: 'tenant' } } ]; for (const auth of authMethods) { const authProtocol = new AzureProtocol('azure-shell', auth.config); await authProtocol.initialize(); expect(authProtocol.healthStatus.isHealthy).toBe(true); await authProtocol.dispose(); } }); it('should validate Azure subscription access', async () => { mockResourcesClient.resourceGroups.list.mockResolvedValueOnce([ { name: 'test-rg', location: 'eastus', id: '/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg' } ]); const healthStatus = await protocol.getHealthStatus(); expect(healthStatus.isHealthy).toBe(true); expect(mockResourcesClient.resourceGroups.list).toHaveBeenCalled(); }); }); describe('Azure CLI Operations', () => { it('should execute Azure CLI commands successfully', async () => { const sessionOptions: SessionOptions = { command: 'az', args: ['account', 'list'], streaming: true, shellType: 'bash' }; const session = await protocol.createSession(sessionOptions); expect(session).toBeDefined(); expect(session.command).toBe('az'); expect(session.args).toEqual(['account', 'list']); expect(session.type).toBe('azure-shell'); expect(session.status).toBe('running'); }); it('should support resource group management', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az group list'); await new Promise(resolve => setTimeout(resolve, 100)); const output = await protocol.getOutput(sessionId); expect(output).toContain('Test Subscription'); }); it('should manage Azure resources', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az resource list --resource-group test-rg'); await new Promise(resolve => setTimeout(resolve, 100)); const output = await protocol.getOutput(sessionId); expect(output).toContain('test-vm'); expect(output).toContain('Microsoft.Compute/virtualMachines'); }); it('should handle subscription switching', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az account set --subscription "Test Subscription"'); await protocol.executeCommand(sessionId, 'az account show'); const output = await protocol.getOutput(sessionId); expect(output).toContain('Test Subscription'); }); it('should support ARM template deployments', async () => { const sessionOptions: SessionOptions = { command: 'az', args: ['deployment', 'group', 'create'], streaming: true, env: { AZURE_TEMPLATE_FILE: '/clouddrive/templates/vm-template.json', AZURE_PARAMETERS_FILE: '/clouddrive/parameters/vm-params.json' } }; const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'validate-template'); expect(session.env?.AZURE_TEMPLATE_FILE).toContain('vm-template.json'); }); }); describe('PowerShell Core Operations', () => { it('should support PowerShell Core sessions', async () => { const sessionOptions: SessionOptions = { command: 'pwsh', args: ['-c', 'Get-AzSubscription'], streaming: true, shellType: 'powershell' }; const session = await protocol.createSession(sessionOptions); expect(session.command).toBe('pwsh'); expect(session.args).toContain('Get-AzSubscription'); }); it('should execute Azure PowerShell cmdlets', async () => { const sessionOptions: SessionOptions = { command: 'pwsh', streaming: true, shellType: 'powershell' }; const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'Get-AzResourceGroup'); await protocol.executeCommand(session.id, 'Get-AzVM'); const output = await protocol.getOutput(session.id); expect(output).toContain('Azure Shell:'); }); it('should support PowerShell module management', async () => { const sessionId = await createTestPowerShellSession(); const commands = [ 'Get-Module -ListAvailable Az*', 'Import-Module Az.Accounts', 'Connect-AzAccount -Identity' ]; for (const command of commands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should handle PowerShell script execution', async () => { const sessionOptions: SessionOptions = { command: 'pwsh', args: ['-File', '/clouddrive/scripts/deploy-resources.ps1'], streaming: true, shellType: 'powershell' }; const session = await protocol.createSession(sessionOptions); expect(session.args).toContain('deploy-resources.ps1'); }); }); describe('File Storage Integration', () => { it('should access Azure Cloud Shell file storage', async () => { const sessionId = await createTestSession(); const commands = [ 'ls /clouddrive', 'mkdir /clouddrive/projects', 'echo "test content" > /clouddrive/test.txt' ]; for (const command of commands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should support file uploads and downloads', async () => { const sessionOptions: SessionOptions = { command: 'azure-storage', args: ['upload', 'local-file.txt', '/clouddrive/uploaded-file.txt'], streaming: true }; mockFileShareClient.getDirectoryClient.mockReturnValue({ getFileClient: jest.fn<any>().mockReturnValue({ upload: jest.fn<any>().mockResolvedValue({}), download: jest.fn<any>().mockResolvedValue({ readableStreamBody: 'test content' }) }) }); const session = await protocol.createSession(sessionOptions); expect(session.args).toContain('upload'); }); it('should manage Azure File Share storage', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az storage share list --account-name cloudshellstorage'); const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should persist session state across reconnections', async () => { const sessionOptions: SessionOptions = { command: 'bash', streaming: true, env: { AZURE_PERSIST_STATE: 'true' } }; const session1 = await protocol.createSession(sessionOptions); await protocol.executeCommand(session1.id, 'export TEST_VAR="persistent_value"'); await protocol.closeSession(session1.id); const session2 = await protocol.createSession(sessionOptions); await protocol.executeCommand(session2.id, 'echo $TEST_VAR'); const output = await protocol.getOutput(session2.id); // In real Azure Cloud Shell, environment would persist expect(output).toContain('Azure Shell:'); }); }); describe('Resource Management Operations', () => { it('should manage virtual machines', async () => { mockComputeClient.virtualMachines.list.mockResolvedValueOnce([ { name: 'test-vm', id: '/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm', location: 'eastus', vmSize: 'Standard_B1s' } ]); const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az vm list --output table'); const output = await protocol.getOutput(sessionId); expect(output).toContain('test-vm'); }); it('should manage storage accounts', async () => { mockStorageClient.storageAccounts.list.mockResolvedValueOnce([ { name: 'teststorage123', location: 'eastus', kind: 'StorageV2' } ]); const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az storage account list --output table'); const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should deploy Azure resources using templates', async () => { const sessionOptions: SessionOptions = { command: 'az', args: [ 'deployment', 'group', 'create', '--resource-group', 'test-rg', '--template-file', 'azuredeploy.json' ], streaming: true }; const session = await protocol.createSession(sessionOptions); await protocol.executeCommand(session.id, 'start-deployment'); expect(session.args).toContain('azuredeploy.json'); }); it('should support Azure DevOps integration', async () => { const sessionId = await createTestSession(); const devOpsCommands = [ 'az extension add --name azure-devops', 'az devops project list --organization https://dev.azure.com/testorg', 'az pipelines list --project TestProject' ]; for (const command of devOpsCommands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); }); describe('Security and Compliance', () => { it('should enforce Azure RBAC permissions', async () => { const sessionOptions: SessionOptions = { command: 'az', args: ['role', 'assignment', 'list'], streaming: true, env: { AZURE_SUBSCRIPTION_ID: '12345678-1234-1234-1234-123456789012' } }; const session = await protocol.createSession(sessionOptions); expect(session.env?.AZURE_SUBSCRIPTION_ID).toBe('12345678-1234-1234-1234-123456789012'); }); it('should validate resource access permissions', async () => { mockResourcesClient.resourceGroups.get.mockRejectedValueOnce( new Error('AuthorizationFailed') ); const sessionId = await createTestSession(); await expect( protocol.executeCommand(sessionId, 'az group show --name unauthorized-rg') ).rejects.toThrow(); }); it('should audit Azure operations', async () => { const sessionId = await createTestSession(); const auditableCommands = [ 'az vm create --name test-vm --resource-group test-rg', 'az storage account create --name teststorage', 'az keyvault create --name test-keyvault' ]; for (const command of auditableCommands) { await protocol.executeCommand(sessionId, command); } const auditLogs = await protocol.getAuditLogs(sessionId); expect(auditLogs).toContain('az vm create'); expect(auditLogs).toContain('az storage account create'); }); it('should handle Azure policy compliance', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az policy assignment list'); await protocol.executeCommand(sessionId, 'az policy state list'); const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should secure credential handling', async () => { const sessionOptions: SessionOptions = { command: 'az', streaming: true, env: { AZURE_CLIENT_SECRET: 'sensitive-secret' } }; const session = await protocol.createSession(sessionOptions); // Verify sensitive data is not logged const output = await protocol.getOutput(session.id); expect(output).not.toContain('sensitive-secret'); }); }); describe('Performance Testing', () => { it('should handle concurrent Azure operations efficiently', async () => { const benchmark = await performanceBenchmark.measureOperation( 'concurrent-azure-operations', async () => { const sessionPromises = Array.from({ length: 3 }, () => createTestSession() ); const sessionIds = await Promise.all(sessionPromises); // Execute concurrent operations const operationPromises = sessionIds.map(sessionId => protocol.executeCommand(sessionId, 'az account list') ); await Promise.all(operationPromises); // Cleanup await Promise.all(sessionIds.map(id => protocol.closeSession(id))); return sessionIds.length; }, { iterations: 3, timeout: 60000 } ); expect(benchmark.averageTime).toBeLessThan(10000); expect(benchmark.successRate).toBe(100); }); it('should optimize Azure CLI command execution', async () => { const sessionId = await createTestSession(); const benchmark = await performanceBenchmark.measureOperation( 'azure-cli-commands', async () => { const commands = [ 'az account list', 'az group list', 'az resource list --query "[].{Name:name, Type:type}"', 'az vm list --output table' ]; for (const command of commands) { await protocol.executeCommand(sessionId, command); } return commands.length; }, { iterations: 3 } ); expect(benchmark.averageTime).toBeLessThan(5000); await protocol.closeSession(sessionId); }); it('should monitor memory usage during resource operations', async () => { const sessionId = await createTestSession(); const initialMemory = process.memoryUsage().heapUsed; // Simulate resource-intensive operations const resourceOperations = [ 'az vm list --output json', 'az storage account list --output json', 'az network vnet list --output json', 'az keyvault list --output json' ]; for (const command of resourceOperations) { await protocol.executeCommand(sessionId, command); await new Promise(resolve => setTimeout(resolve, 50)); } const finalMemory = process.memoryUsage().heapUsed; const memoryGrowth = finalMemory - initialMemory; // Memory growth should be reasonable (less than 100MB) expect(memoryGrowth).toBeLessThan(100 * 1024 * 1024); await protocol.closeSession(sessionId); }); }); describe('Error Handling and Recovery', () => { it('should handle Azure service outages gracefully', async () => { mockCredential.getToken.mockRejectedValueOnce(new Error('ServiceUnavailable')); const healthStatus = await protocol.getHealthStatus(); expect(healthStatus.isHealthy).toBe(false); expect(healthStatus.errors).toContain('ServiceUnavailable'); }); it('should retry failed Azure API calls', async () => { let attemptCount = 0; mockResourcesClient.resourceGroups.list.mockImplementation(() => { attemptCount++; if (attemptCount < 3) { return Promise.reject(new Error('TooManyRequests')); } return Promise.resolve([]); }); const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az group list'); expect(attemptCount).toBe(3); }); it('should handle token expiration and renewal', async () => { // Mock expired token mockCredential.getToken .mockResolvedValueOnce({ token: 'expired-token', expiresOnTimestamp: Date.now() - 1000 }) .mockResolvedValueOnce({ token: 'new-token', expiresOnTimestamp: Date.now() + 3600000 }); const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az account list'); // Should have called getToken twice (expired + renewal) expect(mockCredential.getToken).toHaveBeenCalledTimes(2); }); it('should recover from Cloud Shell 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(mockWS).toHaveBeenCalledWith( expect.stringMatching(/wss.*azure/), expect.any(Array) ); }); }); describe('Multi-tenant Operations', () => { it('should support tenant switching', async () => { const sessionId = await createTestSession(); await protocol.executeCommand(sessionId, 'az account tenant list'); await protocol.executeCommand(sessionId, 'az login --tenant test-tenant-id'); const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should manage multiple subscriptions', async () => { const sessionId = await createTestSession(); const subscriptionCommands = [ 'az account list --output table', 'az account set --subscription "Production Subscription"', 'az account show --query name', 'az account set --subscription "Development Subscription"' ]; for (const command of subscriptionCommands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); }); describe('Integration with Azure Services', () => { it('should integrate with Azure Key Vault', async () => { const sessionId = await createTestSession(); const keyVaultCommands = [ 'az keyvault list', 'az keyvault secret list --vault-name test-keyvault', 'az keyvault secret show --vault-name test-keyvault --name test-secret' ]; for (const command of keyVaultCommands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should work with Azure Container Registry', async () => { const sessionId = await createTestSession(); const acrCommands = [ 'az acr list --output table', 'az acr repository list --name testregistry', 'docker login testregistry.azurecr.io' ]; for (const command of acrCommands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); it('should support Azure Kubernetes Service', async () => { const sessionId = await createTestSession(); const aksCommands = [ 'az aks list --output table', 'az aks get-credentials --resource-group test-rg --name test-aks', 'kubectl get nodes' ]; for (const command of aksCommands) { await protocol.executeCommand(sessionId, command); } const output = await protocol.getOutput(sessionId); expect(output).toContain('Azure Shell:'); }); }); // Helper functions function setupSuccessfulMockResponses(): void { mockCredential.getToken.mockResolvedValue({ token: 'valid-azure-token', expiresOnTimestamp: Date.now() + 3600000 }); mockResourcesClient.resourceGroups.list.mockResolvedValue([ { name: 'test-rg', location: 'eastus', id: '/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg' } ]); mockComputeClient.virtualMachines.list.mockResolvedValue([]); mockStorageClient.storageAccounts.list.mockResolvedValue([]); } async function createTestSession(): Promise<string> { const sessionOptions: SessionOptions = { command: 'bash', streaming: true, shellType: 'bash' }; const session = await protocol.createSession(sessionOptions); return session.id; } async function createTestPowerShellSession(): Promise<string> { const sessionOptions: SessionOptions = { command: 'pwsh', streaming: true, shellType: 'powershell' }; const session = await protocol.createSession(sessionOptions); 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