runbooks 0.7.6__py3-none-any.whl → 0.7.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +5 -1
  3. runbooks/cfat/__init__.py +8 -4
  4. runbooks/cfat/assessment/collectors.py +171 -14
  5. runbooks/cfat/assessment/compliance.py +871 -0
  6. runbooks/cfat/assessment/runner.py +122 -11
  7. runbooks/cfat/models.py +6 -2
  8. runbooks/common/logger.py +14 -0
  9. runbooks/common/rich_utils.py +451 -0
  10. runbooks/enterprise/__init__.py +68 -0
  11. runbooks/enterprise/error_handling.py +411 -0
  12. runbooks/enterprise/logging.py +439 -0
  13. runbooks/enterprise/multi_tenant.py +583 -0
  14. runbooks/finops/README.md +468 -241
  15. runbooks/finops/__init__.py +39 -3
  16. runbooks/finops/cli.py +83 -18
  17. runbooks/finops/cross_validation.py +375 -0
  18. runbooks/finops/dashboard_runner.py +812 -164
  19. runbooks/finops/enhanced_dashboard_runner.py +525 -0
  20. runbooks/finops/finops_dashboard.py +1892 -0
  21. runbooks/finops/helpers.py +485 -51
  22. runbooks/finops/optimizer.py +823 -0
  23. runbooks/finops/tests/__init__.py +19 -0
  24. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  25. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  26. runbooks/finops/tests/run_tests.py +305 -0
  27. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  28. runbooks/finops/tests/test_integration.py +477 -0
  29. runbooks/finops/tests/test_performance.py +380 -0
  30. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  31. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  32. runbooks/finops/tests/test_single_account_features.py +715 -0
  33. runbooks/finops/tests/validate_test_suite.py +220 -0
  34. runbooks/finops/types.py +1 -1
  35. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  36. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  37. runbooks/inventory/collectors/aws_comprehensive.py +442 -0
  38. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  39. runbooks/inventory/core/collector.py +172 -13
  40. runbooks/inventory/discovery.md +1 -1
  41. runbooks/inventory/list_ec2_instances.py +18 -20
  42. runbooks/inventory/list_ssm_parameters.py +31 -3
  43. runbooks/inventory/organizations_discovery.py +1269 -0
  44. runbooks/inventory/rich_inventory_display.py +393 -0
  45. runbooks/inventory/run_on_multi_accounts.py +35 -19
  46. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  47. runbooks/inventory/runbooks.security.run_script.log +0 -0
  48. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  49. runbooks/main.py +2215 -119
  50. runbooks/metrics/dora_metrics_engine.py +599 -0
  51. runbooks/operate/__init__.py +2 -2
  52. runbooks/operate/base.py +122 -10
  53. runbooks/operate/deployment_framework.py +1032 -0
  54. runbooks/operate/deployment_validator.py +853 -0
  55. runbooks/operate/dynamodb_operations.py +10 -6
  56. runbooks/operate/ec2_operations.py +319 -11
  57. runbooks/operate/executive_dashboard.py +779 -0
  58. runbooks/operate/mcp_integration.py +750 -0
  59. runbooks/operate/nat_gateway_operations.py +1120 -0
  60. runbooks/operate/networking_cost_heatmap.py +685 -0
  61. runbooks/operate/privatelink_operations.py +940 -0
  62. runbooks/operate/s3_operations.py +10 -6
  63. runbooks/operate/vpc_endpoints.py +644 -0
  64. runbooks/operate/vpc_operations.py +1038 -0
  65. runbooks/remediation/__init__.py +2 -2
  66. runbooks/remediation/acm_remediation.py +1 -1
  67. runbooks/remediation/base.py +1 -1
  68. runbooks/remediation/cloudtrail_remediation.py +1 -1
  69. runbooks/remediation/cognito_remediation.py +1 -1
  70. runbooks/remediation/dynamodb_remediation.py +1 -1
  71. runbooks/remediation/ec2_remediation.py +1 -1
  72. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  73. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  74. runbooks/remediation/kms_remediation.py +1 -1
  75. runbooks/remediation/lambda_remediation.py +1 -1
  76. runbooks/remediation/multi_account.py +1 -1
  77. runbooks/remediation/rds_remediation.py +1 -1
  78. runbooks/remediation/s3_block_public_access.py +1 -1
  79. runbooks/remediation/s3_enable_access_logging.py +1 -1
  80. runbooks/remediation/s3_encryption.py +1 -1
  81. runbooks/remediation/s3_remediation.py +1 -1
  82. runbooks/remediation/vpc_remediation.py +475 -0
  83. runbooks/security/__init__.py +3 -1
  84. runbooks/security/compliance_automation.py +632 -0
  85. runbooks/security/report_generator.py +10 -0
  86. runbooks/security/run_script.py +31 -5
  87. runbooks/security/security_baseline_tester.py +169 -30
  88. runbooks/security/security_export.py +477 -0
  89. runbooks/validation/__init__.py +10 -0
  90. runbooks/validation/benchmark.py +484 -0
  91. runbooks/validation/cli.py +356 -0
  92. runbooks/validation/mcp_validator.py +768 -0
  93. runbooks/vpc/__init__.py +38 -0
  94. runbooks/vpc/config.py +212 -0
  95. runbooks/vpc/cost_engine.py +347 -0
  96. runbooks/vpc/heatmap_engine.py +605 -0
  97. runbooks/vpc/manager_interface.py +634 -0
  98. runbooks/vpc/networking_wrapper.py +1260 -0
  99. runbooks/vpc/rich_formatters.py +679 -0
  100. runbooks/vpc/tests/__init__.py +5 -0
  101. runbooks/vpc/tests/conftest.py +356 -0
  102. runbooks/vpc/tests/test_cli_integration.py +530 -0
  103. runbooks/vpc/tests/test_config.py +458 -0
  104. runbooks/vpc/tests/test_cost_engine.py +479 -0
  105. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  106. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
  107. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
  108. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
  109. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
  111. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,479 @@
