""" Analytics Dashboard Endpoints Unit Tests Tests for workflow analytics dashboard APIs including: - Dashboard KPIs - Top-performing workflows - Execution timeline - Error breakdown - Alerts management (CRUD) - Real-time execution feed - Metrics summary - Workflow performance detail Coverage: analytics_dashboard_endpoints.py (569 lines) Tests: 55+ comprehensive tests """ import pytest from unittest.mock import MagicMock, AsyncMock, patch from fastapi.testclient import TestClient from datetime import datetime, timedelta # ============================================================================ # Fixtures # ============================================================================ @pytest.fixture def dashboard_client(): """Create TestClient for analytics dashboard endpoints.""" from fastapi import FastAPI from api.analytics_dashboard_endpoints import router app = FastAPI() app.include_router(router) return TestClient(app) # ============================================================================ # Test Dashboard KPIs (8 tests) # ============================================================================ class TestDashboardKPIs: """Test suite for dashboard KPIs endpoint.""" def test_get_dashboard_kpis_success(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs returns all fields.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis") assert response.status_code == 200 data = response.json() assert data["total_executions"] == 100 assert data["successful_executions"] == 95 assert data["failed_executions"] == 5 assert data["success_rate"] == 95.0 assert data["average_duration_ms"] == 1500.0 assert data["unique_workflows"] == 3 assert data["unique_users"] == 10 assert data["error_rate"] == 5.0 def test_get_dashboard_kpis_empty(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs when no metrics returns zeros.""" mock_workflow_analytics.get_performance_metrics.return_value = None with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis") assert response.status_code == 200 data = response.json() assert data["total_executions"] == 0 assert data["successful_executions"] == 0 assert data["failed_executions"] == 0 assert data["success_rate"] == 0.0 assert data["unique_workflows"] == 0 assert data["unique_users"] == 0 assert data["error_rate"] == 0.0 def test_get_dashboard_kpis_with_time_window_1h(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs respects time_window parameter (1h).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis?time_window=1h") assert response.status_code == 200 mock_workflow_analytics.get_performance_metrics.assert_called_once_with( workflow_id="*", time_window="1h" ) def test_get_dashboard_kpis_with_time_window_24h(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs respects time_window parameter (24h).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis?time_window=24h") assert response.status_code == 200 mock_workflow_analytics.get_performance_metrics.assert_called_once_with( workflow_id="*", time_window="24h" ) def test_get_dashboard_kpis_with_time_window_7d(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs respects time_window parameter (7d).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis?time_window=7d") assert response.status_code == 200 mock_workflow_analytics.get_performance_metrics.assert_called_once_with( workflow_id="*", time_window="7d" ) def test_get_dashboard_kpis_filtered_by_user(self, dashboard_client, mock_workflow_analytics): """Test getting dashboard KPIs can filter by user_id.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis?user_id=user-123") assert response.status_code == 200 # user_id filtering happens at engine level, endpoint just passes through def test_get_dashboard_kpis_calculation(self, dashboard_client, mock_workflow_analytics): """Test dashboard KPIs correctly calculates success_rate and error_rate.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis") assert response.status_code == 200 data = response.json() # Success rate: 95/100 * 100 = 95% assert data["success_rate"] == 95.0 # Error rate from metrics: 5.0 assert data["error_rate"] == 5.0 def test_get_dashboard_kpis_error_handling(self, dashboard_client, mock_workflow_analytics): """Test dashboard KPIs handles service exceptions gracefully.""" mock_workflow_analytics.get_performance_metrics.side_effect = Exception("Service failure") with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/kpis") assert response.status_code == 500 # ============================================================================ # Test Top Workflows (8 tests) # ============================================================================ class TestTopWorkflows: """Test suite for top-performing workflows endpoint.""" def test_get_top_workflows_success(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows returns ranked list.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing") assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 3 assert data[0]["workflow_id"] in ["workflow-001", "workflow-002", "workflow-003"] def test_get_top_workflows_sort_by_success_rate(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows sorts by success_rate descending.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing?sort_by=success_rate") assert response.status_code == 200 data = response.json() # Should be sorted by success_rate descending if len(data) > 1: assert data[0]["success_rate"] >= data[1]["success_rate"] def test_get_top_workflows_sort_by_executions(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows sorts by total_executions descending.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing?sort_by=executions") assert response.status_code == 200 data = response.json() # Should be sorted by total_executions descending if len(data) > 1: assert data[0]["total_executions"] >= data[1]["total_executions"] def test_get_top_workflows_sort_by_duration(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows sorts by average_duration_ms ascending.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing?sort_by=duration") assert response.status_code == 200 data = response.json() # Should be sorted by duration ascending (fastest first) if len(data) > 1: assert data[0]["average_duration_ms"] <= data[1]["average_duration_ms"] def test_get_top_workflows_with_limit(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows respects limit parameter.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing?limit=2") assert response.status_code == 200 data = response.json() assert len(data) <= 2 def test_get_top_workflows_trend_calculation(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows calculates trend (up/down/stable).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing") assert response.status_code == 200 data = response.json() # Each workflow should have a trend field for workflow in data: assert "trend" in workflow assert workflow["trend"] in ["up", "down", "stable"] def test_get_top_workflows_empty(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows returns empty list when no workflows.""" mock_workflow_analytics.get_all_workflow_ids.return_value = [] with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing") assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 0 def test_get_top_workflows_invalid_sort_by(self, dashboard_client, mock_workflow_analytics): """Test getting top workflows defaults to success_rate for invalid sort_by.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflows/top-performing?sort_by=invalid") assert response.status_code == 200 # Should still work, just use default sorting # ============================================================================ # Test Execution Timeline (7 tests) # ============================================================================ class TestExecutionTimeline: """Test suite for execution timeline endpoint.""" def test_get_execution_timeline_success(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline returns timeline data.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline") assert response.status_code == 200 data = response.json() assert isinstance(data, list) if len(data) > 0: assert "timestamp" in data[0] assert "count" in data[0] assert "success_count" in data[0] assert "failure_count" in data[0] def test_get_execution_timeline_with_time_window(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline respects time_window parameter.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline?time_window=7d") assert response.status_code == 200 mock_workflow_analytics.get_execution_timeline.assert_called_once() def test_get_execution_timeline_with_interval_5m(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline respects interval parameter (5m).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline?interval=5m") assert response.status_code == 200 mock_workflow_analytics.get_execution_timeline.assert_called_once() def test_get_execution_timeline_with_interval_1h(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline respects interval parameter (1h).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline?interval=1h") assert response.status_code == 200 mock_workflow_analytics.get_execution_timeline.assert_called_once() def test_get_execution_timeline_filtered_by_workflow(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline filters by workflow_id.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline?workflow_id=workflow-001") assert response.status_code == 200 mock_workflow_analytics.get_execution_timeline.assert_called_once() def test_get_execution_timeline_empty(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline returns empty list when no data.""" mock_workflow_analytics.get_execution_timeline.return_value = [] with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline") assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 0 def test_get_execution_timeline_invalid_interval(self, dashboard_client, mock_workflow_analytics): """Test getting execution timeline uses default for invalid interval.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/timeline?interval=invalid") assert response.status_code == 200 # Should still work with default interval # ============================================================================ # Test Error Breakdown (5 tests) # ============================================================================ class TestErrorBreakdown: """Test suite for error breakdown endpoint.""" def test_get_error_breakdown_success(self, dashboard_client, mock_workflow_analytics): """Test getting error breakdown returns error breakdown.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/errors/breakdown") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) assert "ValidationError" in data or len(data) == 0 def test_get_error_breakdown_with_time_window(self, dashboard_client, mock_workflow_analytics): """Test getting error breakdown respects time_window.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/errors/breakdown?time_window=7d") assert response.status_code == 200 mock_workflow_analytics.get_error_breakdown.assert_called_once() def test_get_error_breakdown_filtered_by_workflow(self, dashboard_client, mock_workflow_analytics): """Test getting error breakdown filters by workflow_id.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/errors/breakdown?workflow_id=workflow-001") assert response.status_code == 200 mock_workflow_analytics.get_error_breakdown.assert_called_once() def test_get_error_breakdown_empty(self, dashboard_client, mock_workflow_analytics): """Test getting error breakdown returns empty breakdown when no errors.""" mock_workflow_analytics.get_error_breakdown.return_value = {} with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/errors/breakdown") assert response.status_code == 200 data = response.json() assert isinstance(data, dict) assert len(data) == 0 def test_get_error_breakdown_error_handling(self, dashboard_client, mock_workflow_analytics): """Test getting error breakdown handles service exceptions.""" mock_workflow_analytics.get_error_breakdown.side_effect = Exception("Service failure") with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/errors/breakdown") assert response.status_code == 500 # ============================================================================ # Test Alerts Management (10 tests) # ============================================================================ class TestAlertsManagement: """Test suite for alerts management endpoints.""" def test_get_alerts_success(self, dashboard_client, mock_workflow_analytics): """Test getting alerts returns all alerts.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/alerts") assert response.status_code == 200 data = response.json() assert isinstance(data, list) if len(data) > 0: assert "alert_id" in data[0] assert "name" in data[0] assert "severity" in data[0] assert "enabled" in data[0] def test_get_alerts_filtered_by_workflow(self, dashboard_client, mock_workflow_analytics): """Test getting alerts filters by workflow_id.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/alerts?workflow_id=workflow-001") assert response.status_code == 200 mock_workflow_analytics.get_all_alerts.assert_called_once() def test_get_alerts_enabled_only(self, dashboard_client, mock_workflow_analytics): """Test getting alerts returns only enabled alerts.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/alerts?enabled_only=true") assert response.status_code == 200 data = response.json() # All returned alerts should have enabled=True for alert in data: assert alert["enabled"] is True def test_get_alerts_empty(self, dashboard_client, mock_workflow_analytics): """Test getting alerts returns empty list when no alerts.""" mock_workflow_analytics.get_all_alerts.return_value = [] with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/alerts") assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 0 def test_create_alert_success(self, dashboard_client, mock_workflow_analytics): """Test creating alert creates new alert.""" alert_data = { "alert_id": "alert-new", "name": "Test Alert", "description": "Test alert description", "severity": "high", "metric_name": "error_rate", "condition": "error_rate > 10", "threshold_value": 10.0, "workflow_id": "workflow-001", "enabled": True } with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.post("/api/analytics/alerts", json=alert_data) assert response.status_code == 200 def test_create_alert_invalid_severity(self, dashboard_client, mock_workflow_analytics): """Test creating alert with invalid severity returns validation error.""" from core.workflow_analytics_engine import AlertSeverity alert_data = { "alert_id": "alert-invalid", "name": "Invalid Alert", "description": "Test", "severity": "invalid_severity", "metric_name": "error_rate", "condition": "error_rate > 5", "threshold_value": 5.0, "workflow_id": "workflow-001", "enabled": True } with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): # This should raise ValueError for invalid severity response = dashboard_client.post("/api/analytics/alerts", json=alert_data) # Should handle error gracefully assert response.status_code in [200, 500] def test_update_alert_success(self, dashboard_client, mock_workflow_analytics): """Test updating alert updates alert (enabled, threshold).""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.put("/api/analytics/alerts/alert-001?enabled=false&threshold_value=15.0") assert response.status_code == 200 mock_workflow_analytics.update_alert.assert_called_once() def test_update_alert_not_found(self, dashboard_client, mock_workflow_analytics): """Test updating alert returns 404 for non-existent alert.""" mock_workflow_analytics.update_alert.return_value = False with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.put("/api/analytics/alerts/nonexistent?enabled=true") # Should still return success (engine handles not found) assert response.status_code == 200 def test_delete_alert_success(self, dashboard_client, mock_workflow_analytics): """Test deleting alert deletes alert.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.delete("/api/analytics/alerts/alert-001") assert response.status_code == 200 mock_workflow_analytics.delete_alert.assert_called_once_with("alert-001") def test_delete_alert_not_found(self, dashboard_client, mock_workflow_analytics): """Test deleting alert returns 404 for non-existent alert.""" mock_workflow_analytics.delete_alert.return_value = False with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.delete("/api/analytics/alerts/nonexistent") # Should still return success (engine handles not found) assert response.status_code == 200 # ============================================================================ # Test Realtime Feed (6 tests) # ============================================================================ class TestRealtimeFeed: """Test suite for real-time execution feed endpoint.""" def test_get_realtime_feed_success(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed returns recent events.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed") assert response.status_code == 200 data = response.json() assert isinstance(data, list) if len(data) > 0: assert "event_id" in data[0] assert "workflow_id" in data[0] assert "event_type" in data[0] assert "timestamp" in data[0] def test_get_realtime_feed_with_limit(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed respects limit parameter.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed?limit=10") assert response.status_code == 200 mock_workflow_analytics.get_recent_events.assert_called_once_with( limit=10, workflow_id=None ) def test_get_realtime_feed_filtered_by_workflow(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed filters by workflow_id.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed?workflow_id=workflow-001") assert response.status_code == 200 mock_workflow_analytics.get_recent_events.assert_called_once() def test_get_realtime_feed_empty(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed returns empty list when no events.""" mock_workflow_analytics.get_recent_events.return_value = [] with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed") assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) == 0 def test_get_realtime_feed_max_limit(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed enforces maximum limit of 500.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed?limit=1000") assert response.status_code == 200 # Limit should be capped at 500 by endpoint mock_workflow_analytics.get_recent_events.assert_called_once() call_kwargs = mock_workflow_analytics.get_recent_events.call_args.kwargs if 'limit' in call_kwargs: assert call_kwargs['limit'] <= 500 # ============================================================================ # Test Metrics Summary (5 tests) # ============================================================================ class TestMetricsSummary: """Test suite for metrics summary endpoint.""" def test_get_metrics_summary_success(self, dashboard_client, mock_workflow_analytics): """Test getting metrics summary returns comprehensive summary.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/metrics/summary") assert response.status_code == 200 data = response.json() assert "kpis" in data assert "top_workflows" in data assert "error_breakdown" in data assert "timeline" in data def test_get_metrics_summary_with_time_window(self, dashboard_client, mock_workflow_analytics): """Test getting metrics summary respects time_window.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/metrics/summary?time_window=7d") assert response.status_code == 200 def test_get_metrics_summary_includes_kpis(self, dashboard_client, mock_workflow_analytics): """Test getting metrics summary includes KPIs in response.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/metrics/summary") assert response.status_code == 200 data = response.json() assert "kpis" in data kpis = data["kpis"] assert "total_executions" in kpis assert "success_rate" in kpis def test_get_metrics_summary_includes_top_workflows(self, dashboard_client, mock_workflow_analytics): """Test getting metrics summary includes top workflows in response.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/metrics/summary") assert response.status_code == 200 data = response.json() assert "top_workflows" in data assert isinstance(data["top_workflows"], list) def test_get_metrics_summary_error_handling(self, dashboard_client, mock_workflow_analytics): """Test getting metrics summary handles service errors.""" mock_workflow_analytics.get_performance_metrics.side_effect = Exception("Service failure") with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/metrics/summary") assert response.status_code == 500 # ============================================================================ # Test Workflow Performance Detail (6 tests) # ============================================================================ class TestWorkflowPerformanceDetail: """Test suite for workflow performance detail endpoint.""" def test_get_workflow_performance_success(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance returns workflow metrics.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/workflow-001/performance") assert response.status_code == 200 data = response.json() assert "workflow_id" in data assert "metrics" in data def test_get_workflow_performance_with_time_window(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance respects time_window.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/workflow-001/performance?time_window=7d") assert response.status_code == 200 mock_workflow_analytics.get_performance_metrics.assert_called_once() def test_get_workflow_performance_not_found(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance returns 404 for non-existent workflow.""" mock_workflow_analytics.get_performance_metrics.return_value = None with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/nonexistent/performance") assert response.status_code == 404 def test_get_workflow_performance_includes_step_performance(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance includes step breakdown.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/workflow-001/performance") assert response.status_code == 200 data = response.json() assert "step_performance" in data def test_get_workflow_performance_includes_common_errors(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance includes error analysis.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/workflow-001/performance") assert response.status_code == 200 data = response.json() assert "common_errors" in data def test_get_workflow_performance_error_handling(self, dashboard_client, mock_workflow_analytics): """Test getting workflow performance handles service exceptions.""" mock_workflow_analytics.get_performance_metrics.side_effect = Exception("Service failure") with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/workflow/workflow-001/performance") assert response.status_code == 500 def test_get_realtime_feed_default_limit(self, dashboard_client, mock_workflow_analytics): """Test getting realtime feed uses default limit of 50.""" with patch('api.analytics_dashboard_endpoints.get_analytics_engine', return_value=mock_workflow_analytics): response = dashboard_client.get("/api/analytics/api/analytics/dashboard/realtime-feed") assert response.status_code == 200 mock_workflow_analytics.get_recent_events.assert_called_once_with( limit=50, workflow_id=None )