@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.
629 lines (524 loc) • 20.7 kB
JavaScript
/**
* A2A Transport Layer Production Validation Tests
*
* Comprehensive validation of the Agent-to-Agent transport layer with real connection
* scenarios, error handling, retry logic, and performance under load
*/
import { A2ATransportLayer } from '../../src/protocols/a2a/core/a2a-transport-layer.js';
import { Logger } from '../../src/utils/logger.js';
describe('A2A Transport Layer Production Validation', () => {
let transportLayer;
let logger;
const mockTransportConfigs = [
{
protocol: 'websocket',
host: 'localhost',
port: 8080,
secure: false,
timeout: 5000,
auth: { type: 'token', credentials: { token: 'valid-token' } }
},
{
protocol: 'http',
host: 'api.example.com',
port: 443,
secure: true,
timeout: 10000,
auth: { type: 'oauth2' },
tls: { rejectUnauthorized: true }
},
{
protocol: 'grpc',
host: 'grpc.example.com',
port: 9090,
secure: true,
timeout: 15000,
auth: { type: 'certificate' }
},
{
protocol: 'tcp',
host: '10.0.0.1',
port: 9999,
keepAlive: true,
timeout: 30000
}
];
beforeAll(() => {
logger = new Logger('A2ATransportValidationTest');
});
beforeEach(async () => {
transportLayer = new A2ATransportLayer();
});
afterEach(async () => {
if (transportLayer) {
await transportLayer.shutdown();
}
});
describe('Transport Layer Initialization', () => {
it('should initialize with valid transport configurations', async () => {
const initPromise = transportLayer.initialize(mockTransportConfigs);
await expect(initPromise).resolves.not.toThrow();
// Verify supported protocols
expect(transportLayer.isProtocolSupported('websocket')).toBe(true);
expect(transportLayer.isProtocolSupported('http')).toBe(true);
expect(transportLayer.isProtocolSupported('grpc')).toBe(true);
expect(transportLayer.isProtocolSupported('tcp')).toBe(true);
expect(transportLayer.isProtocolSupported('invalid')).toBe(false);
});
it('should reject invalid transport configurations', async () => {
const invalidConfigs = [
{ protocol: 'invalid-protocol', host: 'localhost' },
{ protocol: 'websocket', host: '' },
{ protocol: 'http', host: 'localhost', port: 70000 }
];
await expect(transportLayer.initialize(invalidConfigs))
.rejects.toThrow();
});
it('should emit initialization event on successful setup', async () => {
let initializeEventEmitted = false;
transportLayer.on('initialized', () => {
initializeEventEmitted = true;
});
await transportLayer.initialize(mockTransportConfigs);
expect(initializeEventEmitted).toBe(true);
});
});
describe('Connection Management', () => {
beforeEach(async () => {
await transportLayer.initialize(mockTransportConfigs);
});
it('should establish WebSocket connections successfully', async () => {
const agentId = 'test-agent-ws';
const config = mockTransportConfigs[0];
const connection = await transportLayer.connect(agentId, config);
expect(connection).toBeDefined();
expect(connection.id).toBeDefined();
expect(connection.protocol).toBe('websocket');
expect(connection.agentId).toBe(agentId);
expect(connection.isConnected).toBe(true);
expect(connection.connectionTime).toBeGreaterThan(0);
});
it('should establish HTTP connections with TLS', async () => {
const agentId = 'test-agent-http';
const config = mockTransportConfigs[1];
const connection = await transportLayer.connect(agentId, config);
expect(connection).toBeDefined();
expect(connection.protocol).toBe('http');
expect(connection.config.secure).toBe(true);
expect(connection.config.tls).toBeDefined();
});
it('should establish gRPC connections with authentication', async () => {
const agentId = 'test-agent-grpc';
const config = mockTransportConfigs[2];
const connection = await transportLayer.connect(agentId, config);
expect(connection).toBeDefined();
expect(connection.protocol).toBe('grpc');
expect(connection.config.auth?.type).toBe('certificate');
});
it('should establish TCP connections with keepalive', async () => {
const agentId = 'test-agent-tcp';
const config = mockTransportConfigs[3];
const connection = await transportLayer.connect(agentId, config);
expect(connection).toBeDefined();
expect(connection.protocol).toBe('tcp');
expect(connection.config.keepAlive).toBe(true);
});
it('should handle connection authentication failures', async () => {
const agentId = 'test-agent-auth-fail';
const config = {
...mockTransportConfigs[0],
auth: { type: 'token', credentials: { token: 'invalid-token' } }
};
await expect(transportLayer.connect(agentId, config))
.rejects.toThrow(/authentication failed/i);
});
it('should enforce connection pool limits', async () => {
const agentId = 'test-agent-pool';
const config = mockTransportConfigs[0];
const connections = [];
// Try to create many connections for the same agent
for (let i = 0; i < 10; i++) {
try {
const connection = await transportLayer.connect(`${agentId}-${i}`, config);
connections.push(connection);
} catch (error) {
// Pool limit may be reached
if (error.message.includes('capacity exceeded')) {
break;
}
}
}
// Should have created some connections but hit limits
expect(connections.length).toBeGreaterThan(0);
expect(connections.length).toBeLessThan(10);
});
it('should emit connection events', async () => {
let connectionEstablished = false;
let connectionClosed = false;
transportLayer.on('connectionEstablished', () => {
connectionEstablished = true;
});
transportLayer.on('connectionClosed', () => {
connectionClosed = true;
});
const agentId = 'test-agent-events';
const connection = await transportLayer.connect(agentId, mockTransportConfigs[0]);
expect(connectionEstablished).toBe(true);
await transportLayer.disconnect(connection.id);
expect(connectionClosed).toBe(true);
});
});
describe('Message Transmission', () => {
let connection;
beforeEach(async () => {
await transportLayer.initialize(mockTransportConfigs);
connection = await transportLayer.connect('test-agent', mockTransportConfigs[0]);
});
it('should send messages successfully over WebSocket', async () => {
const message = {
jsonrpc: '2.0',
method: 'test.echo',
params: { data: 'Hello World' },
id: 'test-message-1',
from: 'test-client',
to: 'test-agent',
timestamp: Date.now(),
messageType: 'request'
};
const response = await transportLayer.sendMessage(connection.id, message);
expect(response).toBeDefined();
expect(response.jsonrpc).toBe('2.0');
expect(response.result).toBeDefined();
expect(response.id).toBe(message.id);
expect(response.messageType).toBe('response');
});
it('should send notifications without expecting response', async () => {
const notification = {
jsonrpc: '2.0',
method: 'test.notify',
params: { event: 'status_update' },
from: 'test-client',
to: 'test-agent',
timestamp: Date.now(),
messageType: 'notification'
};
await expect(transportLayer.sendNotification(connection.id, notification))
.resolves.not.toThrow();
// Verify connection stats were updated
const updatedConnection = transportLayer.getActiveConnections().get(connection.id);
expect(updatedConnection?.messagesSent).toBeGreaterThan(0);
});
it('should handle message retry logic on failures', async () => {
// Use HTTP connection which has simulated error rate
const httpConnection = await transportLayer.connect('test-http-agent', mockTransportConfigs[1]);
const message = {
jsonrpc: '2.0',
method: 'test.unreliable',
params: { retry: true },
id: 'retry-test-1',
from: 'test-client',
to: 'test-http-agent',
timestamp: Date.now(),
messageType: 'request'
};
// Multiple attempts to trigger retry logic
let successCount = 0;
let errorCount = 0;
for (let i = 0; i < 20; i++) {
try {
await transportLayer.sendMessage(httpConnection.id, {
...message,
id: `retry-test-${i}`
});
successCount++;
} catch (error) {
errorCount++;
}
}
// Should have some successes and some errors due to simulated 5% error rate
expect(successCount).toBeGreaterThan(0);
// May have errors due to simulated network issues
const totalAttempts = successCount + errorCount;
expect(totalAttempts).toBe(20);
});
it('should handle message timeouts', async () => {
// Create connection with very short timeout
const shortTimeoutConfig = {
...mockTransportConfigs[0],
timeout: 1 // 1ms timeout - will definitely timeout
};
const timeoutConnection = await transportLayer.connect('timeout-agent', shortTimeoutConfig);
const message = {
jsonrpc: '2.0',
method: 'test.slow',
params: { delay: 1000 },
id: 'timeout-test',
from: 'test-client',
to: 'timeout-agent',
timestamp: Date.now(),
messageType: 'request'
};
await expect(transportLayer.sendMessage(timeoutConnection.id, message))
.rejects.toThrow(/timeout/i);
});
it('should broadcast messages to multiple connections', async () => {
// Create multiple connections
const connections = [];
for (let i = 0; i < 3; i++) {
const conn = await transportLayer.connect(`agent-${i}`, mockTransportConfigs[0]);
connections.push(conn);
}
const broadcastMessage = {
jsonrpc: '2.0',
method: 'test.broadcast',
params: { announcement: 'Hello everyone' },
id: 'broadcast-test',
from: 'test-broadcaster',
to: 'all',
timestamp: Date.now(),
messageType: 'request'
};
const responses = await transportLayer.broadcastMessage(broadcastMessage);
expect(responses.length).toBeGreaterThan(0);
expect(responses.length).toBeLessThanOrEqual(connections.length + 1); // +1 for original connection
responses.forEach(response => {
expect(response.jsonrpc).toBe('2.0');
expect(response.result).toBeDefined();
});
});
it('should handle disconnected connections gracefully', async () => {
// Disconnect the connection
await transportLayer.disconnect(connection.id);
const message = {
jsonrpc: '2.0',
method: 'test.afterDisconnect',
params: {},
id: 'disconnected-test',
from: 'test-client',
to: 'test-agent',
timestamp: Date.now(),
messageType: 'request'
};
await expect(transportLayer.sendMessage(connection.id, message))
.rejects.toThrow(/not active/i);
});
});
describe('Performance and Metrics', () => {
beforeEach(async () => {
await transportLayer.initialize(mockTransportConfigs);
});
it('should track transport metrics accurately', async () => {
// Create connections and send messages
const connections = [];
for (let i = 0; i < 3; i++) {
const conn = await transportLayer.connect(`perf-agent-${i}`, mockTransportConfigs[0]);
connections.push(conn);
}
// Send multiple messages
for (let i = 0; i < 10; i++) {
const message = {
jsonrpc: '2.0',
method: 'test.performance',
params: { iteration: i },
id: `perf-test-${i}`,
from: 'perf-client',
to: 'perf-agent-0',
timestamp: Date.now(),
messageType: 'request'
};
try {
await transportLayer.sendMessage(connections[0].id, message);
} catch (error) {
// Some may fail, that's expected for testing
}
}
const metrics = transportLayer.getTransportMetrics();
expect(metrics.totalConnections).toBeGreaterThan(0);
expect(metrics.activeConnections).toBeGreaterThan(0);
expect(metrics.totalMessages).toBeGreaterThan(0);
expect(metrics.avgLatency).toBeGreaterThan(0);
expect(metrics.successRate).toBeGreaterThanOrEqual(0);
expect(metrics.successRate).toBeLessThanOrEqual(1);
expect(metrics.totalBytesTransferred).toBeGreaterThan(0);
expect(metrics.protocolMetrics).toBeDefined();
expect(metrics.protocolMetrics.websocket).toBeDefined();
});
it('should handle high concurrent load', async () => {
const connection = await transportLayer.connect('load-test-agent', mockTransportConfigs[0]);
const concurrentMessages = 50;
const startTime = Date.now();
// Send messages concurrently
const messagePromises = Array.from({ length: concurrentMessages }, (_, i) => {
const message = {
jsonrpc: '2.0',
method: 'test.load',
params: { messageId: i },
id: `load-test-${i}`,
from: 'load-client',
to: 'load-test-agent',
timestamp: Date.now(),
messageType: 'request'
};
return transportLayer.sendMessage(connection.id, message)
.catch(error => ({ error: error.message }));
});
const results = await Promise.allSettled(messagePromises);
const endTime = Date.now();
const duration = endTime - startTime;
// Analyze results
const successful = results.filter(r =>
r.status === 'fulfilled' && !r.value.error
).length;
const failed = results.length - successful;
console.log(`Load test: ${successful}/${concurrentMessages} messages succeeded in ${duration}ms`);
// Performance assertions
expect(duration).toBeLessThan(10000); // Should complete within 10 seconds
expect(successful).toBeGreaterThan(concurrentMessages * 0.8); // At least 80% success
const throughput = successful / (duration / 1000); // messages per second
expect(throughput).toBeGreaterThan(5); // At least 5 messages per second
});
it('should provide protocol-specific metrics', async () => {
// Create connections with different protocols
const wsConnection = await transportLayer.connect('ws-agent', mockTransportConfigs[0]);
const httpConnection = await transportLayer.connect('http-agent', mockTransportConfigs[1]);
// Send messages over both protocols
const wsMessage = {
jsonrpc: '2.0',
method: 'test.websocket',
params: {},
id: 'ws-test',
from: 'test-client',
to: 'ws-agent',
timestamp: Date.now(),
messageType: 'request'
};
const httpMessage = {
jsonrpc: '2.0',
method: 'test.http',
params: {},
id: 'http-test',
from: 'test-client',
to: 'http-agent',
timestamp: Date.now(),
messageType: 'request'
};
await transportLayer.sendMessage(wsConnection.id, wsMessage);
await transportLayer.sendMessage(httpConnection.id, httpMessage);
const metrics = transportLayer.getTransportMetrics();
expect(metrics.protocolMetrics.websocket).toBeDefined();
expect(metrics.protocolMetrics.http).toBeDefined();
expect(metrics.protocolMetrics.websocket.connections).toBeGreaterThan(0);
expect(metrics.protocolMetrics.http.connections).toBeGreaterThan(0);
expect(metrics.protocolMetrics.websocket.messages).toBeGreaterThan(0);
expect(metrics.protocolMetrics.http.messages).toBeGreaterThan(0);
});
});
describe('Error Handling and Recovery', () => {
beforeEach(async () => {
await transportLayer.initialize(mockTransportConfigs);
});
it('should create standardized transport errors', async () => {
const agentId = 'error-test-agent';
// Try to connect with unsupported protocol
const invalidConfig = {
protocol: 'unsupported-protocol',
host: 'localhost',
port: 8080
};
await expect(transportLayer.connect(agentId, invalidConfig))
.rejects.toMatchObject({
type: 'protocol_error',
message: expect.stringContaining('Unsupported protocol')
});
});
it('should handle connection pool exhaustion', async () => {
const connections = [];
const maxConnections = 20; // Try to exceed typical limits
for (let i = 0; i < maxConnections; i++) {
try {
const connection = await transportLayer.connect(`bulk-agent-${i}`, mockTransportConfigs[0]);
connections.push(connection);
} catch (error) {
if (error.message.includes('capacity exceeded')) {
// This is expected behavior
break;
}
}
}
// Should have created some connections before hitting limits
expect(connections.length).toBeGreaterThan(0);
});
it('should clean up stale connections automatically', async () => {
// Create connections
const connection1 = await transportLayer.connect('stale-agent-1', mockTransportConfigs[0]);
const connection2 = await transportLayer.connect('stale-agent-2', mockTransportConfigs[0]);
// Manually mark one as stale
const activeConnections = transportLayer.getActiveConnections();
const conn1 = activeConnections.get(connection1.id);
if (conn1) {
conn1.lastActivity = Date.now() - 500000; // 8+ minutes ago
conn1.isConnected = false;
}
// Wait for cleanup cycle (or trigger manually)
await new Promise(resolve => setTimeout(resolve, 100));
// Verify active connections
const updatedConnections = transportLayer.getActiveConnections();
expect(updatedConnections.has(connection2.id)).toBe(true);
// connection1 may have been cleaned up
});
it('should handle network errors gracefully', async () => {
const connection = await transportLayer.connect('network-error-agent', mockTransportConfigs[1]); // HTTP with error simulation
const message = {
jsonrpc: '2.0',
method: 'test.networkError',
params: {},
id: 'network-error-test',
from: 'test-client',
to: 'network-error-agent',
timestamp: Date.now(),
messageType: 'request'
};
// Try multiple times to potentially trigger network errors
let errorOccurred = false;
for (let i = 0; i < 10; i++) {
try {
await transportLayer.sendMessage(connection.id, {
...message,
id: `network-error-test-${i}`
});
} catch (error) {
if (error.type === 'routing_error') {
errorOccurred = true;
expect(error.message).toContain('HTTP request failed');
break;
}
}
}
// Network errors should be handled with proper error structure
// Note: Due to randomness, this test may not always trigger the error
});
});
describe('Connection Lifecycle', () => {
beforeEach(async () => {
await transportLayer.initialize(mockTransportConfigs);
});
it('should properly shutdown and clean up all resources', async () => {
// Create multiple connections
const connections = [];
for (let i = 0; i < 3; i++) {
const conn = await transportLayer.connect(`shutdown-agent-${i}`, mockTransportConfigs[0]);
connections.push(conn);
}
expect(transportLayer.getActiveConnections().size).toBe(connections.length);
// Shutdown transport layer
await transportLayer.shutdown();
// Verify all connections are cleaned up
expect(transportLayer.getActiveConnections().size).toBe(0);
});
it('should reject operations after shutdown', async () => {
await transportLayer.shutdown();
await expect(transportLayer.connect('post-shutdown-agent', mockTransportConfigs[0]))
.rejects.toThrow('not initialized');
});
});
});