1
+ """
2
+ Tests for Networking Cost Engine
3
+
4
+ Tests the core cost calculation and analysis logic for VPC networking components.
5
+ """
6
+
7
+ import time
8
+ from datetime import datetime, timedelta
9
+ from unittest.mock import Mock, patch
10
+
11
+ import boto3
12
+ import pytest
13
+
14
+ from runbooks.vpc.config import VPCNetworkingConfig
15
+ from runbooks.vpc.cost_engine import NetworkingCostEngine
16
+
17
+
18
+ @pytest.mark.unit
19
+ class TestNetworkingCostEngine:
20
+ """Test Networking Cost Engine functionality."""
21
+
22
+ def test_initialization_default(self):
23
+ """Test cost engine initialization with defaults."""
24
+ engine = NetworkingCostEngine()
25
+
26
+ assert engine.session is not None
27
+ assert isinstance(engine.config, VPCNetworkingConfig)
28
+ assert engine.cost_model is not None
29
+ assert engine._cost_explorer_client is None
30
+ assert engine._cloudwatch_client is None
31
+
32
+ def test_initialization_with_parameters(self, vpc_test_config):
33
+ """Test cost engine initialization with parameters."""
34
+ mock_session = Mock(spec=boto3.Session)
35
+
36
+ engine = NetworkingCostEngine(session=mock_session, config=vpc_test_config)
37
+
38
+ assert engine.session == mock_session
39
+ assert engine.config == vpc_test_config
40
+ assert engine.cost_model == vpc_test_config.cost_model
41
+
42
+ def test_lazy_client_loading(self, networking_cost_engine):
43
+ """Test lazy loading of AWS clients."""
44
+ # Initially, clients should be None
45
+ assert networking_cost_engine._cost_explorer_client is None
46
+ assert networking_cost_engine._cloudwatch_client is None
47
+
48
+ # Mock the session client method
49
+ mock_ce_client = Mock()
50
+ mock_cw_client = Mock()
51
+
52
+ networking_cost_engine.session.client.side_effect = lambda service, **kwargs: {
53
+ "ce": mock_ce_client,
54
+ "cloudwatch": mock_cw_client,
55
+ }.get(service)
56
+
57
+ # Access properties should create clients
58
+ ce_client = networking_cost_engine.cost_explorer
59
+ cw_client = networking_cost_engine.cloudwatch
60
+
61
+ assert ce_client == mock_ce_client
62
+ assert cw_client == mock_cw_client
63
+ assert networking_cost_engine._cost_explorer_client == mock_ce_client
64
+ assert networking_cost_engine._cloudwatch_client == mock_cw_client
65
+
66
+ @pytest.mark.performance
67
+ def test_nat_gateway_cost_calculation_performance(
68
+ self, networking_cost_engine, performance_benchmarks, assert_performance_benchmark
69
+ ):
70
+ """Test NAT Gateway cost calculation performance."""
71
+ # Mock CloudWatch response
72
+ mock_cloudwatch = Mock()
73
+ mock_cloudwatch.get_metric_statistics.return_value = {
74
+ "Datapoints": [
75
+ {
76
+ "Sum": 5368709120.0, # 5 GB in bytes
77
+ "Average": 100.0,
78
+ "Maximum": 150.0,
79
+ }
80
+ ]
81
+ }
82
+
83
+ networking_cost_engine._cloudwatch_client = mock_cloudwatch
84
+
85
+ start_time = time.time()
86
+ result = networking_cost_engine.calculate_nat_gateway_cost("nat-0123456789abcdef0", days=30)
87
+ execution_time = time.time() - start_time
88
+
89
+ # Assert performance benchmark
90
+ assert_performance_benchmark(execution_time, "cost_calculation_max_time", performance_benchmarks)
91
+
92
+ # Validate result structure
93
+ assert "nat_gateway_id" in result
94
+ assert "base_cost" in result
95
+ assert "data_processing_cost" in result
96
+ assert "total_cost" in result
97
+
98
+ def test_nat_gateway_cost_calculation_basic(self, networking_cost_engine):
99
+ """Test basic NAT Gateway cost calculation without data processing."""
100
+ result = networking_cost_engine.calculate_nat_gateway_cost(
101
+ "nat-0123456789abcdef0", days=30, include_data_processing=False
102
+ )
103
+
104
+ # Validate basic cost calculation
105
+ expected_base_cost = 0.045 * 24 * 30 # hourly rate * hours * days
106
+
107
+ assert result["nat_gateway_id"] == "nat-0123456789abcdef0"
108
+ assert result["period_days"] == 30
109
+ assert result["base_cost"] == expected_base_cost
110
+ assert result["data_processing_cost"] == 0.0
111
+ assert result["total_cost"] == expected_base_cost
112
+ assert result["daily_average"] == expected_base_cost / 30
113
+ assert result["monthly_projection"] == expected_base_cost
114
+
115
+ def test_nat_gateway_cost_calculation_with_data_processing(self, networking_cost_engine):
116
+ """Test NAT Gateway cost calculation with data processing."""
117
+ # Mock CloudWatch response with data processing metrics
118
+ mock_cloudwatch = Mock()
119
+ mock_cloudwatch.get_metric_statistics.return_value = {
120
+ "Datapoints": [
121
+ {
122
+ "Sum": 10737418240.0, # 10 GB in bytes
123
+ "Average": 100.0,
124
+ "Maximum": 150.0,
125
+ }
126
+ ]
127
+ }
128
+
129
+ networking_cost_engine._cloudwatch_client = mock_cloudwatch
130
+
131
+ result = networking_cost_engine.calculate_nat_gateway_cost(
132
+ "nat-0123456789abcdef0", days=30, include_data_processing=True
133
+ )
134
+
135
+ # Validate cost calculation with data processing
136
+ expected_base_cost = 0.045 * 24 * 30
137
+ expected_data_cost = 10.0 * 0.045 # 10 GB * $0.045/GB
138
+ expected_total = expected_base_cost + expected_data_cost
139
+
140
+ assert result["base_cost"] == expected_base_cost
141
+ assert result["data_processing_cost"] == expected_data_cost
142
+ assert result["total_cost"] == expected_total
143
+ assert result["monthly_projection"] == expected_total
144
+
145
+ def test_nat_gateway_cost_with_cloudwatch_error(self, networking_cost_engine):
146
+ """Test NAT Gateway cost calculation when CloudWatch fails."""
147
+ # Mock CloudWatch to raise exception
148
+ mock_cloudwatch = Mock()
149
+ mock_cloudwatch.get_metric_statistics.side_effect = Exception("CloudWatch API Error")
150
+
151
+ networking_cost_engine._cloudwatch_client = mock_cloudwatch
152
+
153
+ result = networking_cost_engine.calculate_nat_gateway_cost(
154
+ "nat-0123456789abcdef0", days=30, include_data_processing=True
155
+ )
156
+
157
+ # Should still calculate base cost, but no data processing cost
158
+ expected_base_cost = 0.045 * 24 * 30
159
+
160
+ assert result["base_cost"] == expected_base_cost
161
+ assert result["data_processing_cost"] == 0.0
162
+ assert result["total_cost"] == expected_base_cost
163
+
164
+ def test_vpc_endpoint_cost_calculation_interface(self, networking_cost_engine):
165
+ """Test VPC Endpoint cost calculation for Interface endpoints."""
166
+ result = networking_cost_engine.calculate_vpc_endpoint_cost(
167
+ endpoint_type="Interface", availability_zones=2, data_processed_gb=100.0
168
+ )
169
+
170
+ # Validate Interface endpoint cost calculation
171
+ expected_base_cost = 10.0 * 2 # $10/month * 2 AZs
172
+ expected_data_cost = 100.0 * 0.01 # 100 GB * $0.01/GB
173
+ expected_total = expected_base_cost + expected_data_cost
174
+
175
+ assert result["endpoint_type"] == "Interface"
176
+ assert result["availability_zones"] == 2
177
+ assert result["data_processed_gb"] == 100.0
178
+ assert result["base_cost"] == expected_base_cost
179
+ assert result["data_processing_cost"] == expected_data_cost
180
+ assert result["total_monthly_cost"] == expected_total
181
+
182
+ def test_vpc_endpoint_cost_calculation_gateway(self, networking_cost_engine):
183
+ """Test VPC Endpoint cost calculation for Gateway endpoints."""
184
+ result = networking_cost_engine.calculate_vpc_endpoint_cost(
185
+ endpoint_type="Gateway", availability_zones=0, data_processed_gb=1000.0
186
+ )
187
+
188
+ # Gateway endpoints are always free
189
+ assert result["endpoint_type"] == "Gateway"
190
+ assert result["base_cost"] == 0.0
191
+ assert result["data_processing_cost"] == 0.0
192
+ assert result["total_monthly_cost"] == 0.0
193
+
194
+ def test_transit_gateway_cost_calculation(self, networking_cost_engine):
195
+ """Test Transit Gateway cost calculation."""
196
+ result = networking_cost_engine.calculate_transit_gateway_cost(attachments=5, data_processed_gb=500.0, days=30)
197
+
198
+ # Validate Transit Gateway cost calculation
199
+ expected_base_cost = 0.05 * 24 * 30 # $0.05/hour * 24h * 30 days
200
+ expected_attachment_cost = 0.05 * 24 * 30 * 5 # $0.05/hour * 24h * 30 days * 5 attachments
201
+ expected_data_cost = 500.0 * 0.02 # 500 GB * $0.02/GB
202
+ expected_total = expected_base_cost + expected_attachment_cost + expected_data_cost
203
+ expected_monthly = expected_total # Already calculated for 30 days
204
+
205
+ assert result["attachments"] == 5
206
+ assert result["data_processed_gb"] == 500.0
207
+ assert result["base_cost"] == expected_base_cost
208
+ assert result["attachment_cost"] == expected_attachment_cost
209
+ assert result["data_processing_cost"] == expected_data_cost
210
+ assert result["total_cost"] == expected_total
211
+ assert result["monthly_projection"] == expected_monthly
212
+
213
+ def test_elastic_ip_cost_calculation(self, networking_cost_engine):
214
+ """Test Elastic IP cost calculation."""
215
+ result = networking_cost_engine.calculate_elastic_ip_cost(
216
+ idle_hours=720, # 30 days
217
+ remaps=5,
218
+ )
219
+
220
+ # Validate Elastic IP cost calculation
221
+ expected_idle_cost = 720 * 0.005 # 720 hours * $0.005/hour
222
+ expected_remap_cost = 5 * 0.10 # 5 remaps * $0.10/remap
223
+ expected_total = expected_idle_cost + expected_remap_cost
224
+ expected_monthly = expected_total # 720 hours = 30 days
225
+
226
+ assert result["idle_hours"] == 720
227
+ assert result["remaps"] == 5
228
+ assert result["idle_cost"] == expected_idle_cost
229
+ assert result["remap_cost"] == expected_remap_cost
230
+ assert result["total_cost"] == expected_total
231
+ assert result["monthly_projection"] == expected_monthly
232
+
233
+ def test_elastic_ip_cost_with_no_idle_time(self, networking_cost_engine):
234
+ """Test Elastic IP cost calculation with no idle time."""
235
+ result = networking_cost_engine.calculate_elastic_ip_cost(idle_hours=0, remaps=3)
236
+
237
+ expected_remap_cost = 3 * 0.10
238
+
239
+ assert result["idle_cost"] == 0.0
240
+ assert result["remap_cost"] == expected_remap_cost
241
+ assert result["total_cost"] == expected_remap_cost
242
+ assert result["monthly_projection"] == expected_remap_cost
243
+
244
+ def test_data_transfer_cost_calculation(self, networking_cost_engine):
245
+ """Test data transfer cost calculation."""
246
+ result = networking_cost_engine.calculate_data_transfer_cost(
247
+ inter_az_gb=1000.0, inter_region_gb=500.0, internet_out_gb=200.0
248
+ )
249
+
250
+ # Validate data transfer cost calculation
251
+ expected_inter_az = 1000.0 * 0.01 # 1000 GB * $0.01/GB
252
+ expected_inter_region = 500.0 * 0.02 # 500 GB * $0.02/GB
253
+ expected_internet_out = 200.0 * 0.09 # 200 GB * $0.09/GB
254
+ expected_total = expected_inter_az + expected_inter_region + expected_internet_out
255
+
256
+ assert result["inter_az_gb"] == 1000.0
257
+ assert result["inter_region_gb"] == 500.0
258
+ assert result["internet_out_gb"] == 200.0
259
+ assert result["inter_az_cost"] == expected_inter_az
260
+ assert result["inter_region_cost"] == expected_inter_region
261
+ assert result["internet_out_cost"] == expected_internet_out
262
+ assert result["total_cost"] == expected_total
263
+
264
+ def test_get_actual_costs_from_cost_explorer(self, networking_cost_engine, mock_cost_explorer_responses):
265
+ """Test getting actual costs from Cost Explorer."""
266
+ # Mock Cost Explorer response
267
+ mock_ce = Mock()
268
+ mock_ce.get_cost_and_usage.return_value = mock_cost_explorer_responses["vpc_costs"]
269
+
270
+ networking_cost_engine._cost_explorer_client = mock_ce
271
+
272
+ result = networking_cost_engine.get_actual_costs_from_cost_explorer(
273
+ service="Amazon Virtual Private Cloud",
274
+ start_date="2024-01-01",
275
+ end_date="2024-01-31",
276
+ granularity="MONTHLY",
277
+ )
278
+
279
+ # Validate Cost Explorer result
280
+ assert result["service"] == "Amazon Virtual Private Cloud"
281
+ assert result["granularity"] == "MONTHLY"
282
+ assert result["total_cost"] == 145.67
283
+ assert len(result["results_by_time"]) == 1
284
+
285
+ # Validate API call
286
+ mock_ce.get_cost_and_usage.assert_called_once()
287
+ call_args = mock_ce.get_cost_and_usage.call_args[1]
288
+ assert call_args["TimePeriod"]["Start"] == "2024-01-01"
289
+ assert call_args["TimePeriod"]["End"] == "2024-01-31"
290
+ assert call_args["Granularity"] == "MONTHLY"
291
+
292
+ def test_cost_explorer_error_handling(self, networking_cost_engine):
293
+ """Test Cost Explorer error handling."""
294
+ # Mock Cost Explorer to raise exception
295
+ mock_ce = Mock()
296
+ mock_ce.get_cost_and_usage.side_effect = Exception("Cost Explorer API Error")
297
+
298
+ networking_cost_engine._cost_explorer_client = mock_ce
299
+
300
+ result = networking_cost_engine.get_actual_costs_from_cost_explorer(
301
+ service="Amazon Virtual Private Cloud", start_date="2024-01-01", end_date="2024-01-31"
302
+ )
303
+
304
+ # Should return error result
305
+ assert result["service"] == "Amazon Virtual Private Cloud"
306
+ assert "error" in result
307
+ assert result["total_cost"] == 0.0
308
+
309
+ def test_estimate_optimization_savings(self, networking_cost_engine):
310
+ """Test optimization savings estimation."""
311
+ current_costs = {"nat_gateways": 200.0, "vpc_endpoints": 50.0, "elastic_ips": 10.0}
312
+
313
+ optimization_scenarios = [
314
+ {
315
+ "name": "Conservative",
316
+ "description": "Remove idle resources",
317
+ "reductions": {
318
+ "nat_gateways": 20, # 20% reduction
319
+ "elastic_ips": 50, # 50% reduction
320
+ },
321
+ "risk_level": "low",
322
+ "effort": "low",
323
+ },
324
+ {
325
+ "name": "Aggressive",
326
+ "description": "Comprehensive optimization",
327
+ "reductions": {
328
+ "nat_gateways": 40, # 40% reduction
329
+ "vpc_endpoints": 25, # 25% reduction
330
+ "elastic_ips": 80, # 80% reduction
331
+ },
332
+ "risk_level": "high",
333
+ "effort": "high",
334
+ },
335
+ ]
336
+
337
+ result = networking_cost_engine.estimate_optimization_savings(current_costs, optimization_scenarios)
338
+
339
+ # Validate savings estimation
340
+ assert result["current_monthly_cost"] == 260.0 # Sum of current costs
341
+ assert len(result["scenarios"]) == 2
342
+
343
+ # Validate conservative scenario
344
+ conservative = next(s for s in result["scenarios"] if s["name"] == "Conservative")
345
+ expected_conservative_savings = (200.0 * 0.20) + (10.0 * 0.50) # 40 + 5 = 45
346
+ assert conservative["monthly_savings"] == expected_conservative_savings
347
+ assert conservative["annual_savings"] == expected_conservative_savings * 12
348
+
349
+ # Validate aggressive scenario
350
+ aggressive = next(s for s in result["scenarios"] if s["name"] == "Aggressive")
351
+ expected_aggressive_savings = (200.0 * 0.40) + (50.0 * 0.25) + (10.0 * 0.80) # 80 + 12.5 + 8 = 100.5
352
+ assert aggressive["monthly_savings"] == expected_aggressive_savings
353
+
354
+ # Validate recommended scenario (should be the one with maximum savings)
355
+ assert result["recommended_scenario"]["name"] == "Aggressive"
356
+ assert result["maximum_savings"] == expected_aggressive_savings
357
+
358
+ def test_estimate_optimization_with_no_scenarios(self, networking_cost_engine):
359
+ """Test optimization estimation with no scenarios."""
360
+ current_costs = {"nat_gateways": 100.0}
361
+ optimization_scenarios = []
362
+
363
+ result = networking_cost_engine.estimate_optimization_savings(current_costs, optimization_scenarios)
364
+
365
+ assert result["current_monthly_cost"] == 100.0
366
+ assert len(result["scenarios"]) == 0
367
+ assert result["recommended_scenario"] is None
368
+ assert result["maximum_savings"] == 0.0
369
+
370
+ def test_estimate_optimization_with_empty_costs(self, networking_cost_engine):
371
+ """Test optimization estimation with empty current costs."""
372
+ current_costs = {}
373
+ optimization_scenarios = [{"name": "Test", "reductions": {"nonexistent": 50}}]
374
+
375
+ result = networking_cost_engine.estimate_optimization_savings(current_costs, optimization_scenarios)
376
+
377
+ assert result["current_monthly_cost"] == 0.0
378
+ assert len(result["scenarios"]) == 1
379
+ assert result["scenarios"][0]["monthly_savings"] == 0.0
380
+
381
+ @pytest.mark.integration
382
+ def test_cost_calculation_consistency(self, networking_cost_engine):
383
+ """Test consistency across different cost calculation methods."""
384
+ # Calculate costs using different methods and ensure consistency
385
+
386
+ # NAT Gateway costs for different periods
387
+ nat_cost_30_days = networking_cost_engine.calculate_nat_gateway_cost(
388
+ "nat-test", days=30, include_data_processing=False
389
+ )
390
+
391
+ nat_cost_60_days = networking_cost_engine.calculate_nat_gateway_cost(
392
+ "nat-test", days=60, include_data_processing=False
393
+ )
394
+
395
+ # Daily average should be consistent
396
+ daily_30 = nat_cost_30_days["daily_average"]
397
+ daily_60 = nat_cost_60_days["daily_average"]
398
+
399
+ assert abs(daily_30 - daily_60) < 0.01, "Daily averages should be consistent across periods"
400
+
401
+ # Total cost should scale linearly
402
+ expected_60_day_cost = nat_cost_30_days["base_cost"] * 2
403
+ assert abs(nat_cost_60_days["base_cost"] - expected_60_day_cost) < 0.01
404
+
405
+ @pytest.mark.performance
406
+ def test_cost_engine_memory_efficiency(self, networking_cost_engine):
407
+ """Test cost engine memory efficiency with large datasets."""
408
+ import sys
409
+
410
+ initial_size = sys.getsizeof(networking_cost_engine.__dict__)
411
+
412
+ # Perform multiple calculations
413
+ for i in range(100):
414
+ networking_cost_engine.calculate_nat_gateway_cost(f"nat-{i}", days=30, include_data_processing=False)
415
+
416
+ final_size = sys.getsizeof(networking_cost_engine.__dict__)
417
+
418
+ # Memory usage should not grow significantly
419
+ growth = final_size - initial_size
420
+ assert growth < 10000, f"Memory usage grew too much: {growth} bytes"
421
+
422
+ @pytest.mark.security
423
+ def test_cost_calculation_input_validation(self, networking_cost_engine):
424
+ """Test cost calculation input validation and sanitization."""
425
+ # Test with potentially malicious inputs
426
+ malicious_inputs = [
427
+ "nat-0123456789abcdef0; rm -rf /",
428
+ "nat-0123456789abcdef0 && malicious_command",
429
+ "nat-$(curl malicious-site.com)",
430
+ ]
431
+
432
+ for malicious_input in malicious_inputs:
433
+ # Should not raise exception and should handle input safely
434
+ result = networking_cost_engine.calculate_nat_gateway_cost(
435
+ malicious_input, days=30, include_data_processing=False
436
+ )
437
+
438
+ # Verify malicious input is stored as-is but doesn't cause execution
439
+ assert result["nat_gateway_id"] == malicious_input
440
+ assert isinstance(result["total_cost"], (int, float))
441
+
442
+ @pytest.mark.integration
443
+ def test_full_cost_analysis_workflow(self, networking_cost_engine):
444
+ """Test complete cost analysis workflow."""
445
+ # Mock all required services
446
+ mock_cloudwatch = Mock()
447
+ mock_cost_explorer = Mock()
448
+
449
+ # Configure CloudWatch mock
450
+ mock_cloudwatch.get_metric_statistics.return_value = {
451
+ "Datapoints": [{"Sum": 5368709120.0, "Average": 100.0, "Maximum": 150.0}]
452
+ }
453
+
454
+ # Configure Cost Explorer mock
455
+ mock_cost_explorer.get_cost_and_usage.return_value = {
456
+ "ResultsByTime": [
457
+ {
458
+ "TimePeriod": {"Start": "2024-01-01", "End": "2024-01-31"},
459
+ "Total": {"BlendedCost": {"Amount": "123.45", "Unit": "USD"}},
460
+ }
461
+ ]
462
+ }
463
+
464
+ networking_cost_engine._cloudwatch_client = mock_cloudwatch
465
+ networking_cost_engine._cost_explorer_client = mock_cost_explorer
466
+
467
+ # Execute full workflow
468
+ nat_costs = networking_cost_engine.calculate_nat_gateway_cost("nat-test", days=30)
469
+ vpc_costs = networking_cost_engine.calculate_vpc_endpoint_cost("Interface", 2, 100.0)
470
+ actual_costs = networking_cost_engine.get_actual_costs_from_cost_explorer("VPC", "2024-01-01", "2024-01-31")
471
+
472
+ # Validate workflow results
473
+ assert nat_costs["total_cost"] > 0
474
+ assert vpc_costs["total_monthly_cost"] > 0
475
+ assert actual_costs["total_cost"] == 123.45
476
+
477
+ # Validate cost relationships
478
+ total_calculated = nat_costs["total_cost"] + vpc_costs["total_monthly_cost"]
479
+ assert total_calculated > 0