UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

802 lines (671 loc) 26.1 kB
/** * A2A Transport Layer Tests * * Comprehensive test suite for A2ATransportLayer supporting WebSocket, HTTP, gRPC */ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { TransportProtocol, TransportConfig, A2AMessage, A2AResponse, AgentId, TLSConfig, AuthConfig } from '../../../src/types/a2a'; // Mock transport implementations will be created later interface TransportConnection { id: string; protocol: TransportProtocol; isConnected: boolean; lastActivity: number; } class A2ATransportLayer { constructor() {} async initialize(configs: TransportConfig[]): Promise<void> {} async shutdown(): Promise<void> {} async connect(agentId: AgentId, config: TransportConfig): Promise<TransportConnection> { return {} as TransportConnection; } async disconnect(connectionId: string): Promise<void> {} async sendMessage(connectionId: string, message: A2AMessage): Promise<A2AResponse> { return {} as A2AResponse; } async sendNotification(connectionId: string, notification: A2AMessage): Promise<void> {} async broadcastMessage(message: A2AMessage, excludeConnections?: string[]): Promise<A2AResponse[]> { return []; } getActiveConnections(): Map<string, TransportConnection> { return new Map(); } getConnectionByAgentId(agentId: AgentId): TransportConnection | undefined { return undefined; } getTransportMetrics(): any { return {}; } isProtocolSupported(protocol: TransportProtocol): boolean { return true; } } describe('A2ATransportLayer', () => { let transportLayer: A2ATransportLayer; let mockConfigs: TransportConfig[]; beforeEach(async () => { transportLayer = new A2ATransportLayer(); mockConfigs = [ { protocol: 'websocket', host: 'localhost', port: 8080, path: '/a2a-ws', secure: false, timeout: 30000, keepAlive: true, compression: true, auth: { type: 'token', credentials: { token: 'ws-auth-token' } } }, { protocol: 'http', host: 'localhost', port: 8081, path: '/a2a-http', secure: true, timeout: 15000, compression: false, tls: { cert: 'mock-cert.pem', key: 'mock-key.pem', ca: 'mock-ca.pem', rejectUnauthorized: true }, auth: { type: 'certificate', credentials: { certFile: 'client-cert.pem' } } }, { protocol: 'grpc', host: 'localhost', port: 9090, secure: true, timeout: 45000, keepAlive: true, tls: { cert: 'grpc-cert.pem', key: 'grpc-key.pem', rejectUnauthorized: false }, auth: { type: 'oauth2', credentials: { clientId: 'grpc-client-id', clientSecret: 'grpc-client-secret', tokenUrl: 'https://auth.example.com/token' } } }, { protocol: 'tcp', host: '192.168.1.100', port: 9999, secure: false, timeout: 60000, keepAlive: true, auth: { type: 'none' } } ]; await transportLayer.initialize(mockConfigs); }); afterEach(async () => { await transportLayer.shutdown(); jest.clearAllMocks(); }); describe('Initialization and Configuration', () => { it('should initialize with multiple transport configurations', async () => { await expect(transportLayer.initialize(mockConfigs)).resolves.not.toThrow(); }); it('should validate transport configurations', async () => { const invalidConfigs = [ { protocol: 'websocket' as TransportProtocol, // Missing required host port: 8080 } ]; await expect(transportLayer.initialize(invalidConfigs)) .rejects.toThrow('Invalid transport configuration: missing host'); }); it('should check protocol support', () => { expect(transportLayer.isProtocolSupported('websocket')).toBe(true); expect(transportLayer.isProtocolSupported('http')).toBe(true); expect(transportLayer.isProtocolSupported('grpc')).toBe(true); expect(transportLayer.isProtocolSupported('tcp')).toBe(true); }); it('should handle graceful shutdown', async () => { await expect(transportLayer.shutdown()).resolves.not.toThrow(); }); }); describe('WebSocket Transport', () => { let wsConfig: TransportConfig; beforeEach(() => { wsConfig = mockConfigs.find(c => c.protocol === 'websocket')!; }); it('should establish WebSocket connection', async () => { const connection = await transportLayer.connect('test-agent-001', wsConfig); expect(connection.protocol).toBe('websocket'); expect(connection.isConnected).toBe(true); expect(connection.id).toBeDefined(); }); it('should send message over WebSocket', async () => { const connection = await transportLayer.connect('test-agent-001', wsConfig); const message: A2AMessage = { jsonrpc: '2.0', method: 'test.websocket', params: { data: 'websocket test data' }, id: 'ws-001', from: 'test-agent-001', to: 'target-agent-001', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, message); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe('ws-001'); }); it('should handle WebSocket connection failure', async () => { const invalidWsConfig: TransportConfig = { ...wsConfig, port: 99999 // Invalid port }; await expect(transportLayer.connect('test-agent-002', invalidWsConfig)) .rejects.toThrow('WebSocket connection failed'); }); it('should support WebSocket compression', async () => { const compressedConfig: TransportConfig = { ...wsConfig, compression: true }; const connection = await transportLayer.connect('compressed-agent', compressedConfig); expect(connection.isConnected).toBe(true); }); it('should handle WebSocket keepalive', async () => { const connection = await transportLayer.connect('keepalive-agent', wsConfig); // Simulate some time passing await new Promise(resolve => setTimeout(resolve, 100)); expect(connection.isConnected).toBe(true); expect(connection.lastActivity).toBeGreaterThan(0); }); it('should handle WebSocket authentication', async () => { const authedConfig: TransportConfig = { ...wsConfig, auth: { type: 'token', credentials: { token: 'valid-ws-token' } } }; const connection = await transportLayer.connect('authed-agent', authedConfig); expect(connection.isConnected).toBe(true); }); it('should reject invalid WebSocket authentication', async () => { const invalidAuthConfig: TransportConfig = { ...wsConfig, auth: { type: 'token', credentials: { token: 'invalid-token' } } }; await expect(transportLayer.connect('invalid-auth-agent', invalidAuthConfig)) .rejects.toThrow('WebSocket authentication failed'); }); }); describe('HTTP Transport', () => { let httpConfig: TransportConfig; beforeEach(() => { httpConfig = mockConfigs.find(c => c.protocol === 'http')!; }); it('should establish HTTP connection', async () => { const connection = await transportLayer.connect('http-agent-001', httpConfig); expect(connection.protocol).toBe('http'); expect(connection.isConnected).toBe(true); }); it('should send message over HTTP', async () => { const connection = await transportLayer.connect('http-agent-001', httpConfig); const message: A2AMessage = { jsonrpc: '2.0', method: 'test.http', params: { endpoint: '/api/test' }, id: 'http-001', from: 'http-agent-001', to: 'target-agent-001', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, message); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe('http-001'); }); it('should handle HTTPS with TLS configuration', async () => { const httpsConfig: TransportConfig = { ...httpConfig, secure: true, tls: { cert: 'client-cert.pem', key: 'client-key.pem', ca: 'ca-cert.pem', rejectUnauthorized: true } }; const connection = await transportLayer.connect('https-agent', httpsConfig); expect(connection.isConnected).toBe(true); }); it('should handle HTTP timeout', async () => { const timeoutConfig: TransportConfig = { ...httpConfig, timeout: 100 // Very short timeout }; const connection = await transportLayer.connect('timeout-agent', timeoutConfig); const slowMessage: A2AMessage = { jsonrpc: '2.0', method: 'slow.operation', params: {}, id: 'timeout-001', from: 'timeout-agent', to: 'slow-target', timestamp: Date.now(), messageType: 'request' }; await expect(transportLayer.sendMessage(connection.id, slowMessage)) .rejects.toThrow('HTTP request timeout'); }); it('should handle HTTP certificate authentication', async () => { const certAuthConfig: TransportConfig = { ...httpConfig, auth: { type: 'certificate', credentials: { certFile: 'client-cert.pem', keyFile: 'client-key.pem' } } }; const connection = await transportLayer.connect('cert-agent', certAuthConfig); expect(connection.isConnected).toBe(true); }); it('should handle HTTP connection pooling', async () => { // Create multiple connections to same endpoint const connections = await Promise.all([ transportLayer.connect('pool-agent-1', httpConfig), transportLayer.connect('pool-agent-2', httpConfig), transportLayer.connect('pool-agent-3', httpConfig) ]); expect(connections).toHaveLength(3); connections.forEach(conn => { expect(conn.isConnected).toBe(true); expect(conn.protocol).toBe('http'); }); }); }); describe('gRPC Transport', () => { let grpcConfig: TransportConfig; beforeEach(() => { grpcConfig = mockConfigs.find(c => c.protocol === 'grpc')!; }); it('should establish gRPC connection', async () => { const connection = await transportLayer.connect('grpc-agent-001', grpcConfig); expect(connection.protocol).toBe('grpc'); expect(connection.isConnected).toBe(true); }); it('should send message over gRPC', async () => { const connection = await transportLayer.connect('grpc-agent-001', grpcConfig); const message: A2AMessage = { jsonrpc: '2.0', method: 'test.grpc', params: { service: 'A2AService', method: 'ProcessMessage' }, id: 'grpc-001', from: 'grpc-agent-001', to: 'target-agent-001', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, message); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe('grpc-001'); }); it('should handle gRPC streaming', async () => { const streamingConfig: TransportConfig = { ...grpcConfig, // gRPC-specific streaming options could be added here }; const connection = await transportLayer.connect('streaming-agent', streamingConfig); const streamMessage: A2AMessage = { jsonrpc: '2.0', method: 'stream.data', params: { streamId: 'stream-001' }, id: 'stream-001', from: 'streaming-agent', to: 'stream-consumer', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, streamMessage); expect(response.id).toBe('stream-001'); }); it('should handle gRPC OAuth2 authentication', async () => { const oauth2Config: TransportConfig = { ...grpcConfig, auth: { type: 'oauth2', credentials: { clientId: 'grpc-client', clientSecret: 'grpc-secret', tokenUrl: 'https://auth.example.com/oauth/token' } } }; const connection = await transportLayer.connect('oauth2-agent', oauth2Config); expect(connection.isConnected).toBe(true); }); it('should handle gRPC connection errors', async () => { const invalidGrpcConfig: TransportConfig = { ...grpcConfig, host: 'invalid-grpc-host.local' }; await expect(transportLayer.connect('invalid-grpc-agent', invalidGrpcConfig)) .rejects.toThrow('gRPC connection failed'); }); it('should handle gRPC metadata and headers', async () => { const connection = await transportLayer.connect('metadata-agent', grpcConfig); const messageWithMetadata: A2AMessage = { jsonrpc: '2.0', method: 'test.metadata', params: { metadata: { 'custom-header': 'custom-value', 'agent-version': '1.0.0' } }, id: 'metadata-001', from: 'metadata-agent', to: 'target-agent', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, messageWithMetadata); expect(response.id).toBe('metadata-001'); }); }); describe('TCP Transport', () => { let tcpConfig: TransportConfig; beforeEach(() => { tcpConfig = mockConfigs.find(c => c.protocol === 'tcp')!; }); it('should establish TCP connection', async () => { const connection = await transportLayer.connect('tcp-agent-001', tcpConfig); expect(connection.protocol).toBe('tcp'); expect(connection.isConnected).toBe(true); }); it('should send message over TCP', async () => { const connection = await transportLayer.connect('tcp-agent-001', tcpConfig); const message: A2AMessage = { jsonrpc: '2.0', method: 'test.tcp', params: { data: 'raw tcp data' }, id: 'tcp-001', from: 'tcp-agent-001', to: 'target-agent-001', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, message); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe('tcp-001'); }); it('should handle TCP connection keepalive', async () => { const keepAliveConfig: TransportConfig = { ...tcpConfig, keepAlive: true }; const connection = await transportLayer.connect('keepalive-tcp-agent', keepAliveConfig); expect(connection.isConnected).toBe(true); // Simulate network activity await new Promise(resolve => setTimeout(resolve, 50)); expect(connection.lastActivity).toBeGreaterThan(0); }); it('should handle TCP large message transmission', async () => { const connection = await transportLayer.connect('large-msg-agent', tcpConfig); const largeMessage: A2AMessage = { jsonrpc: '2.0', method: 'test.large', params: { data: 'x'.repeat(10000), // 10KB of data chunks: Array(100).fill(0).map((_, i) => `chunk-${i}`) }, id: 'large-001', from: 'large-msg-agent', to: 'target-agent', timestamp: Date.now(), messageType: 'request' }; const response = await transportLayer.sendMessage(connection.id, largeMessage); expect(response.id).toBe('large-001'); }); }); describe('Connection Management', () => { it('should track active connections', async () => { const connections = await Promise.all([ transportLayer.connect('agent-1', mockConfigs[0]), transportLayer.connect('agent-2', mockConfigs[1]), transportLayer.connect('agent-3', mockConfigs[2]) ]); const activeConnections = transportLayer.getActiveConnections(); expect(activeConnections.size).toBe(3); connections.forEach(conn => { expect(activeConnections.has(conn.id)).toBe(true); }); }); it('should find connection by agent ID', async () => { const connection = await transportLayer.connect('findable-agent', mockConfigs[0]); const foundConnection = transportLayer.getConnectionByAgentId('findable-agent'); expect(foundConnection).toBeDefined(); expect(foundConnection?.id).toBe(connection.id); }); it('should disconnect specific connections', async () => { const connection = await transportLayer.connect('disconnect-agent', mockConfigs[0]); expect(connection.isConnected).toBe(true); await transportLayer.disconnect(connection.id); const activeConnections = transportLayer.getActiveConnections(); expect(activeConnections.has(connection.id)).toBe(false); }); it('should handle connection cleanup on shutdown', async () => { await Promise.all([ transportLayer.connect('cleanup-1', mockConfigs[0]), transportLayer.connect('cleanup-2', mockConfigs[1]) ]); expect(transportLayer.getActiveConnections().size).toBe(2); await transportLayer.shutdown(); expect(transportLayer.getActiveConnections().size).toBe(0); }); it('should detect and handle stale connections', async () => { const connection = await transportLayer.connect('stale-agent', mockConfigs[0]); // Simulate stale connection by updating last activity to old timestamp connection.lastActivity = Date.now() - 300000; // 5 minutes ago const activeConnections = transportLayer.getActiveConnections(); const staleConnection = activeConnections.get(connection.id); expect(staleConnection?.lastActivity).toBeLessThan(Date.now() - 60000); }); }); describe('Broadcasting and Multicasting', () => { beforeEach(async () => { // Set up multiple connections for broadcasting tests await Promise.all([ transportLayer.connect('broadcast-1', mockConfigs[0]), transportLayer.connect('broadcast-2', mockConfigs[1]), transportLayer.connect('broadcast-3', mockConfigs[2]) ]); }); it('should broadcast message to all connections', async () => { const broadcastMessage: A2AMessage = { jsonrpc: '2.0', method: 'system.broadcast', params: { announcement: 'System update available' }, id: 'broadcast-001', from: 'system-agent', to: 'broadcast', timestamp: Date.now(), messageType: 'notification' }; const responses = await transportLayer.broadcastMessage(broadcastMessage); expect(responses.length).toBe(3); // Should send to all 3 connections responses.forEach(response => { expect(response.jsonrpc).toBe('2.0'); }); }); it('should broadcast with exclusions', async () => { const connections = transportLayer.getActiveConnections(); const connectionIds = Array.from(connections.keys()); const excludeFirst = [connectionIds[0]]; // Exclude first connection const selectiveBroadcast: A2AMessage = { jsonrpc: '2.0', method: 'selective.broadcast', params: { message: 'Selective message' }, id: 'selective-001', from: 'selective-agent', to: 'broadcast', timestamp: Date.now(), messageType: 'notification' }; const responses = await transportLayer.broadcastMessage(selectiveBroadcast, excludeFirst); expect(responses.length).toBe(2); // Should send to 2 connections (excluding 1) }); it('should handle broadcast failures gracefully', async () => { // Disconnect one connection to simulate partial failure const connections = transportLayer.getActiveConnections(); const firstConnectionId = Array.from(connections.keys())[0]; await transportLayer.disconnect(firstConnectionId); const broadcastMessage: A2AMessage = { jsonrpc: '2.0', method: 'partial.broadcast', params: { data: 'test data' }, id: 'partial-001', from: 'test-agent', to: 'broadcast', timestamp: Date.now(), messageType: 'notification' }; const responses = await transportLayer.broadcastMessage(broadcastMessage); expect(responses.length).toBe(2); // Should succeed with remaining connections }); }); describe('Error Handling and Recovery', () => { it('should handle connection timeout', async () => { const timeoutConfig: TransportConfig = { protocol: 'websocket', host: 'unreachable-host.local', port: 8080, timeout: 1000 // 1 second timeout }; await expect(transportLayer.connect('timeout-agent', timeoutConfig)) .rejects.toThrow('Connection timeout'); }); it('should retry failed connections', async () => { const retryConfig: TransportConfig = { ...mockConfigs[0], host: 'initially-unreachable.local' // Will fail first, succeed on retry }; // Mock retry logic should eventually succeed const connection = await transportLayer.connect('retry-agent', retryConfig); expect(connection.isConnected).toBe(true); }); it('should handle authentication failures', async () => { const invalidAuthConfig: TransportConfig = { ...mockConfigs[0], auth: { type: 'token', credentials: { token: 'invalid-token' } } }; await expect(transportLayer.connect('auth-fail-agent', invalidAuthConfig)) .rejects.toThrow('Authentication failed'); }); it('should handle network interruptions', async () => { const connection = await transportLayer.connect('network-test-agent', mockConfigs[0]); expect(connection.isConnected).toBe(true); // Simulate network interruption // The transport layer should detect and handle this const message: A2AMessage = { jsonrpc: '2.0', method: 'test.interrupted', params: {}, id: 'interrupted-001', from: 'network-test-agent', to: 'target-agent', timestamp: Date.now(), messageType: 'request' }; // Should either succeed with retry or fail gracefully try { const response = await transportLayer.sendMessage(connection.id, message); expect(response.id).toBe('interrupted-001'); } catch (error: any) { expect(error.message).toContain('network'); } }); }); describe('Performance Metrics and Monitoring', () => { it('should track transport performance metrics', async () => { const connection = await transportLayer.connect('metrics-agent', mockConfigs[0]); const message: A2AMessage = { jsonrpc: '2.0', method: 'test.metrics', params: {}, id: 'metrics-001', from: 'metrics-agent', to: 'target-agent', timestamp: Date.now(), messageType: 'request' }; await transportLayer.sendMessage(connection.id, message); const metrics = transportLayer.getTransportMetrics(); expect(metrics.totalMessages).toBeGreaterThanOrEqual(1); expect(metrics.avgLatency).toBeGreaterThan(0); expect(metrics.successRate).toBeGreaterThan(0); }); it('should track protocol-specific metrics', async () => { // Create connections for different protocols await Promise.all([ transportLayer.connect('ws-metrics', mockConfigs[0]), // WebSocket transportLayer.connect('http-metrics', mockConfigs[1]), // HTTP transportLayer.connect('grpc-metrics', mockConfigs[2]) // gRPC ]); const metrics = transportLayer.getTransportMetrics(); expect(metrics.protocolMetrics).toHaveProperty('websocket'); expect(metrics.protocolMetrics).toHaveProperty('http'); expect(metrics.protocolMetrics).toHaveProperty('grpc'); }); it('should provide detailed connection statistics', () => { const metrics = transportLayer.getTransportMetrics(); expect(metrics).toHaveProperty('totalConnections'); expect(metrics).toHaveProperty('activeConnections'); expect(metrics).toHaveProperty('totalMessages'); expect(metrics).toHaveProperty('avgLatency'); expect(metrics).toHaveProperty('successRate'); expect(metrics).toHaveProperty('errorRate'); expect(metrics).toHaveProperty('protocolMetrics'); expect(metrics).toHaveProperty('connectionPoolUtilization'); }); it('should track bandwidth usage', async () => { const connection = await transportLayer.connect('bandwidth-agent', mockConfigs[0]); const largeMessage: A2AMessage = { jsonrpc: '2.0', method: 'test.bandwidth', params: { largeData: 'x'.repeat(50000) // 50KB payload }, id: 'bandwidth-001', from: 'bandwidth-agent', to: 'target-agent', timestamp: Date.now(), messageType: 'request' }; await transportLayer.sendMessage(connection.id, largeMessage); const metrics = transportLayer.getTransportMetrics(); expect(metrics.totalBytesTransferred).toBeGreaterThan(50000); expect(metrics.avgMessageSize).toBeGreaterThan(0); }); }); });