/** * PlantLineageAgent API Tests * Tests for lineage agent API endpoints */ import { PlantLineageAgent, getPlantLineageAgent } from '../../../lib/agents'; import { getBlockchain, initializeBlockchain } from '../../../lib/blockchain/manager'; describe('PlantLineageAgent API', () => { let agent: PlantLineageAgent; beforeEach(() => { // Get fresh agent instance agent = getPlantLineageAgent(); }); afterEach(async () => { // Ensure agent is stopped after each test if (agent.status === 'running') { await agent.stop(); } }); describe('GET /api/agents/lineage', () => { it('should return agent status and configuration', () => { expect(agent.config.id).toBe('plant-lineage-agent'); expect(agent.config.name).toBe('Plant Lineage Agent'); expect(agent.config.description).toBeDefined(); expect(agent.config.priority).toBe('high'); expect(agent.config.intervalMs).toBe(60000); }); it('should return current metrics', () => { const metrics = agent.getMetrics(); expect(metrics.agentId).toBe('plant-lineage-agent'); expect(metrics.tasksCompleted).toBeGreaterThanOrEqual(0); expect(metrics.tasksFailed).toBeGreaterThanOrEqual(0); expect(metrics.averageExecutionMs).toBeGreaterThanOrEqual(0); }); it('should return network statistics', () => { const networkStats = agent.getNetworkStats(); expect(networkStats.totalPlants).toBeGreaterThanOrEqual(0); expect(networkStats.totalLineages).toBeGreaterThanOrEqual(0); expect(networkStats.avgGenerationDepth).toBeGreaterThanOrEqual(0); expect(networkStats.avgLineageSize).toBeGreaterThanOrEqual(0); expect(networkStats.geographicSpread).toBeGreaterThanOrEqual(0); }); it('should return anomaly summary', () => { const anomalies = agent.getAnomalies(); expect(Array.isArray(anomalies)).toBe(true); }); }); describe('POST /api/agents/lineage', () => { it('should start agent successfully', async () => { expect(agent.status).toBe('idle'); await agent.start(); expect(agent.status).toBe('running'); }); it('should stop agent successfully', async () => { await agent.start(); expect(agent.status).toBe('running'); await agent.stop(); expect(agent.status).toBe('idle'); }); it('should pause and resume agent', async () => { await agent.start(); expect(agent.status).toBe('running'); agent.pause(); expect(agent.status).toBe('paused'); agent.resume(); expect(agent.status).toBe('running'); }); it('should handle start when already running', async () => { await agent.start(); const firstStatus = agent.status; await agent.start(); // Should not throw expect(agent.status).toBe(firstStatus); }); }); describe('GET /api/agents/lineage/[plantId]', () => { it('should return null for non-existent plant analysis', () => { const analysis = agent.getLineageAnalysis('non-existent-plant-id'); expect(analysis).toBeNull(); }); it('should return analysis structure when available', () => { // Analysis would be populated after agent runs // For now, test the structure expectations const analysis = agent.getLineageAnalysis('test-plant-id'); // Should return null for non-cached plant expect(analysis).toBeNull(); }); }); describe('GET /api/agents/lineage/anomalies', () => { it('should return empty array when no anomalies', () => { const anomalies = agent.getAnomalies(); expect(Array.isArray(anomalies)).toBe(true); }); it('should support filtering by severity', () => { const allAnomalies = agent.getAnomalies(); const highSeverity = allAnomalies.filter(a => a.severity === 'high'); const mediumSeverity = allAnomalies.filter(a => a.severity === 'medium'); const lowSeverity = allAnomalies.filter(a => a.severity === 'low'); expect(highSeverity.length + mediumSeverity.length + lowSeverity.length).toBe(allAnomalies.length); }); it('should support filtering by type', () => { const allAnomalies = agent.getAnomalies(); const validTypes = ['orphan', 'circular', 'invalid_generation', 'missing_parent', 'suspicious_location']; for (const anomaly of allAnomalies) { expect(validTypes).toContain(anomaly.type); } }); }); describe('Agent Lifecycle', () => { it('should track uptime correctly', async () => { const initialMetrics = agent.getMetrics(); const initialUptime = initialMetrics.uptime; await agent.start(); // Wait a bit await new Promise(resolve => setTimeout(resolve, 100)); const runningMetrics = agent.getMetrics(); expect(runningMetrics.uptime).toBeGreaterThan(initialUptime); }); it('should maintain metrics across start/stop cycles', async () => { await agent.start(); await agent.stop(); const metrics = agent.getMetrics(); expect(metrics.uptime).toBeGreaterThan(0); }); }); describe('Alert Management', () => { it('should return alerts array', () => { const alerts = agent.getAlerts(); expect(Array.isArray(alerts)).toBe(true); }); it('should have proper alert structure', () => { const alerts = agent.getAlerts(); for (const alert of alerts) { expect(alert.id).toBeDefined(); expect(alert.agentId).toBe('plant-lineage-agent'); expect(alert.severity).toBeDefined(); expect(alert.title).toBeDefined(); expect(alert.message).toBeDefined(); expect(alert.timestamp).toBeDefined(); expect(typeof alert.acknowledged).toBe('boolean'); } }); }); describe('Error Handling', () => { it('should handle agent operations gracefully', async () => { // Test that agent doesn't throw on basic operations expect(() => agent.getMetrics()).not.toThrow(); expect(() => agent.getAnomalies()).not.toThrow(); expect(() => agent.getNetworkStats()).not.toThrow(); expect(() => agent.getAlerts()).not.toThrow(); }); it('should handle pause on non-running agent', () => { expect(agent.status).toBe('idle'); agent.pause(); // Should not throw // Status should remain idle (only pauses if running) expect(agent.status).toBe('idle'); }); it('should handle resume on non-paused agent', () => { expect(agent.status).toBe('idle'); agent.resume(); // Should not throw // Status should remain idle (only resumes if paused) expect(agent.status).toBe('idle'); }); }); describe('Response Format', () => { it('should return consistent metrics format', () => { const metrics = agent.getMetrics(); expect(typeof metrics.agentId).toBe('string'); expect(typeof metrics.tasksCompleted).toBe('number'); expect(typeof metrics.tasksFailed).toBe('number'); expect(typeof metrics.averageExecutionMs).toBe('number'); expect(typeof metrics.uptime).toBe('number'); expect(Array.isArray(metrics.errors)).toBe(true); }); it('should return consistent network stats format', () => { const stats = agent.getNetworkStats(); expect(typeof stats.totalPlants).toBe('number'); expect(typeof stats.totalLineages).toBe('number'); expect(typeof stats.avgGenerationDepth).toBe('number'); expect(typeof stats.avgLineageSize).toBe('number'); expect(typeof stats.geographicSpread).toBe('number'); }); }); }); describe('PlantLineageAgent Integration', () => { it('should be accessible via singleton', () => { const agent1 = getPlantLineageAgent(); const agent2 = getPlantLineageAgent(); expect(agent1).toBe(agent2); }); it('should have correct priority', () => { const agent = getPlantLineageAgent(); expect(agent.config.priority).toBe('high'); }); it('should have correct interval', () => { const agent = getPlantLineageAgent(); // Should run every minute expect(agent.config.intervalMs).toBe(60000); }); });