runbooks 0.7.7__py3-none-any.whl → 0.9.0__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.
- runbooks/__init__.py +1 -1
- runbooks/base.py +2 -2
- runbooks/cfat/README.md +12 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +546 -522
- runbooks/cfat/assessment/runner.py +129 -10
- runbooks/cfat/models.py +6 -2
- runbooks/common/__init__.py +152 -0
- runbooks/common/accuracy_validator.py +1039 -0
- runbooks/common/context_logger.py +440 -0
- runbooks/common/cross_module_integration.py +594 -0
- runbooks/common/enhanced_exception_handler.py +1108 -0
- runbooks/common/enterprise_audit_integration.py +634 -0
- runbooks/common/logger.py +14 -0
- runbooks/common/mcp_integration.py +539 -0
- runbooks/common/performance_monitor.py +387 -0
- runbooks/common/profile_utils.py +216 -0
- runbooks/common/rich_utils.py +622 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/feedback/user_feedback_collector.py +440 -0
- runbooks/finops/README.md +129 -14
- runbooks/finops/__init__.py +22 -3
- runbooks/finops/account_resolver.py +279 -0
- runbooks/finops/accuracy_cross_validator.py +638 -0
- runbooks/finops/aws_client.py +721 -36
- runbooks/finops/budget_integration.py +313 -0
- runbooks/finops/cli.py +90 -33
- runbooks/finops/cost_processor.py +211 -37
- runbooks/finops/dashboard_router.py +900 -0
- runbooks/finops/dashboard_runner.py +1334 -399
- runbooks/finops/embedded_mcp_validator.py +288 -0
- runbooks/finops/enhanced_dashboard_runner.py +526 -0
- runbooks/finops/enhanced_progress.py +327 -0
- runbooks/finops/enhanced_trend_visualization.py +423 -0
- runbooks/finops/finops_dashboard.py +41 -0
- runbooks/finops/helpers.py +639 -323
- runbooks/finops/iam_guidance.py +400 -0
- runbooks/finops/markdown_exporter.py +466 -0
- runbooks/finops/multi_dashboard.py +1502 -0
- runbooks/finops/optimizer.py +396 -395
- runbooks/finops/profile_processor.py +2 -2
- runbooks/finops/runbooks.inventory.organizations_discovery.log +0 -0
- runbooks/finops/runbooks.security.report_generator.log +0 -0
- runbooks/finops/runbooks.security.run_script.log +0 -0
- runbooks/finops/runbooks.security.security_export.log +0 -0
- runbooks/finops/service_mapping.py +195 -0
- runbooks/finops/single_dashboard.py +710 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/README.md +12 -1
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +192 -185
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +299 -12
- runbooks/inventory/list_ec2_instances.py +21 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1315 -0
- runbooks/inventory/rich_inventory_display.py +360 -0
- runbooks/inventory/run_on_multi_accounts.py +32 -16
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +4171 -1615
- runbooks/metrics/dora_metrics_engine.py +1293 -0
- runbooks/monitoring/performance_monitor.py +433 -0
- runbooks/operate/README.md +394 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +291 -11
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +321 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/README.md +489 -13
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/commons.py +8 -4
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +506 -0
- runbooks/security/README.md +12 -1
- runbooks/security/__init__.py +166 -33
- runbooks/security/compliance_automation.py +634 -0
- runbooks/security/compliance_automation_engine.py +1021 -0
- runbooks/security/enterprise_security_framework.py +931 -0
- runbooks/security/enterprise_security_policies.json +293 -0
- runbooks/security/integration_test_enterprise_security.py +879 -0
- runbooks/security/module_security_integrator.py +641 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +27 -5
- runbooks/security/security_baseline_tester.py +153 -27
- runbooks/security/security_export.py +456 -0
- runbooks/sre/README.md +472 -0
- runbooks/sre/__init__.py +33 -0
- runbooks/sre/mcp_reliability_engine.py +1049 -0
- runbooks/sre/performance_optimization_engine.py +1032 -0
- runbooks/sre/reliability_monitoring_framework.py +1011 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +489 -0
- runbooks/validation/cli.py +368 -0
- runbooks/validation/mcp_validator.py +797 -0
- runbooks/vpc/README.md +478 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +649 -0
- runbooks/vpc/networking_wrapper.py +1289 -0
- runbooks/vpc/rich_formatters.py +693 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/METADATA +175 -65
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/RECORD +157 -60
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/entry_points.txt +1 -1
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/WHEEL +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.7.dist-info → runbooks-0.9.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,512 @@
|
|
1
|
+
"""
|
2
|
+
Comprehensive tests for VPC Networking Wrapper
|
3
|
+
|
4
|
+
Tests the main VPCNetworkingWrapper class with comprehensive coverage
|
5
|
+
of all networking analysis and optimization functionality.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import time
|
10
|
+
from datetime import datetime
|
11
|
+
from pathlib import Path
|
12
|
+
from unittest.mock import MagicMock, Mock, patch
|
13
|
+
|
14
|
+
import pytest
|
15
|
+
from rich.console import Console
|
16
|
+
|
17
|
+
from runbooks.vpc.networking_wrapper import VPCNetworkingWrapper
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.mark.unit
|
21
|
+
class TestVPCNetworkingWrapper:
|
22
|
+
"""Test VPC Networking Wrapper functionality."""
|
23
|
+
|
24
|
+
def test_initialization_default(self):
|
25
|
+
"""Test VPC wrapper initialization with default parameters."""
|
26
|
+
wrapper = VPCNetworkingWrapper()
|
27
|
+
|
28
|
+
assert wrapper.profile is None
|
29
|
+
assert wrapper.region == "us-east-1"
|
30
|
+
assert wrapper.billing_profile is None
|
31
|
+
assert wrapper.output_format == "rich"
|
32
|
+
assert isinstance(wrapper.console, Console)
|
33
|
+
assert wrapper.session is None
|
34
|
+
assert wrapper.last_results == {}
|
35
|
+
|
36
|
+
def test_initialization_with_parameters(self, mock_console):
|
37
|
+
"""Test VPC wrapper initialization with custom parameters."""
|
38
|
+
wrapper = VPCNetworkingWrapper(
|
39
|
+
profile="test-profile",
|
40
|
+
region="us-west-2",
|
41
|
+
billing_profile="billing-profile",
|
42
|
+
output_format="json",
|
43
|
+
console=mock_console,
|
44
|
+
)
|
45
|
+
|
46
|
+
assert wrapper.profile == "test-profile"
|
47
|
+
assert wrapper.region == "us-west-2"
|
48
|
+
assert wrapper.billing_profile == "billing-profile"
|
49
|
+
assert wrapper.output_format == "json"
|
50
|
+
assert wrapper.console == mock_console
|
51
|
+
|
52
|
+
def test_initialization_with_aws_session(self, mock_console):
|
53
|
+
"""Test VPC wrapper initialization with valid AWS session."""
|
54
|
+
with patch("runbooks.vpc.networking_wrapper.boto3.Session") as mock_session:
|
55
|
+
mock_session_instance = Mock()
|
56
|
+
mock_session.return_value = mock_session_instance
|
57
|
+
|
58
|
+
wrapper = VPCNetworkingWrapper(profile="test-profile", console=mock_console)
|
59
|
+
|
60
|
+
assert wrapper.session == mock_session_instance
|
61
|
+
mock_console.print.assert_called_with("✅ Connected to AWS profile: test-profile", style="green")
|
62
|
+
|
63
|
+
def test_initialization_with_invalid_aws_session(self, mock_console):
|
64
|
+
"""Test VPC wrapper initialization with invalid AWS session."""
|
65
|
+
with patch("runbooks.vpc.networking_wrapper.boto3.Session") as mock_session:
|
66
|
+
mock_session.side_effect = Exception("Invalid credentials")
|
67
|
+
|
68
|
+
wrapper = VPCNetworkingWrapper(profile="invalid-profile", console=mock_console)
|
69
|
+
|
70
|
+
assert wrapper.session is None
|
71
|
+
mock_console.print.assert_called_with("⚠️ Failed to connect to AWS: Invalid credentials", style="yellow")
|
72
|
+
|
73
|
+
@pytest.mark.performance
|
74
|
+
def test_analyze_nat_gateways_performance(
|
75
|
+
self, vpc_networking_wrapper, performance_benchmarks, assert_performance_benchmark
|
76
|
+
):
|
77
|
+
"""Test NAT Gateway analysis performance benchmark."""
|
78
|
+
# Mock AWS clients and responses
|
79
|
+
mock_ec2_client = Mock()
|
80
|
+
mock_cloudwatch_client = Mock()
|
81
|
+
|
82
|
+
# Mock NAT Gateway response
|
83
|
+
mock_ec2_client.describe_nat_gateways.return_value = {
|
84
|
+
"NatGateways": [
|
85
|
+
{
|
86
|
+
"NatGatewayId": "nat-0123456789abcdef0",
|
87
|
+
"State": "available",
|
88
|
+
"VpcId": "vpc-0123456789abcdef0",
|
89
|
+
"SubnetId": "subnet-0123456789abcdef0",
|
90
|
+
}
|
91
|
+
]
|
92
|
+
}
|
93
|
+
|
94
|
+
# Mock CloudWatch response
|
95
|
+
mock_cloudwatch_client.get_metric_statistics.return_value = {
|
96
|
+
"Datapoints": [
|
97
|
+
{
|
98
|
+
"Timestamp": datetime.now(),
|
99
|
+
"Average": 50.0,
|
100
|
+
"Maximum": 100.0,
|
101
|
+
"Sum": 1073741824.0, # 1 GB in bytes
|
102
|
+
"Unit": "Count",
|
103
|
+
}
|
104
|
+
]
|
105
|
+
}
|
106
|
+
|
107
|
+
# Configure mock session to return our mocked clients
|
108
|
+
vpc_networking_wrapper.session.client.side_effect = lambda service: {
|
109
|
+
"ec2": mock_ec2_client,
|
110
|
+
"cloudwatch": mock_cloudwatch_client,
|
111
|
+
}.get(service)
|
112
|
+
|
113
|
+
# Measure execution time
|
114
|
+
start_time = time.time()
|
115
|
+
result = vpc_networking_wrapper.analyze_nat_gateways(days=30)
|
116
|
+
execution_time = time.time() - start_time
|
117
|
+
|
118
|
+
# Assert performance benchmark
|
119
|
+
assert_performance_benchmark(execution_time, "nat_gateway_analysis_max_time", performance_benchmarks)
|
120
|
+
|
121
|
+
# Validate result structure
|
122
|
+
assert isinstance(result, dict)
|
123
|
+
assert "nat_gateways" in result
|
124
|
+
assert "total_cost" in result
|
125
|
+
assert "optimization_potential" in result
|
126
|
+
assert "recommendations" in result
|
127
|
+
|
128
|
+
def test_analyze_nat_gateways_no_session(self, vpc_networking_wrapper, validate_vpc_structure):
|
129
|
+
"""Test NAT Gateway analysis without AWS session."""
|
130
|
+
# Ensure no session
|
131
|
+
vpc_networking_wrapper.session = None
|
132
|
+
|
133
|
+
result = vpc_networking_wrapper.analyze_nat_gateways()
|
134
|
+
|
135
|
+
# Validate structure
|
136
|
+
expected_keys = ["nat_gateways", "total_cost", "optimization_potential", "recommendations"]
|
137
|
+
validate_vpc_structure(result, expected_keys)
|
138
|
+
|
139
|
+
# Should return empty results
|
140
|
+
assert len(result["nat_gateways"]) == 0
|
141
|
+
assert result["total_cost"] == 0
|
142
|
+
assert result["optimization_potential"] == 0
|
143
|
+
|
144
|
+
def test_analyze_nat_gateways_with_data(self, vpc_networking_wrapper, sample_nat_gateways):
|
145
|
+
"""Test NAT Gateway analysis with sample data."""
|
146
|
+
mock_ec2_client = Mock()
|
147
|
+
mock_cloudwatch_client = Mock()
|
148
|
+
|
149
|
+
# Mock responses
|
150
|
+
mock_ec2_client.describe_nat_gateways.return_value = {"NatGateways": sample_nat_gateways}
|
151
|
+
|
152
|
+
mock_cloudwatch_client.get_metric_statistics.return_value = {
|
153
|
+
"Datapoints": [
|
154
|
+
{
|
155
|
+
"Sum": 5368709120.0, # 5 GB
|
156
|
+
"Average": 150.0,
|
157
|
+
"Maximum": 200.0,
|
158
|
+
}
|
159
|
+
]
|
160
|
+
}
|
161
|
+
|
162
|
+
vpc_networking_wrapper.session.client.side_effect = lambda service: {
|
163
|
+
"ec2": mock_ec2_client,
|
164
|
+
"cloudwatch": mock_cloudwatch_client,
|
165
|
+
}.get(service)
|
166
|
+
|
167
|
+
result = vpc_networking_wrapper.analyze_nat_gateways()
|
168
|
+
|
169
|
+
# Validate results
|
170
|
+
assert len(result["nat_gateways"]) == 2
|
171
|
+
assert result["total_cost"] > 0
|
172
|
+
|
173
|
+
# Validate individual NAT Gateway analysis
|
174
|
+
for ng in result["nat_gateways"]:
|
175
|
+
assert "id" in ng
|
176
|
+
assert "monthly_cost" in ng
|
177
|
+
assert "usage" in ng
|
178
|
+
assert "optimization" in ng
|
179
|
+
|
180
|
+
def test_analyze_vpc_endpoints_performance(
|
181
|
+
self, vpc_networking_wrapper, performance_benchmarks, assert_performance_benchmark
|
182
|
+
):
|
183
|
+
"""Test VPC Endpoint analysis performance benchmark."""
|
184
|
+
mock_ec2_client = Mock()
|
185
|
+
mock_ec2_client.describe_vpc_endpoints.return_value = {
|
186
|
+
"VpcEndpoints": [
|
187
|
+
{
|
188
|
+
"VpcEndpointId": "vpce-0123456789abcdef0",
|
189
|
+
"VpcEndpointType": "Interface",
|
190
|
+
"ServiceName": "com.amazonaws.us-east-1.s3",
|
191
|
+
"VpcId": "vpc-0123456789abcdef0",
|
192
|
+
"State": "available",
|
193
|
+
"SubnetIds": ["subnet-1", "subnet-2"],
|
194
|
+
}
|
195
|
+
]
|
196
|
+
}
|
197
|
+
|
198
|
+
vpc_networking_wrapper.session.client.return_value = mock_ec2_client
|
199
|
+
|
200
|
+
start_time = time.time()
|
201
|
+
result = vpc_networking_wrapper.analyze_vpc_endpoints()
|
202
|
+
execution_time = time.time() - start_time
|
203
|
+
|
204
|
+
# Assert performance benchmark
|
205
|
+
assert_performance_benchmark(execution_time, "vpc_endpoint_analysis_max_time", performance_benchmarks)
|
206
|
+
|
207
|
+
# Validate result
|
208
|
+
assert isinstance(result, dict)
|
209
|
+
assert "vpc_endpoints" in result
|
210
|
+
|
211
|
+
def test_analyze_vpc_endpoints_with_data(self, vpc_networking_wrapper, sample_vpc_endpoints):
|
212
|
+
"""Test VPC Endpoint analysis with sample data."""
|
213
|
+
mock_ec2_client = Mock()
|
214
|
+
mock_ec2_client.describe_vpc_endpoints.return_value = {"VpcEndpoints": sample_vpc_endpoints}
|
215
|
+
|
216
|
+
vpc_networking_wrapper.session.client.return_value = mock_ec2_client
|
217
|
+
|
218
|
+
result = vpc_networking_wrapper.analyze_vpc_endpoints()
|
219
|
+
|
220
|
+
# Validate results
|
221
|
+
assert len(result["vpc_endpoints"]) == 2
|
222
|
+
|
223
|
+
# Check Interface endpoint cost calculation
|
224
|
+
interface_endpoint = next(ep for ep in result["vpc_endpoints"] if ep["type"] == "Interface")
|
225
|
+
assert interface_endpoint["monthly_cost"] > 0 # Interface endpoints have costs
|
226
|
+
|
227
|
+
# Check Gateway endpoint cost calculation
|
228
|
+
gateway_endpoint = next(ep for ep in result["vpc_endpoints"] if ep["type"] == "Gateway")
|
229
|
+
assert gateway_endpoint["monthly_cost"] == 0 # Gateway endpoints are free
|
230
|
+
|
231
|
+
def test_generate_cost_heatmaps(self, vpc_networking_wrapper):
|
232
|
+
"""Test cost heatmap generation."""
|
233
|
+
# Mock heatmap engine
|
234
|
+
mock_heatmap_engine = Mock()
|
235
|
+
mock_heatmap_data = {
|
236
|
+
"heatmap_data": "sample_data",
|
237
|
+
"regions": ["us-east-1", "us-west-2"],
|
238
|
+
"cost_breakdown": {"nat_gateways": 100, "vpc_endpoints": 50},
|
239
|
+
}
|
240
|
+
mock_heatmap_engine.generate_comprehensive_heat_maps.return_value = mock_heatmap_data
|
241
|
+
|
242
|
+
with patch("runbooks.vpc.networking_wrapper.NetworkingCostHeatMapEngine") as mock_engine_class:
|
243
|
+
mock_engine_class.return_value = mock_heatmap_engine
|
244
|
+
|
245
|
+
result = vpc_networking_wrapper.generate_cost_heatmaps()
|
246
|
+
|
247
|
+
assert result == mock_heatmap_data
|
248
|
+
assert vpc_networking_wrapper.last_results["heat_maps"] == mock_heatmap_data
|
249
|
+
|
250
|
+
def test_optimize_networking_costs(self, vpc_networking_wrapper):
|
251
|
+
"""Test networking cost optimization recommendations."""
|
252
|
+
# Mock analyze methods to return sample data
|
253
|
+
vpc_networking_wrapper.analyze_nat_gateways = Mock(
|
254
|
+
return_value={
|
255
|
+
"total_cost": 100.0,
|
256
|
+
"optimization_potential": 30.0,
|
257
|
+
"recommendations": [
|
258
|
+
{
|
259
|
+
"type": "NAT Gateway",
|
260
|
+
"potential_savings": 30.0,
|
261
|
+
"risk_level": "low",
|
262
|
+
"action": "Remove unused NAT Gateway",
|
263
|
+
"resource_id": "nat-123456",
|
264
|
+
"implementation_effort": "low",
|
265
|
+
}
|
266
|
+
],
|
267
|
+
}
|
268
|
+
)
|
269
|
+
|
270
|
+
vpc_networking_wrapper.analyze_vpc_endpoints = Mock(
|
271
|
+
return_value={
|
272
|
+
"total_cost": 50.0,
|
273
|
+
"optimization_potential": 10.0,
|
274
|
+
"recommendations": [
|
275
|
+
{
|
276
|
+
"type": "VPC Endpoint",
|
277
|
+
"potential_savings": 10.0,
|
278
|
+
"risk_level": "low",
|
279
|
+
"action": "Optimize endpoint configuration",
|
280
|
+
"resource_id": "vpce-123456",
|
281
|
+
"implementation_effort": "medium",
|
282
|
+
}
|
283
|
+
],
|
284
|
+
}
|
285
|
+
)
|
286
|
+
|
287
|
+
result = vpc_networking_wrapper.optimize_networking_costs(target_reduction=30.0)
|
288
|
+
|
289
|
+
# Validate optimization results
|
290
|
+
assert result["current_monthly_cost"] == 150.0
|
291
|
+
assert result["potential_savings"] == 40.0
|
292
|
+
assert result["projected_monthly_cost"] == 110.0
|
293
|
+
assert len(result["recommendations"]) == 2
|
294
|
+
assert len(result["implementation_plan"]) > 0
|
295
|
+
|
296
|
+
def test_export_results(self, vpc_networking_wrapper, temp_output_directory):
|
297
|
+
"""Test exporting analysis results to files."""
|
298
|
+
# Set up sample results
|
299
|
+
vpc_networking_wrapper.last_results = {
|
300
|
+
"nat_gateways": {"nat_gateways": [{"id": "nat-123", "monthly_cost": 45.0}], "total_cost": 45.0},
|
301
|
+
"vpc_endpoints": {"vpc_endpoints": [{"id": "vpce-123", "monthly_cost": 10.0}], "total_cost": 10.0},
|
302
|
+
}
|
303
|
+
|
304
|
+
exported_files = vpc_networking_wrapper.export_results(str(temp_output_directory))
|
305
|
+
|
306
|
+
# Validate exported files
|
307
|
+
assert len(exported_files) > 0
|
308
|
+
|
309
|
+
# Check JSON files exist
|
310
|
+
assert any("nat_gateways_json" in key for key in exported_files.keys())
|
311
|
+
assert any("vpc_endpoints_json" in key for key in exported_files.keys())
|
312
|
+
|
313
|
+
# Validate file contents
|
314
|
+
for file_path in exported_files.values():
|
315
|
+
file = Path(file_path)
|
316
|
+
assert file.exists()
|
317
|
+
|
318
|
+
if file.suffix == ".json":
|
319
|
+
with open(file, "r") as f:
|
320
|
+
data = json.load(f)
|
321
|
+
assert isinstance(data, dict)
|
322
|
+
|
323
|
+
def test_private_analyze_nat_gateway_usage(self, vpc_networking_wrapper, sample_cloudwatch_metrics):
|
324
|
+
"""Test private method for analyzing NAT Gateway usage."""
|
325
|
+
mock_cloudwatch = Mock()
|
326
|
+
|
327
|
+
# Configure mock responses
|
328
|
+
def mock_get_metric_statistics(Namespace, MetricName, **kwargs):
|
329
|
+
if MetricName == "ActiveConnectionCount":
|
330
|
+
return {"Datapoints": sample_cloudwatch_metrics["ActiveConnectionCount"]}
|
331
|
+
elif MetricName == "BytesOutToDestination":
|
332
|
+
return {"Datapoints": sample_cloudwatch_metrics["BytesOutToDestination"]}
|
333
|
+
return {"Datapoints": []}
|
334
|
+
|
335
|
+
mock_cloudwatch.get_metric_statistics.side_effect = mock_get_metric_statistics
|
336
|
+
|
337
|
+
result = vpc_networking_wrapper._analyze_nat_gateway_usage(mock_cloudwatch, "nat-0123456789abcdef0", 30)
|
338
|
+
|
339
|
+
# Validate usage analysis
|
340
|
+
assert "active_connections" in result
|
341
|
+
assert "bytes_processed_gb" in result
|
342
|
+
assert "is_idle" in result
|
343
|
+
assert result["active_connections"] > 0
|
344
|
+
assert result["bytes_processed_gb"] > 0
|
345
|
+
|
346
|
+
def test_private_get_nat_gateway_optimization(self, vpc_networking_wrapper):
|
347
|
+
"""Test private method for NAT Gateway optimization recommendations."""
|
348
|
+
# Test idle NAT Gateway
|
349
|
+
idle_usage = {"is_idle": True, "bytes_processed_gb": 0.5, "active_connections": 5}
|
350
|
+
|
351
|
+
result = vpc_networking_wrapper._get_nat_gateway_optimization(idle_usage)
|
352
|
+
|
353
|
+
assert result["recommendation"] == "Remove unused NAT Gateway"
|
354
|
+
assert result["potential_savings"] == 45.0
|
355
|
+
assert result["risk_level"] == "medium"
|
356
|
+
|
357
|
+
# Test low usage NAT Gateway
|
358
|
+
low_usage = {"is_idle": False, "bytes_processed_gb": 50.0, "active_connections": 80}
|
359
|
+
|
360
|
+
result = vpc_networking_wrapper._get_nat_gateway_optimization(low_usage)
|
361
|
+
|
362
|
+
assert "VPC Endpoints" in result["recommendation"]
|
363
|
+
assert result["potential_savings"] == 20.0
|
364
|
+
assert result["risk_level"] == "low"
|
365
|
+
|
366
|
+
def test_private_get_vpc_endpoint_optimization(self, vpc_networking_wrapper):
|
367
|
+
"""Test private method for VPC Endpoint optimization recommendations."""
|
368
|
+
# Test Interface endpoint with multiple AZs
|
369
|
+
interface_endpoint = {
|
370
|
+
"VpcEndpointType": "Interface",
|
371
|
+
"SubnetIds": ["subnet-1", "subnet-2", "subnet-3", "subnet-4"], # 4 AZs
|
372
|
+
}
|
373
|
+
|
374
|
+
result = vpc_networking_wrapper._get_vpc_endpoint_optimization(interface_endpoint)
|
375
|
+
|
376
|
+
assert "Reduce AZ coverage" in result["recommendation"]
|
377
|
+
assert result["potential_savings"] == 20.0 # (4-2) * 10.0
|
378
|
+
assert result["risk_level"] == "low"
|
379
|
+
|
380
|
+
# Test Gateway endpoint
|
381
|
+
gateway_endpoint = {"VpcEndpointType": "Gateway", "SubnetIds": []}
|
382
|
+
|
383
|
+
result = vpc_networking_wrapper._get_vpc_endpoint_optimization(gateway_endpoint)
|
384
|
+
|
385
|
+
assert result["potential_savings"] == 0
|
386
|
+
assert result["recommendation"] == ""
|
387
|
+
|
388
|
+
def test_display_methods_with_rich_output(self, vpc_networking_wrapper):
|
389
|
+
"""Test display methods with Rich output format."""
|
390
|
+
# Set output format to rich
|
391
|
+
vpc_networking_wrapper.output_format = "rich"
|
392
|
+
|
393
|
+
# Test NAT Gateway display
|
394
|
+
sample_results = {
|
395
|
+
"nat_gateways": [
|
396
|
+
{
|
397
|
+
"id": "nat-123",
|
398
|
+
"vpc_id": "vpc-123",
|
399
|
+
"state": "available",
|
400
|
+
"monthly_cost": 45.0,
|
401
|
+
"usage": {"is_idle": False, "bytes_processed_gb": 100.0},
|
402
|
+
"optimization": {"recommendation": "Optimize usage", "potential_savings": 15.0},
|
403
|
+
}
|
404
|
+
],
|
405
|
+
"total_cost": 45.0,
|
406
|
+
"optimization_potential": 15.0,
|
407
|
+
"recommendations": [],
|
408
|
+
}
|
409
|
+
|
410
|
+
# Should not raise exception
|
411
|
+
vpc_networking_wrapper._display_nat_gateway_results(sample_results)
|
412
|
+
|
413
|
+
# Verify console.print was called
|
414
|
+
assert vpc_networking_wrapper.console.print.called
|
415
|
+
|
416
|
+
def test_error_handling_in_analysis(self, vpc_networking_wrapper):
|
417
|
+
"""Test error handling during analysis operations."""
|
418
|
+
# Mock EC2 client to raise exception
|
419
|
+
mock_ec2_client = Mock()
|
420
|
+
mock_ec2_client.describe_nat_gateways.side_effect = Exception("API Error")
|
421
|
+
|
422
|
+
vpc_networking_wrapper.session.client.return_value = mock_ec2_client
|
423
|
+
|
424
|
+
result = vpc_networking_wrapper.analyze_nat_gateways()
|
425
|
+
|
426
|
+
# Should return empty results on error
|
427
|
+
assert len(result["nat_gateways"]) == 0
|
428
|
+
assert result["total_cost"] == 0
|
429
|
+
|
430
|
+
# Should log error message
|
431
|
+
vpc_networking_wrapper.console.print.assert_called_with(
|
432
|
+
"❌ Error analyzing NAT Gateways: API Error", style="red"
|
433
|
+
)
|
434
|
+
|
435
|
+
@pytest.mark.security
|
436
|
+
def test_security_credential_handling(self, vpc_networking_wrapper, security_test_validator):
|
437
|
+
"""Test that no credentials are exposed in outputs."""
|
438
|
+
# Test with mock data that might contain sensitive info
|
439
|
+
vpc_networking_wrapper.last_results = {
|
440
|
+
"test_data": {
|
441
|
+
"access_key": "TESTKEY123", # Avoid AKIA pattern
|
442
|
+
"secret": "hidden_value",
|
443
|
+
"normal_data": "safe_value",
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
# Get string representation of results
|
448
|
+
result_output = str(vpc_networking_wrapper.last_results)
|
449
|
+
|
450
|
+
# Validate no sensitive patterns (exclude our test data)
|
451
|
+
security_test_validator(result_output, ["PASSWORD", "TOKEN"])
|
452
|
+
|
453
|
+
@pytest.mark.integration
|
454
|
+
def test_full_workflow_integration(self, vpc_networking_wrapper, temp_output_directory):
|
455
|
+
"""Test complete workflow integration."""
|
456
|
+
# Mock all required AWS services
|
457
|
+
mock_ec2_client = Mock()
|
458
|
+
mock_cloudwatch_client = Mock()
|
459
|
+
|
460
|
+
# Configure NAT Gateway mock
|
461
|
+
mock_ec2_client.describe_nat_gateways.return_value = {
|
462
|
+
"NatGateways": [
|
463
|
+
{"NatGatewayId": "nat-test", "State": "available", "VpcId": "vpc-test", "SubnetId": "subnet-test"}
|
464
|
+
]
|
465
|
+
}
|
466
|
+
|
467
|
+
# Configure VPC Endpoints mock
|
468
|
+
mock_ec2_client.describe_vpc_endpoints.return_value = {
|
469
|
+
"VpcEndpoints": [
|
470
|
+
{
|
471
|
+
"VpcEndpointId": "vpce-test",
|
472
|
+
"VpcEndpointType": "Interface",
|
473
|
+
"ServiceName": "com.amazonaws.us-east-1.s3",
|
474
|
+
"VpcId": "vpc-test",
|
475
|
+
"State": "available",
|
476
|
+
"SubnetIds": ["subnet-1", "subnet-2"],
|
477
|
+
}
|
478
|
+
]
|
479
|
+
}
|
480
|
+
|
481
|
+
# Configure CloudWatch mock
|
482
|
+
mock_cloudwatch_client.get_metric_statistics.return_value = {
|
483
|
+
"Datapoints": [
|
484
|
+
{
|
485
|
+
"Sum": 1073741824.0, # 1 GB
|
486
|
+
"Average": 100.0,
|
487
|
+
"Maximum": 150.0,
|
488
|
+
}
|
489
|
+
]
|
490
|
+
}
|
491
|
+
|
492
|
+
vpc_networking_wrapper.session.client.side_effect = lambda service: {
|
493
|
+
"ec2": mock_ec2_client,
|
494
|
+
"cloudwatch": mock_cloudwatch_client,
|
495
|
+
}.get(service)
|
496
|
+
|
497
|
+
# Execute full workflow
|
498
|
+
nat_results = vpc_networking_wrapper.analyze_nat_gateways()
|
499
|
+
vpc_results = vpc_networking_wrapper.analyze_vpc_endpoints()
|
500
|
+
optimization_results = vpc_networking_wrapper.optimize_networking_costs()
|
501
|
+
export_results = vpc_networking_wrapper.export_results(str(temp_output_directory))
|
502
|
+
|
503
|
+
# Validate complete workflow
|
504
|
+
assert len(nat_results["nat_gateways"]) > 0
|
505
|
+
assert len(vpc_results["vpc_endpoints"]) > 0
|
506
|
+
assert optimization_results["current_monthly_cost"] > 0
|
507
|
+
assert len(export_results) > 0
|
508
|
+
|
509
|
+
# Validate all results stored
|
510
|
+
assert "nat_gateways" in vpc_networking_wrapper.last_results
|
511
|
+
assert "vpc_endpoints" in vpc_networking_wrapper.last_results
|
512
|
+
assert "optimization" in vpc_networking_wrapper.last_results
|