localgreenchain/__tests__/unit/agents/AgentOrchestrator.test.ts
Claude 78b208b42a
feat: add comprehensive testing and CI/CD infrastructure (agent 5)
- Add GitHub Actions CI workflow with lint, type-check, test, build, and e2e jobs
- Configure Jest for unit and integration tests with coverage reporting
- Create unit tests for BaseAgent, PlantLineageAgent, and AgentOrchestrator
- Add blockchain PlantChain unit tests
- Create API integration tests for plants endpoints
- Configure Cypress for E2E testing with support files and custom commands
- Add E2E tests for home, plant registration, transparency, and vertical farm pages
- Set up Prettier for code formatting with configuration
- Configure Husky pre-commit hooks with lint-staged
- Add commitlint for conventional commit message enforcement
- Update package.json with new scripts and dev dependencies

This implements Agent 5 (Testing & CI/CD) from the deployment plan.
2025-11-23 03:50:36 +00:00

317 lines
10 KiB
TypeScript

/**
* AgentOrchestrator Tests
* Tests for the agent orchestration system
*/
import { AgentOrchestrator, getOrchestrator } from '../../../lib/agents/AgentOrchestrator';
// Mock all agents
jest.mock('../../../lib/agents/PlantLineageAgent', () => ({
PlantLineageAgent: jest.fn().mockImplementation(() => ({
config: { id: 'plant-lineage-agent', name: 'Plant Lineage Agent', priority: 'high' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'plant-lineage-agent',
tasksCompleted: 5,
tasksFailed: 0,
averageExecutionMs: 100,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getPlantLineageAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/TransportTrackerAgent', () => ({
TransportTrackerAgent: jest.fn().mockImplementation(() => ({
config: { id: 'transport-tracker-agent', name: 'Transport Tracker Agent', priority: 'high' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'transport-tracker-agent',
tasksCompleted: 3,
tasksFailed: 1,
averageExecutionMs: 150,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getTransportTrackerAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/DemandForecastAgent', () => ({
DemandForecastAgent: jest.fn().mockImplementation(() => ({
config: { id: 'demand-forecast-agent', name: 'Demand Forecast Agent', priority: 'high' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'demand-forecast-agent',
tasksCompleted: 2,
tasksFailed: 0,
averageExecutionMs: 200,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getDemandForecastAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/VerticalFarmAgent', () => ({
VerticalFarmAgent: jest.fn().mockImplementation(() => ({
config: { id: 'vertical-farm-agent', name: 'Vertical Farm Agent', priority: 'critical' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'vertical-farm-agent',
tasksCompleted: 10,
tasksFailed: 0,
averageExecutionMs: 50,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getVerticalFarmAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/EnvironmentAnalysisAgent', () => ({
EnvironmentAnalysisAgent: jest.fn().mockImplementation(() => ({
config: { id: 'environment-analysis-agent', name: 'Environment Analysis Agent', priority: 'medium' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'environment-analysis-agent',
tasksCompleted: 1,
tasksFailed: 0,
averageExecutionMs: 300,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getEnvironmentAnalysisAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/MarketMatchingAgent', () => ({
MarketMatchingAgent: jest.fn().mockImplementation(() => ({
config: { id: 'market-matching-agent', name: 'Market Matching Agent', priority: 'high' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'market-matching-agent',
tasksCompleted: 4,
tasksFailed: 0,
averageExecutionMs: 120,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getMarketMatchingAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/SustainabilityAgent', () => ({
SustainabilityAgent: jest.fn().mockImplementation(() => ({
config: { id: 'sustainability-agent', name: 'Sustainability Agent', priority: 'medium' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'sustainability-agent',
tasksCompleted: 1,
tasksFailed: 0,
averageExecutionMs: 400,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getSustainabilityAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/NetworkDiscoveryAgent', () => ({
NetworkDiscoveryAgent: jest.fn().mockImplementation(() => ({
config: { id: 'network-discovery-agent', name: 'Network Discovery Agent', priority: 'medium' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'network-discovery-agent',
tasksCompleted: 1,
tasksFailed: 0,
averageExecutionMs: 500,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getNetworkDiscoveryAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/QualityAssuranceAgent', () => ({
QualityAssuranceAgent: jest.fn().mockImplementation(() => ({
config: { id: 'quality-assurance-agent', name: 'Quality Assurance Agent', priority: 'critical' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'quality-assurance-agent',
tasksCompleted: 8,
tasksFailed: 0,
averageExecutionMs: 80,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getQualityAssuranceAgent: jest.fn(),
}));
jest.mock('../../../lib/agents/GrowerAdvisoryAgent', () => ({
GrowerAdvisoryAgent: jest.fn().mockImplementation(() => ({
config: { id: 'grower-advisory-agent', name: 'Grower Advisory Agent', priority: 'high' },
status: 'idle',
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
getMetrics: jest.fn().mockReturnValue({
agentId: 'grower-advisory-agent',
tasksCompleted: 6,
tasksFailed: 1,
averageExecutionMs: 180,
errors: [],
}),
getAlerts: jest.fn().mockReturnValue([]),
})),
getGrowerAdvisoryAgent: jest.fn(),
}));
describe('AgentOrchestrator', () => {
let orchestrator: AgentOrchestrator;
beforeEach(() => {
// Reset singleton
(globalThis as any).__orchestratorInstance = undefined;
orchestrator = new AgentOrchestrator();
});
afterEach(async () => {
const status = orchestrator.getStatus();
if (status.isRunning) {
await orchestrator.stopAll();
}
});
describe('Initialization', () => {
it('should initialize with all 10 agents', () => {
const status = orchestrator.getStatus();
expect(status.totalAgents).toBe(10);
});
it('should not be running initially', () => {
const status = orchestrator.getStatus();
expect(status.isRunning).toBe(false);
});
it('should have no running agents initially', () => {
const status = orchestrator.getStatus();
expect(status.runningAgents).toBe(0);
});
});
describe('Starting Agents', () => {
it('should start all agents', async () => {
await orchestrator.startAll();
const status = orchestrator.getStatus();
expect(status.isRunning).toBe(true);
});
it('should start individual agent', async () => {
await orchestrator.startAgent('plant-lineage-agent');
const health = orchestrator.getAgentHealth('plant-lineage-agent');
expect(health).toBeDefined();
});
});
describe('Stopping Agents', () => {
it('should stop all agents', async () => {
await orchestrator.startAll();
await orchestrator.stopAll();
const status = orchestrator.getStatus();
expect(status.isRunning).toBe(false);
});
it('should stop individual agent', async () => {
await orchestrator.startAgent('plant-lineage-agent');
await orchestrator.stopAgent('plant-lineage-agent');
// Agent should be stopped
});
});
describe('Agent Health', () => {
it('should return health for existing agent', async () => {
await orchestrator.startAll();
const health = orchestrator.getAgentHealth('plant-lineage-agent');
expect(health).toHaveProperty('agentId');
expect(health?.agentId).toBe('plant-lineage-agent');
});
it('should return undefined for non-existent agent', () => {
const health = orchestrator.getAgentHealth('non-existent-agent');
expect(health).toBeUndefined();
});
});
describe('Status', () => {
it('should return correct status structure', () => {
const status = orchestrator.getStatus();
expect(status).toHaveProperty('isRunning');
expect(status).toHaveProperty('totalAgents');
expect(status).toHaveProperty('runningAgents');
expect(status).toHaveProperty('healthyAgents');
});
it('should track uptime when running', async () => {
await orchestrator.startAll();
await new Promise((resolve) => setTimeout(resolve, 50));
const status = orchestrator.getStatus();
expect(status.uptime).toBeGreaterThan(0);
});
});
describe('Dashboard', () => {
it('should return dashboard data', async () => {
await orchestrator.startAll();
const dashboard = orchestrator.getDashboard();
expect(dashboard).toHaveProperty('status');
expect(dashboard).toHaveProperty('agents');
expect(dashboard).toHaveProperty('recentAlerts');
});
it('should include all agents in dashboard', async () => {
await orchestrator.startAll();
const dashboard = orchestrator.getDashboard();
expect(dashboard.agents.length).toBe(10);
});
});
describe('Alerts', () => {
it('should return empty alerts initially', () => {
const alerts = orchestrator.getAlerts();
expect(alerts).toEqual([]);
});
it('should filter alerts by severity', () => {
const criticalAlerts = orchestrator.getAlerts('critical');
expect(Array.isArray(criticalAlerts)).toBe(true);
});
});
describe('Singleton', () => {
it('should return same instance from getOrchestrator', () => {
const orch1 = getOrchestrator();
const orch2 = getOrchestrator();
expect(orch1).toBe(orch2);
});
});
});