- 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.
317 lines
10 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|