Skip to main content
Glama
aws-samples

CFM Tips - Cost Optimization MCP Server

by aws-samples
test_cloudwatch_result_processor.py22.3 kB
""" Tests for CloudWatch Result Processor - Zero-Cost Pagination and Sorting These tests verify that the CloudWatch result processor provides cost-based sorting and pagination without making any additional AWS API calls. """ import pytest import unittest.mock as mock from unittest.mock import MagicMock, patch from playbooks.cloudwatch.result_processor import CloudWatchResultProcessor, PaginationMetadata class TestCloudWatchResultProcessor: """Test suite for CloudWatch Result Processor with zero-cost guarantee.""" def setup_method(self): """Set up test fixtures.""" # Mock pricing service to avoid any API calls self.mock_pricing_service = MagicMock() self.mock_pricing_service.get_logs_pricing.return_value = { 'status': 'success', 'logs_pricing': {'storage_per_gb_month': 0.03} } self.mock_pricing_service.get_metrics_pricing.return_value = { 'status': 'success', 'metrics_pricing': {'custom_metric_per_month': 0.30} } self.mock_pricing_service.get_alarms_pricing.return_value = { 'status': 'success', 'alarms_pricing': { 'standard_alarm_per_month': 0.10, 'high_resolution_alarm_per_month': 0.50 } } self.mock_pricing_service.get_dashboards_pricing.return_value = { 'status': 'success', 'dashboards_pricing': {'dashboard_per_month': 3.00} } self.processor = CloudWatchResultProcessor(pricing_service=self.mock_pricing_service) def test_initialization_zero_cost_guarantee(self): """Test that processor initializes with zero-cost guarantee.""" processor = CloudWatchResultProcessor() assert processor.page_size == 10 assert not hasattr(processor, '_aws_clients') def test_calculate_log_group_cost_no_api_calls(self): """Test log group cost calculation without API calls.""" log_group = { 'logGroupName': 'test-log-group', 'storedBytes': 1073741824 # 1 GB } # Verify no AWS clients are created or called with patch('boto3.client') as mock_boto3: cost = self.processor.calculate_log_group_cost(log_group) mock_boto3.assert_not_called() # Should calculate cost based on stored bytes assert cost == 0.03 # 1 GB * $0.03/GB/month def test_calculate_log_group_cost_zero_bytes(self): """Test log group cost calculation with zero stored bytes.""" log_group = { 'logGroupName': 'empty-log-group', 'storedBytes': 0 } cost = self.processor.calculate_log_group_cost(log_group) assert cost == 0.0 def test_calculate_custom_metric_cost_no_api_calls(self): """Test custom metric cost calculation without API calls.""" custom_metric = { 'MetricName': 'CustomMetric', 'Namespace': 'MyApp/Performance' } with patch('boto3.client') as mock_boto3: cost = self.processor.calculate_custom_metric_cost(custom_metric) mock_boto3.assert_not_called() assert cost == 0.30 # Custom metric cost def test_calculate_aws_metric_cost_free(self): """Test that AWS metrics are correctly identified as free.""" aws_metric = { 'MetricName': 'CPUUtilization', 'Namespace': 'AWS/EC2' } cost = self.processor.calculate_custom_metric_cost(aws_metric) assert cost == 0.0 # AWS metrics are free def test_calculate_alarm_cost_standard(self): """Test standard alarm cost calculation.""" standard_alarm = { 'AlarmName': 'test-alarm', 'Period': 300 # 5 minutes = standard resolution } with patch('boto3.client') as mock_boto3: cost = self.processor.calculate_alarm_cost(standard_alarm) mock_boto3.assert_not_called() assert cost == 0.10 # Standard alarm cost def test_calculate_alarm_cost_high_resolution(self): """Test high-resolution alarm cost calculation.""" high_res_alarm = { 'AlarmName': 'test-alarm-hr', 'Period': 60 # 1 minute = high resolution } cost = self.processor.calculate_alarm_cost(high_res_alarm) assert cost == 0.50 # High-resolution alarm cost def test_calculate_dashboard_cost_free_tier(self): """Test dashboard cost calculation within free tier.""" dashboard = {'DashboardName': 'test-dashboard'} with patch('boto3.client') as mock_boto3: cost = self.processor.calculate_dashboard_cost(dashboard, total_dashboards=2) mock_boto3.assert_not_called() assert cost == 0.0 # Within free tier (3 dashboards) def test_calculate_dashboard_cost_beyond_free_tier(self): """Test dashboard cost calculation beyond free tier.""" dashboard = {'DashboardName': 'test-dashboard'} cost = self.processor.calculate_dashboard_cost(dashboard, total_dashboards=5) assert cost == 3.00 # Beyond free tier def test_enrich_items_with_cost_estimates_no_api_calls(self): """Test enriching items with cost estimates without API calls.""" log_groups = [ {'logGroupName': 'group1', 'storedBytes': 1073741824}, # 1 GB {'logGroupName': 'group2', 'storedBytes': 2147483648} # 2 GB ] with patch('boto3.client') as mock_boto3: enriched = self.processor.enrich_items_with_cost_estimates(log_groups, 'log_groups') mock_boto3.assert_not_called() assert len(enriched) == 2 assert enriched[0]['estimated_monthly_cost'] == 0.03 assert enriched[1]['estimated_monthly_cost'] == 0.06 def test_sort_by_cost_descending_no_api_calls(self): """Test cost-based sorting without API calls.""" items = [ {'name': 'item1', 'estimated_monthly_cost': 10.0}, {'name': 'item2', 'estimated_monthly_cost': 25.0}, {'name': 'item3', 'estimated_monthly_cost': 5.0} ] with patch('boto3.client') as mock_boto3: sorted_items = self.processor.sort_by_cost_descending(items) mock_boto3.assert_not_called() assert len(sorted_items) == 3 assert sorted_items[0]['estimated_monthly_cost'] == 25.0 assert sorted_items[1]['estimated_monthly_cost'] == 10.0 assert sorted_items[2]['estimated_monthly_cost'] == 5.0 def test_sort_by_cost_missing_field(self): """Test sorting with missing cost field.""" items = [ {'name': 'item1', 'estimated_monthly_cost': 10.0}, {'name': 'item2'}, # Missing cost field {'name': 'item3', 'estimated_monthly_cost': 5.0} ] sorted_items = self.processor.sort_by_cost_descending(items) # Items with missing cost should be treated as 0.0 and sorted last assert sorted_items[0]['estimated_monthly_cost'] == 10.0 assert sorted_items[1]['estimated_monthly_cost'] == 5.0 assert 'estimated_monthly_cost' not in sorted_items[2] def test_create_pagination_metadata_page_1(self): """Test pagination metadata creation for page 1.""" metadata = self.processor.create_pagination_metadata(total_items=25, current_page=1) assert metadata.current_page == 1 assert metadata.page_size == 10 assert metadata.total_items == 25 assert metadata.total_pages == 3 assert metadata.has_next_page is True assert metadata.has_previous_page is False def test_create_pagination_metadata_middle_page(self): """Test pagination metadata creation for middle page.""" metadata = self.processor.create_pagination_metadata(total_items=25, current_page=2) assert metadata.current_page == 2 assert metadata.total_pages == 3 assert metadata.has_next_page is True assert metadata.has_previous_page is True def test_create_pagination_metadata_last_page(self): """Test pagination metadata creation for last page.""" metadata = self.processor.create_pagination_metadata(total_items=25, current_page=3) assert metadata.current_page == 3 assert metadata.total_pages == 3 assert metadata.has_next_page is False assert metadata.has_previous_page is True def test_create_pagination_metadata_invalid_page_correction(self): """Test pagination metadata with invalid page number correction.""" # Test page 0 correction metadata = self.processor.create_pagination_metadata(total_items=25, current_page=0) assert metadata.current_page == 1 # Test negative page correction metadata = self.processor.create_pagination_metadata(total_items=25, current_page=-5) assert metadata.current_page == 1 def test_create_pagination_metadata_empty_results(self): """Test pagination metadata with empty results.""" metadata = self.processor.create_pagination_metadata(total_items=0, current_page=1) assert metadata.current_page == 1 assert metadata.page_size == 10 assert metadata.total_items == 0 assert metadata.total_pages == 0 assert metadata.has_next_page is False assert metadata.has_previous_page is False def test_paginate_results_page_1(self): """Test pagination for page 1.""" items = [{'id': i, 'estimated_monthly_cost': i} for i in range(25)] with patch('boto3.client') as mock_boto3: result = self.processor.paginate_results(items, page=1) mock_boto3.assert_not_called() assert len(result['items']) == 10 assert result['items'][0]['id'] == 0 assert result['items'][9]['id'] == 9 assert result['pagination']['current_page'] == 1 assert result['pagination']['total_items'] == 25 assert result['pagination']['total_pages'] == 3 assert result['pagination']['has_next_page'] is True assert result['pagination']['has_previous_page'] is False def test_paginate_results_page_2(self): """Test pagination for page 2.""" items = [{'id': i, 'estimated_monthly_cost': i} for i in range(25)] result = self.processor.paginate_results(items, page=2) assert len(result['items']) == 10 assert result['items'][0]['id'] == 10 assert result['items'][9]['id'] == 19 assert result['pagination']['current_page'] == 2 def test_paginate_results_last_page_partial(self): """Test pagination for last page with partial results.""" items = [{'id': i, 'estimated_monthly_cost': i} for i in range(25)] result = self.processor.paginate_results(items, page=3) assert len(result['items']) == 5 # Only 5 items on last page assert result['items'][0]['id'] == 20 assert result['items'][4]['id'] == 24 assert result['pagination']['current_page'] == 3 assert result['pagination']['has_next_page'] is False def test_paginate_results_out_of_range(self): """Test pagination for out-of-range page.""" items = [{'id': i, 'estimated_monthly_cost': i} for i in range(25)] result = self.processor.paginate_results(items, page=10) assert len(result['items']) == 0 # No items for out-of-range page assert result['pagination']['current_page'] == 10 assert result['pagination']['total_items'] == 25 assert result['pagination']['total_pages'] == 3 assert result['pagination']['has_next_page'] is False assert result['pagination']['has_previous_page'] is True def test_paginate_results_single_page(self): """Test pagination with single page of results.""" items = [{'id': i, 'estimated_monthly_cost': i} for i in range(7)] result = self.processor.paginate_results(items, page=1) assert len(result['items']) == 7 assert result['pagination']['current_page'] == 1 assert result['pagination']['total_pages'] == 1 assert result['pagination']['has_next_page'] is False assert result['pagination']['has_previous_page'] is False def test_process_log_groups_results_integration(self): """Test complete log groups processing with sorting and pagination.""" log_groups = [ {'logGroupName': 'small-group', 'storedBytes': 1073741824}, # 1 GB = $0.03 {'logGroupName': 'large-group', 'storedBytes': 10737418240}, # 10 GB = $0.30 {'logGroupName': 'medium-group', 'storedBytes': 5368709120} # 5 GB = $0.15 ] with patch('boto3.client') as mock_boto3: result = self.processor.process_log_groups_results(log_groups, page=1) mock_boto3.assert_not_called() # Should be sorted by cost descending items = result['items'] assert len(items) == 3 assert items[0]['logGroupName'] == 'large-group' assert items[0]['estimated_monthly_cost'] == 0.30 assert items[1]['logGroupName'] == 'medium-group' assert items[1]['estimated_monthly_cost'] == 0.15 assert items[2]['logGroupName'] == 'small-group' assert items[2]['estimated_monthly_cost'] == 0.03 # Check pagination metadata assert result['pagination']['current_page'] == 1 assert result['pagination']['total_items'] == 3 assert result['pagination']['total_pages'] == 1 def test_process_metrics_results_integration(self): """Test complete metrics processing with sorting and pagination.""" metrics = [ {'MetricName': 'AWSMetric', 'Namespace': 'AWS/EC2'}, {'MetricName': 'CustomMetric1', 'Namespace': 'MyApp/Performance'}, {'MetricName': 'CustomMetric2', 'Namespace': 'MyApp/Business'} ] with patch('boto3.client') as mock_boto3: result = self.processor.process_metrics_results(metrics, page=1) mock_boto3.assert_not_called() # Should be sorted by cost descending (custom metrics first) items = result['items'] assert len(items) == 3 # Custom metrics should be first (cost = 0.30 each) assert items[0]['Namespace'] in ['MyApp/Performance', 'MyApp/Business'] assert items[0]['estimated_monthly_cost'] == 0.30 assert items[1]['Namespace'] in ['MyApp/Performance', 'MyApp/Business'] assert items[1]['estimated_monthly_cost'] == 0.30 # AWS metric should be last (cost = 0.0) assert items[2]['Namespace'] == 'AWS/EC2' assert items[2]['estimated_monthly_cost'] == 0.0 def test_process_alarms_results_integration(self): """Test complete alarms processing with sorting and pagination.""" alarms = [ {'AlarmName': 'standard-alarm', 'Period': 300}, # Standard = $0.10 {'AlarmName': 'high-res-alarm', 'Period': 60} # High-res = $0.50 ] with patch('boto3.client') as mock_boto3: result = self.processor.process_alarms_results(alarms, page=1) mock_boto3.assert_not_called() # Should be sorted by cost descending items = result['items'] assert len(items) == 2 assert items[0]['AlarmName'] == 'high-res-alarm' assert items[0]['estimated_monthly_cost'] == 0.50 assert items[1]['AlarmName'] == 'standard-alarm' assert items[1]['estimated_monthly_cost'] == 0.10 def test_process_dashboards_results_integration(self): """Test complete dashboards processing with sorting and pagination.""" dashboards = [ {'DashboardName': 'dashboard1'}, {'DashboardName': 'dashboard2'}, {'DashboardName': 'dashboard3'}, {'DashboardName': 'dashboard4'}, # Beyond free tier {'DashboardName': 'dashboard5'} # Beyond free tier ] with patch('boto3.client') as mock_boto3: result = self.processor.process_dashboards_results(dashboards, page=1) mock_boto3.assert_not_called() # Should be sorted by cost descending (paid dashboards first) items = result['items'] assert len(items) == 5 # First dashboards should be the paid ones (beyond free tier) assert items[0]['estimated_monthly_cost'] == 3.00 assert items[1]['estimated_monthly_cost'] == 3.00 # Last dashboards should be free tier assert items[2]['estimated_monthly_cost'] == 0.0 assert items[3]['estimated_monthly_cost'] == 0.0 assert items[4]['estimated_monthly_cost'] == 0.0 def test_process_recommendations_integration(self): """Test complete recommendations processing with sorting and pagination.""" recommendations = [ {'type': 'low_impact', 'potential_monthly_savings': 5.0}, {'type': 'high_impact', 'potential_monthly_savings': 50.0}, {'type': 'medium_impact', 'potential_monthly_savings': 20.0} ] with patch('boto3.client') as mock_boto3: result = self.processor.process_recommendations(recommendations, page=1) mock_boto3.assert_not_called() # Should be sorted by potential savings descending items = result['items'] assert len(items) == 3 assert items[0]['type'] == 'high_impact' assert items[0]['potential_monthly_savings'] == 50.0 assert items[1]['type'] == 'medium_impact' assert items[1]['potential_monthly_savings'] == 20.0 assert items[2]['type'] == 'low_impact' assert items[2]['potential_monthly_savings'] == 5.0 def test_error_handling_graceful_degradation(self): """Test that errors in processing don't break the system.""" # Test with invalid log group data invalid_log_groups = [ {'invalid': 'data'}, {'logGroupName': 'valid-group', 'storedBytes': 1073741824} ] result = self.processor.process_log_groups_results(invalid_log_groups, page=1) # Should still process valid items and handle invalid ones gracefully assert len(result['items']) == 2 assert result['items'][0]['estimated_monthly_cost'] == 0.03 # Valid item assert result['items'][1]['estimated_monthly_cost'] == 0.0 # Invalid item defaulted to 0 def test_no_pricing_service_fallback(self): """Test that processor works without pricing service using fallback pricing.""" processor_no_pricing = CloudWatchResultProcessor(pricing_service=None) log_group = {'logGroupName': 'test', 'storedBytes': 1073741824} # 1 GB with patch('boto3.client') as mock_boto3: cost = processor_no_pricing.calculate_log_group_cost(log_group) mock_boto3.assert_not_called() # Should use fallback pricing assert cost == 0.03 # Default fallback price class TestZeroCostGuarantee: """Specific tests to verify zero-cost guarantee.""" def test_no_boto3_imports_in_processing_methods(self): """Test that processing methods don't import boto3.""" processor = CloudWatchResultProcessor() # Mock boto3 to track if it's imported with patch('boto3.client') as mock_boto3, \ patch('boto3.resource') as mock_resource, \ patch('boto3.Session') as mock_session: # Run all processing methods items = [{'test': 'data', 'estimated_monthly_cost': 1.0}] processor.sort_by_cost_descending(items) processor.paginate_results(items) processor.create_pagination_metadata(10, 1) # Verify no boto3 calls were made mock_boto3.assert_not_called() mock_resource.assert_not_called() mock_session.assert_not_called() def test_no_external_network_calls(self): """Test that no external network calls are made during processing.""" processor = CloudWatchResultProcessor() # Mock requests library to ensure no HTTP calls with patch('requests.get') as mock_get, \ patch('requests.post') as mock_post, \ patch('urllib.request.urlopen') as mock_urlopen: # Process sample data log_groups = [{'logGroupName': 'test', 'storedBytes': 1073741824}] result = processor.process_log_groups_results(log_groups) # Verify no network calls were made mock_get.assert_not_called() mock_post.assert_not_called() mock_urlopen.assert_not_called() def test_memory_only_operations(self): """Test that all operations are memory-only.""" processor = CloudWatchResultProcessor() # Create test data large_dataset = [ {'id': i, 'estimated_monthly_cost': i * 0.1} for i in range(100) ] # All operations should complete without external dependencies sorted_data = processor.sort_by_cost_descending(large_dataset) paginated_data = processor.paginate_results(sorted_data, page=5) # Verify results are correct assert len(paginated_data['items']) == 10 assert paginated_data['pagination']['current_page'] == 5 assert paginated_data['pagination']['total_items'] == 100 # Verify highest cost items are first assert sorted_data[0]['estimated_monthly_cost'] == 9.9 assert sorted_data[-1]['estimated_monthly_cost'] == 0.0 if __name__ == '__main__': pytest.main([__file__])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aws-samples/sample-cfm-tips-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server