@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.
836 lines (758 loc) • 25.9 kB
text/typescript
/**
* A2A Message Router Tests
*
* Comprehensive test suite for A2AMessageRouter with intelligent routing logic
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import {
A2AMessage,
AgentCard,
AgentId,
RoutingStrategy,
MessageRoute,
MessagePriority,
A2AErrorType,
TransportProtocol
} from '../../../src/types/a2a';
// Mock implementation will be created later
class A2AMessageRouter {
constructor() {}
async initialize(): Promise<void> {}
async shutdown(): Promise<void> {}
async routeMessage(message: A2AMessage): Promise<MessageRoute> { return {} as MessageRoute; }
async registerAgent(agentCard: AgentCard): Promise<void> {}
async unregisterAgent(agentId: AgentId): Promise<void> {}
async findRoute(from: AgentId, to: AgentId | AgentId[], strategy?: RoutingStrategy): Promise<MessageRoute> { return {} as MessageRoute; }
async updateAgentMetrics(agentId: AgentId, metrics: any): Promise<void> {}
getRoutingTable(): Map<AgentId, AgentCard> { return new Map(); }
getRoutingMetrics(): any { return {}; }
}
describe('A2AMessageRouter', () => {
let messageRouter: A2AMessageRouter;
let mockAgents: AgentCard[];
beforeEach(async () => {
messageRouter = new A2AMessageRouter();
// Create mock agent network
mockAgents = [
{
id: 'coordinator-001',
name: 'Task Coordinator',
description: 'Central task coordination agent',
version: '1.0.0',
capabilities: [
{
name: 'task-coordination',
version: '1.0.0',
description: 'Coordinate complex multi-agent tasks'
},
{
name: 'resource-management',
version: '1.0.0',
description: 'Manage computational resources'
}
],
services: [
{
name: 'coordinateTask',
method: 'task.coordinate',
description: 'Coordinate a multi-step task',
cost: 10,
latency: 50,
reliability: 0.99
}
],
endpoints: [
{
protocol: 'websocket',
address: 'coordinator.local',
port: 8080,
secure: true,
maxConnections: 200
}
],
metadata: {
type: 'coordinator',
status: 'idle',
load: 0.20,
created: Date.now() - 3600000,
lastSeen: Date.now() - 500,
metrics: {
responseTime: { avg: 45, p50: 40, p95: 80, p99: 150 },
requestsPerSecond: 25.0,
messagesProcessed: 5420,
cpuUsage: 0.30,
memoryUsage: 0.45,
networkUsage: 2048000,
successRate: 0.99,
errorRate: 0.01,
uptime: 99.8
},
trustLevel: 'trusted'
}
},
{
id: 'researcher-001',
name: 'Research Agent',
description: 'Specialized research and analysis agent',
version: '1.2.0',
capabilities: [
{
name: 'web-research',
version: '1.2.0',
description: 'Advanced web research capabilities'
},
{
name: 'data-analysis',
version: '2.0.0',
description: 'Statistical and qualitative data analysis'
}
],
services: [
{
name: 'researchTopic',
method: 'research.topic',
description: 'Conduct comprehensive topic research',
cost: 15,
latency: 2000,
reliability: 0.95
}
],
endpoints: [
{
protocol: 'http',
address: 'researcher.local',
port: 8081,
secure: true
}
],
metadata: {
type: 'researcher',
status: 'busy',
load: 0.75,
created: Date.now() - 7200000,
lastSeen: Date.now() - 100,
metrics: {
responseTime: { avg: 1800, p50: 1500, p95: 3000, p99: 5000 },
requestsPerSecond: 2.5,
messagesProcessed: 892,
cpuUsage: 0.80,
memoryUsage: 0.65,
networkUsage: 5120000,
successRate: 0.95,
errorRate: 0.05,
uptime: 98.5
},
trustLevel: 'verified'
}
},
{
id: 'coder-001',
name: 'Code Generation Agent',
description: 'Advanced code generation and refactoring agent',
version: '2.1.0',
capabilities: [
{
name: 'code-generation',
version: '2.1.0',
description: 'Generate high-quality code in multiple languages'
},
{
name: 'code-analysis',
version: '1.5.0',
description: 'Static and dynamic code analysis'
}
],
services: [
{
name: 'generateCode',
method: 'code.generate',
description: 'Generate code from specifications',
cost: 20,
latency: 1500,
reliability: 0.97
}
],
endpoints: [
{
protocol: 'grpc',
address: 'coder.local',
port: 9090,
secure: true
}
],
metadata: {
type: 'coder',
status: 'idle',
load: 0.10,
created: Date.now() - 1800000,
lastSeen: Date.now() - 200,
metrics: {
responseTime: { avg: 1400, p50: 1200, p95: 2500, p99: 4000 },
requestsPerSecond: 1.8,
messagesProcessed: 324,
cpuUsage: 0.15,
memoryUsage: 0.35,
networkUsage: 1536000,
successRate: 0.97,
errorRate: 0.03,
uptime: 99.2
},
trustLevel: 'trusted'
}
},
{
id: 'analyzer-001',
name: 'Data Analysis Agent',
description: 'Specialized data analysis and visualization agent',
version: '1.8.0',
capabilities: [
{
name: 'statistical-analysis',
version: '1.8.0',
description: 'Advanced statistical analysis capabilities'
},
{
name: 'data-visualization',
version: '1.0.0',
description: 'Create charts and visualizations'
}
],
services: [
{
name: 'analyzeData',
method: 'data.analyze',
description: 'Perform statistical analysis on datasets',
cost: 12,
latency: 800,
reliability: 0.98
}
],
endpoints: [
{
protocol: 'websocket',
address: 'analyzer.local',
port: 8082,
secure: false
}
],
metadata: {
type: 'analyst',
status: 'idle',
load: 0.30,
created: Date.now() - 5400000,
lastSeen: Date.now() - 300,
metrics: {
responseTime: { avg: 750, p50: 600, p95: 1200, p99: 2000 },
requestsPerSecond: 8.5,
messagesProcessed: 2108,
cpuUsage: 0.35,
memoryUsage: 0.50,
networkUsage: 3072000,
successRate: 0.98,
errorRate: 0.02,
uptime: 99.5
},
trustLevel: 'verified'
}
}
];
await messageRouter.initialize();
// Register all mock agents
for (const agent of mockAgents) {
await messageRouter.registerAgent(agent);
}
});
afterEach(async () => {
await messageRouter.shutdown();
jest.clearAllMocks();
});
describe('Agent Registration and Discovery', () => {
it('should register agents in routing table', async () => {
const newAgent: AgentCard = {
id: 'test-agent-002',
name: 'Test Agent 2',
description: 'Another test agent',
version: '1.0.0',
capabilities: [],
services: [],
endpoints: [{
protocol: 'http',
address: 'localhost',
port: 8083
}],
metadata: {
type: 'specialist',
status: 'idle',
load: 0.0,
created: Date.now(),
lastSeen: Date.now()
}
};
await expect(messageRouter.registerAgent(newAgent)).resolves.not.toThrow();
const routingTable = messageRouter.getRoutingTable();
expect(routingTable.has('test-agent-002')).toBe(true);
expect(routingTable.get('test-agent-002')?.name).toBe('Test Agent 2');
});
it('should unregister agents from routing table', async () => {
await messageRouter.unregisterAgent('researcher-001');
const routingTable = messageRouter.getRoutingTable();
expect(routingTable.has('researcher-001')).toBe(false);
});
it('should update agent metrics', async () => {
const newMetrics = {
responseTime: { avg: 100, p50: 80, p95: 150, p99: 250 },
load: 0.45,
status: 'busy'
};
await expect(messageRouter.updateAgentMetrics('coordinator-001', newMetrics))
.resolves.not.toThrow();
});
it('should handle duplicate agent registration', async () => {
const duplicateAgent = { ...mockAgents[0] };
await expect(messageRouter.registerAgent(duplicateAgent))
.rejects.toThrow('Agent already registered: coordinator-001');
});
});
describe('Direct Routing Strategy', () => {
it('should find direct route to single agent', async () => {
const route = await messageRouter.findRoute(
'coordinator-001',
'researcher-001',
'direct'
);
expect(route.strategy).toBe('direct');
expect(route.path).toEqual(['coordinator-001', 'researcher-001']);
expect(route.hops).toBe(1);
});
it('should route direct message successfully', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'research.topic',
params: { topic: 'artificial intelligence' },
id: 'direct-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request',
priority: 'normal'
};
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('direct');
expect(route.path).toContain('researcher-001');
});
it('should handle direct routing to non-existent agent', async () => {
await expect(messageRouter.findRoute('coordinator-001', 'non-existent', 'direct'))
.rejects.toMatchObject({
type: 'agent_unavailable',
message: 'Target agent not found: non-existent'
});
});
});
describe('Load Balanced Routing Strategy', () => {
it('should select least loaded agent with required capability', async () => {
// Add another coder agent with higher load
const busyCoderAgent: AgentCard = {
...mockAgents.find(a => a.id === 'coder-001')!,
id: 'coder-002',
name: 'Busy Coder Agent',
metadata: {
...mockAgents.find(a => a.id === 'coder-001')!.metadata,
load: 0.90, // Much higher load
status: 'busy'
}
};
await messageRouter.registerAgent(busyCoderAgent);
const route = await messageRouter.findRoute(
'coordinator-001',
['coder-001', 'coder-002'],
'load_balanced'
);
expect(route.strategy).toBe('load_balanced');
// Should prefer coder-001 (load: 0.10) over coder-002 (load: 0.90)
expect(route.path).toContain('coder-001');
});
it('should handle load balancing with all agents busy', async () => {
// Update all agents to be busy
for (const agent of mockAgents) {
await messageRouter.updateAgentMetrics(agent.id, {
load: 0.95,
status: 'overloaded'
});
}
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'any.service',
params: {},
id: 'overload-001',
from: 'coordinator-001',
to: ['researcher-001', 'coder-001', 'analyzer-001'],
timestamp: Date.now(),
messageType: 'request',
priority: 'normal'
};
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('load_balanced');
// Should still route to least loaded available agent
expect(route.path.length).toBeGreaterThan(1);
});
});
describe('Capability-Aware Routing Strategy', () => {
it('should route to agent with specific capability', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'research.topic',
params: { topic: 'machine learning' },
id: 'capability-001',
from: 'coordinator-001',
to: 'broadcast', // Let router decide based on capabilities
timestamp: Date.now(),
messageType: 'request',
capabilities: [
{
name: 'web-research',
version: '1.0.0',
description: 'Requires web research capability'
}
]
};
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('capability_aware');
expect(route.path).toContain('researcher-001'); // Has web-research capability
});
it('should route to best matching agent based on capability versions', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'data.analyze',
params: { dataset: 'large-dataset.csv' },
id: 'version-match-001',
from: 'coordinator-001',
to: 'broadcast',
timestamp: Date.now(),
messageType: 'request',
capabilities: [
{
name: 'data-analysis',
version: '2.0.0', // Exact version match
description: 'Requires advanced data analysis'
}
]
};
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('capability_aware');
// researcher-001 has data-analysis v2.0.0, analyzer-001 might have older version
expect(route.path).toContain('researcher-001');
});
it('should handle no agents with required capability', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'quantum.compute',
params: {},
id: 'no-capability-001',
from: 'coordinator-001',
to: 'broadcast',
timestamp: Date.now(),
messageType: 'request',
capabilities: [
{
name: 'quantum-computing',
version: '1.0.0',
description: 'Requires quantum computing capability'
}
]
};
await expect(messageRouter.routeMessage(message))
.rejects.toMatchObject({
type: 'capability_not_found',
message: 'No agents found with required capability: quantum-computing'
});
});
});
describe('Cost-Optimized Routing Strategy', () => {
it('should select lowest cost agent for service', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'analyze.data',
params: { data: 'sample-data' },
id: 'cost-001',
from: 'coordinator-001',
to: ['researcher-001', 'analyzer-001'], // Both can analyze data
timestamp: Date.now(),
messageType: 'request',
context: {
maxCost: 15 // Prefer cheaper option
}
};
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('cost_optimized');
// analyzer-001 has lower cost (12) vs researcher-001 (15)
expect(route.path).toContain('analyzer-001');
});
it('should respect maximum cost constraints', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'generate.code',
params: { specification: 'simple function' },
id: 'cost-limit-001',
from: 'coordinator-001',
to: 'coder-001',
timestamp: Date.now(),
messageType: 'request',
context: {
maxCost: 10 // coder-001 costs 20, should reject
}
};
await expect(messageRouter.routeMessage(message))
.rejects.toMatchObject({
type: 'resource_exhausted',
message: 'Service cost (20) exceeds maximum allowed cost (10)'
});
});
});
describe('Shortest Path Routing Strategy', () => {
it('should find shortest path in multi-hop network', async () => {
// Create a network topology where some agents are not directly connected
const route = await messageRouter.findRoute(
'coordinator-001',
'analyzer-001',
'shortest_path'
);
expect(route.strategy).toBe('shortest_path');
expect(route.hops).toBeGreaterThanOrEqual(1);
expect(route.path[0]).toBe('coordinator-001');
expect(route.path[route.path.length - 1]).toBe('analyzer-001');
});
it('should handle unreachable agents', async () => {
// Simulate disconnected agent
await messageRouter.unregisterAgent('analyzer-001');
await expect(messageRouter.findRoute(
'coordinator-001',
'analyzer-001',
'shortest_path'
)).rejects.toMatchObject({
type: 'routing_error',
message: 'No path found to agent: analyzer-001'
});
});
it('should respect maximum hop limit', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'distant.service',
params: {},
id: 'max-hops-001',
from: 'coordinator-001',
to: 'analyzer-001',
timestamp: Date.now(),
messageType: 'request',
route: {
path: [],
hops: 0,
maxHops: 1, // Very restrictive
strategy: 'shortest_path'
}
};
const route = await messageRouter.routeMessage(message);
expect(route.hops).toBeLessThanOrEqual(1);
});
});
describe('Priority-Based Message Handling', () => {
it('should prioritize critical messages', async () => {
const normalMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'normal.task',
params: {},
id: 'normal-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request',
priority: 'normal'
};
const criticalMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'critical.task',
params: {},
id: 'critical-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request',
priority: 'critical'
};
// Both should route successfully, but critical should be prioritized
const [normalRoute, criticalRoute] = await Promise.all([
messageRouter.routeMessage(normalMessage),
messageRouter.routeMessage(criticalMessage)
]);
expect(normalRoute.path).toBeDefined();
expect(criticalRoute.path).toBeDefined();
});
it('should queue low priority messages when system is busy', async () => {
const lowPriorityMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'background.task',
params: {},
id: 'low-priority-001',
from: 'coordinator-001',
to: 'researcher-001', // Already busy (load: 0.75)
timestamp: Date.now(),
messageType: 'request',
priority: 'low'
};
const route = await messageRouter.routeMessage(lowPriorityMessage);
expect(route.path).toBeDefined();
});
});
describe('Broadcast and Multicast Routing', () => {
it('should handle broadcast messages to all agents', async () => {
const broadcastMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'system.announcement',
params: { message: 'System maintenance scheduled' },
id: 'broadcast-001',
from: 'coordinator-001',
to: 'broadcast',
timestamp: Date.now(),
messageType: 'notification'
};
const route = await messageRouter.routeMessage(broadcastMessage);
expect(route.path.length).toBeGreaterThan(2); // Should include multiple agents
expect(route.path).toContain('researcher-001');
expect(route.path).toContain('coder-001');
expect(route.path).toContain('analyzer-001');
});
it('should handle multicast to specific agent list', async () => {
const multicastMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'coordinate.subtask',
params: { subtaskId: 'subtask-001' },
id: 'multicast-001',
from: 'coordinator-001',
to: ['researcher-001', 'coder-001'],
timestamp: Date.now(),
messageType: 'request'
};
const route = await messageRouter.routeMessage(multicastMessage);
expect(route.path).toContain('researcher-001');
expect(route.path).toContain('coder-001');
expect(route.path).not.toContain('analyzer-001'); // Not in target list
});
it('should handle partial failures in multicast', async () => {
// Remove one target agent to simulate failure
await messageRouter.unregisterAgent('coder-001');
const multicastMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'coordinate.subtask',
params: { subtaskId: 'subtask-002' },
id: 'multicast-failure-001',
from: 'coordinator-001',
to: ['researcher-001', 'coder-001', 'analyzer-001'],
timestamp: Date.now(),
messageType: 'request'
};
const route = await messageRouter.routeMessage(multicastMessage);
// Should route to available agents only
expect(route.path).toContain('researcher-001');
expect(route.path).toContain('analyzer-001');
expect(route.path).not.toContain('coder-001');
});
});
describe('Error Handling and Fallbacks', () => {
it('should handle routing table corruption gracefully', async () => {
// Simulate corrupted routing table by clearing it
const routingTable = messageRouter.getRoutingTable();
routingTable.clear();
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'test.method',
params: {},
id: 'corruption-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request'
};
await expect(messageRouter.routeMessage(message))
.rejects.toMatchObject({
type: 'routing_error',
message: 'Routing table is empty or corrupted'
});
});
it('should provide fallback routing when primary strategy fails', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'fallback.test',
params: {},
id: 'fallback-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request',
route: {
path: [],
hops: 0,
strategy: 'capability_aware' // This will fail, should fallback to direct
},
capabilities: [
{
name: 'non-existent-capability',
version: '1.0.0',
description: 'This capability does not exist'
}
]
};
// Should fallback to direct routing when capability routing fails
const route = await messageRouter.routeMessage(message);
expect(route.strategy).toBe('direct'); // Fallback strategy
expect(route.path).toContain('researcher-001');
});
});
describe('Performance Metrics and Monitoring', () => {
it('should track routing performance metrics', async () => {
const message: A2AMessage = {
jsonrpc: '2.0',
method: 'metrics.test',
params: {},
id: 'metrics-001',
from: 'coordinator-001',
to: 'researcher-001',
timestamp: Date.now(),
messageType: 'request'
};
await messageRouter.routeMessage(message);
const metrics = messageRouter.getRoutingMetrics();
expect(metrics.totalRoutedMessages).toBeGreaterThanOrEqual(1);
expect(metrics.avgRoutingTime).toBeGreaterThan(0);
expect(metrics.routingSuccessRate).toBeGreaterThan(0);
});
it('should track routing failures', async () => {
const failingMessage: A2AMessage = {
jsonrpc: '2.0',
method: 'failing.test',
params: {},
id: 'fail-metrics-001',
from: 'coordinator-001',
to: 'non-existent-agent',
timestamp: Date.now(),
messageType: 'request'
};
try {
await messageRouter.routeMessage(failingMessage);
} catch (error) {
// Expected to fail
}
const metrics = messageRouter.getRoutingMetrics();
expect(metrics.routingErrorRate).toBeGreaterThan(0);
expect(metrics.routingFailuresByType).toHaveProperty('agent_unavailable');
});
it('should provide detailed routing statistics', () => {
const metrics = messageRouter.getRoutingMetrics();
expect(metrics).toHaveProperty('totalRoutedMessages');
expect(metrics).toHaveProperty('avgRoutingTime');
expect(metrics).toHaveProperty('routingSuccessRate');
expect(metrics).toHaveProperty('routingErrorRate');
expect(metrics).toHaveProperty('strategiesUsed');
expect(metrics).toHaveProperty('hopDistribution');
expect(metrics).toHaveProperty('agentLoadDistribution');
});
});
});