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.
- runbooks/__init__.py +1 -1
- runbooks/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -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/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -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/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- 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 +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- 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 +319 -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/__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/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/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -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 +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -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.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,530 @@
|
|
1
|
+
"""
|
2
|
+
Tests for VPC CLI Integration
|
3
|
+
|
4
|
+
Tests the VPC command-line interface integration with the main runbooks CLI.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import tempfile
|
9
|
+
from pathlib import Path
|
10
|
+
from unittest.mock import MagicMock, Mock, patch
|
11
|
+
|
12
|
+
import pytest
|
13
|
+
from click.testing import CliRunner
|
14
|
+
|
15
|
+
from runbooks.main import main
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.mark.cli
|
19
|
+
class TestVPCCLIIntegration:
|
20
|
+
"""Test VPC CLI integration with main runbooks CLI."""
|
21
|
+
|
22
|
+
def setup_method(self):
|
23
|
+
"""Set up test environment."""
|
24
|
+
self.runner = CliRunner()
|
25
|
+
|
26
|
+
def test_vpc_help_command(self):
|
27
|
+
"""Test VPC help command."""
|
28
|
+
result = self.runner.invoke(main, ["vpc", "--help"])
|
29
|
+
|
30
|
+
assert result.exit_code == 0
|
31
|
+
assert "VPC Networking Analysis" in result.output or "vpc" in result.output.lower()
|
32
|
+
|
33
|
+
def test_vpc_analyze_help_command(self):
|
34
|
+
"""Test VPC analyze command help."""
|
35
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--help"])
|
36
|
+
|
37
|
+
assert result.exit_code == 0
|
38
|
+
assert "--profile" in result.output
|
39
|
+
assert "--region" in result.output
|
40
|
+
assert "--days" in result.output
|
41
|
+
|
42
|
+
def test_vpc_heatmap_help_command(self):
|
43
|
+
"""Test VPC heatmap command help."""
|
44
|
+
result = self.runner.invoke(main, ["vpc", "heatmap", "--help"])
|
45
|
+
|
46
|
+
assert result.exit_code == 0
|
47
|
+
assert "--account-scope" in result.output
|
48
|
+
|
49
|
+
def test_vpc_optimize_help_command(self):
|
50
|
+
"""Test VPC optimize command help."""
|
51
|
+
result = self.runner.invoke(main, ["vpc", "optimize", "--help"])
|
52
|
+
|
53
|
+
assert result.exit_code == 0
|
54
|
+
assert "--target-reduction" in result.output
|
55
|
+
|
56
|
+
@pytest.mark.integration
|
57
|
+
def test_vpc_analyze_command_execution(self):
|
58
|
+
"""Test VPC analyze command execution."""
|
59
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
60
|
+
# Configure mock wrapper
|
61
|
+
mock_wrapper = Mock()
|
62
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
63
|
+
"nat_gateways": [],
|
64
|
+
"total_cost": 0.0,
|
65
|
+
"optimization_potential": 0.0,
|
66
|
+
"recommendations": [],
|
67
|
+
}
|
68
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {
|
69
|
+
"vpc_endpoints": [],
|
70
|
+
"total_cost": 0.0,
|
71
|
+
"optimization_potential": 0.0,
|
72
|
+
"recommendations": [],
|
73
|
+
}
|
74
|
+
mock_wrapper_class.return_value = mock_wrapper
|
75
|
+
|
76
|
+
result = self.runner.invoke(
|
77
|
+
main, ["vpc", "analyze", "--profile", "test-profile", "--region", "us-east-1", "--days", "30"]
|
78
|
+
)
|
79
|
+
|
80
|
+
assert result.exit_code == 0
|
81
|
+
|
82
|
+
# Verify wrapper was initialized with correct parameters
|
83
|
+
mock_wrapper_class.assert_called_once()
|
84
|
+
init_args = mock_wrapper_class.call_args
|
85
|
+
assert init_args[1]["profile"] == "test-profile"
|
86
|
+
assert init_args[1]["region"] == "us-east-1"
|
87
|
+
|
88
|
+
# Verify analysis methods were called
|
89
|
+
mock_wrapper.analyze_nat_gateways.assert_called_once_with(days=30)
|
90
|
+
mock_wrapper.analyze_vpc_endpoints.assert_called_once()
|
91
|
+
|
92
|
+
@pytest.mark.integration
|
93
|
+
def test_vpc_heatmap_command_execution(self):
|
94
|
+
"""Test VPC heatmap command execution."""
|
95
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
96
|
+
# Configure mock wrapper
|
97
|
+
mock_wrapper = Mock()
|
98
|
+
mock_wrapper.generate_cost_heatmaps.return_value = {
|
99
|
+
"heatmap_data": "sample_data",
|
100
|
+
"regions": ["us-east-1", "us-west-2"],
|
101
|
+
}
|
102
|
+
mock_wrapper_class.return_value = mock_wrapper
|
103
|
+
|
104
|
+
result = self.runner.invoke(
|
105
|
+
main, ["vpc", "heatmap", "--account-scope", "single", "--profile", "test-profile"]
|
106
|
+
)
|
107
|
+
|
108
|
+
assert result.exit_code == 0
|
109
|
+
|
110
|
+
# Verify wrapper was initialized
|
111
|
+
mock_wrapper_class.assert_called_once()
|
112
|
+
|
113
|
+
# Verify heatmap generation was called
|
114
|
+
mock_wrapper.generate_cost_heatmaps.assert_called_once_with(account_scope="single")
|
115
|
+
|
116
|
+
@pytest.mark.integration
|
117
|
+
def test_vpc_optimize_command_execution(self):
|
118
|
+
"""Test VPC optimize command execution."""
|
119
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
120
|
+
# Configure mock wrapper
|
121
|
+
mock_wrapper = Mock()
|
122
|
+
mock_wrapper.optimize_networking_costs.return_value = {
|
123
|
+
"current_monthly_cost": 100.0,
|
124
|
+
"potential_savings": 30.0,
|
125
|
+
"projected_monthly_cost": 70.0,
|
126
|
+
"recommendations": [],
|
127
|
+
"implementation_plan": [],
|
128
|
+
}
|
129
|
+
mock_wrapper_class.return_value = mock_wrapper
|
130
|
+
|
131
|
+
result = self.runner.invoke(
|
132
|
+
main, ["vpc", "optimize", "--target-reduction", "30", "--profile", "test-profile"]
|
133
|
+
)
|
134
|
+
|
135
|
+
assert result.exit_code == 0
|
136
|
+
|
137
|
+
# Verify wrapper was initialized
|
138
|
+
mock_wrapper_class.assert_called_once()
|
139
|
+
|
140
|
+
# Verify optimization was called with correct target
|
141
|
+
mock_wrapper.optimize_networking_costs.assert_called_once_with(target_reduction=30.0)
|
142
|
+
|
143
|
+
def test_vpc_analyze_with_invalid_profile(self):
|
144
|
+
"""Test VPC analyze with invalid profile handling."""
|
145
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
146
|
+
# Configure wrapper to simulate connection failure
|
147
|
+
mock_wrapper = Mock()
|
148
|
+
mock_wrapper.session = None # No session indicates connection failure
|
149
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
150
|
+
"nat_gateways": [],
|
151
|
+
"total_cost": 0.0,
|
152
|
+
"optimization_potential": 0.0,
|
153
|
+
"recommendations": [],
|
154
|
+
}
|
155
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {
|
156
|
+
"vpc_endpoints": [],
|
157
|
+
"total_cost": 0.0,
|
158
|
+
"optimization_potential": 0.0,
|
159
|
+
"recommendations": [],
|
160
|
+
}
|
161
|
+
mock_wrapper_class.return_value = mock_wrapper
|
162
|
+
|
163
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--profile", "invalid-profile"])
|
164
|
+
|
165
|
+
# Should still succeed but with empty results
|
166
|
+
assert result.exit_code == 0
|
167
|
+
|
168
|
+
def test_vpc_command_with_output_format_json(self):
|
169
|
+
"""Test VPC command with JSON output format."""
|
170
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
171
|
+
mock_wrapper = Mock()
|
172
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
173
|
+
"nat_gateways": [{"id": "nat-123", "cost": 45.0}],
|
174
|
+
"total_cost": 45.0,
|
175
|
+
}
|
176
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {
|
177
|
+
"vpc_endpoints": [{"id": "vpce-123", "cost": 10.0}],
|
178
|
+
"total_cost": 10.0,
|
179
|
+
}
|
180
|
+
mock_wrapper_class.return_value = mock_wrapper
|
181
|
+
|
182
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--output-format", "json"])
|
183
|
+
|
184
|
+
assert result.exit_code == 0
|
185
|
+
|
186
|
+
# Verify wrapper was initialized with JSON output format
|
187
|
+
init_args = mock_wrapper_class.call_args
|
188
|
+
assert init_args[1]["output_format"] == "json"
|
189
|
+
|
190
|
+
@pytest.mark.performance
|
191
|
+
def test_vpc_cli_response_time(self, performance_benchmarks, assert_performance_benchmark):
|
192
|
+
"""Test VPC CLI response time performance."""
|
193
|
+
import time
|
194
|
+
|
195
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
196
|
+
# Configure fast mock response
|
197
|
+
mock_wrapper = Mock()
|
198
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
199
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
200
|
+
mock_wrapper_class.return_value = mock_wrapper
|
201
|
+
|
202
|
+
start_time = time.time()
|
203
|
+
result = self.runner.invoke(main, ["vpc", "analyze"])
|
204
|
+
execution_time = time.time() - start_time
|
205
|
+
|
206
|
+
assert result.exit_code == 0
|
207
|
+
|
208
|
+
# Assert CLI response time benchmark
|
209
|
+
assert_performance_benchmark(execution_time, "cli_response_max_time", performance_benchmarks)
|
210
|
+
|
211
|
+
def test_vpc_analyze_with_billing_profile(self):
|
212
|
+
"""Test VPC analyze with separate billing profile."""
|
213
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
214
|
+
mock_wrapper = Mock()
|
215
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
216
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
217
|
+
mock_wrapper_class.return_value = mock_wrapper
|
218
|
+
|
219
|
+
result = self.runner.invoke(
|
220
|
+
main, ["vpc", "analyze", "--profile", "ops-profile", "--billing-profile", "billing-profile"]
|
221
|
+
)
|
222
|
+
|
223
|
+
assert result.exit_code == 0
|
224
|
+
|
225
|
+
# Verify both profiles were passed
|
226
|
+
init_args = mock_wrapper_class.call_args
|
227
|
+
assert init_args[1]["profile"] == "ops-profile"
|
228
|
+
assert init_args[1]["billing_profile"] == "billing-profile"
|
229
|
+
|
230
|
+
def test_vpc_heatmap_multi_account_scope(self):
|
231
|
+
"""Test VPC heatmap with multi-account scope."""
|
232
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
233
|
+
mock_wrapper = Mock()
|
234
|
+
mock_wrapper.generate_cost_heatmaps.return_value = {
|
235
|
+
"accounts": ["123456789012", "123456789013"],
|
236
|
+
"heatmap_data": "multi_account_data",
|
237
|
+
}
|
238
|
+
mock_wrapper_class.return_value = mock_wrapper
|
239
|
+
|
240
|
+
result = self.runner.invoke(main, ["vpc", "heatmap", "--account-scope", "multi"])
|
241
|
+
|
242
|
+
assert result.exit_code == 0
|
243
|
+
|
244
|
+
# Verify multi-account scope was passed
|
245
|
+
mock_wrapper.generate_cost_heatmaps.assert_called_once_with(account_scope="multi")
|
246
|
+
|
247
|
+
def test_vpc_optimize_with_high_target_reduction(self):
|
248
|
+
"""Test VPC optimize with high target reduction."""
|
249
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
250
|
+
mock_wrapper = Mock()
|
251
|
+
mock_wrapper.optimize_networking_costs.return_value = {
|
252
|
+
"target_reduction": 50.0,
|
253
|
+
"achievable_reduction": 35.0,
|
254
|
+
"recommendations": ["Remove idle NAT Gateways"],
|
255
|
+
}
|
256
|
+
mock_wrapper_class.return_value = mock_wrapper
|
257
|
+
|
258
|
+
result = self.runner.invoke(main, ["vpc", "optimize", "--target-reduction", "50"])
|
259
|
+
|
260
|
+
assert result.exit_code == 0
|
261
|
+
|
262
|
+
# Verify high target reduction was passed
|
263
|
+
mock_wrapper.optimize_networking_costs.assert_called_once_with(target_reduction=50.0)
|
264
|
+
|
265
|
+
@pytest.mark.error_handling
|
266
|
+
def test_vpc_command_with_import_error(self):
|
267
|
+
"""Test VPC command behavior when import fails."""
|
268
|
+
with patch("runbooks.main.VPCNetworkingWrapper", side_effect=ImportError("Module not found")):
|
269
|
+
result = self.runner.invoke(main, ["vpc", "analyze"])
|
270
|
+
|
271
|
+
# Should handle import error gracefully
|
272
|
+
assert result.exit_code != 0
|
273
|
+
assert "error" in result.output.lower() or "failed" in result.output.lower()
|
274
|
+
|
275
|
+
@pytest.mark.error_handling
|
276
|
+
def test_vpc_analyze_with_aws_error(self):
|
277
|
+
"""Test VPC analyze command with AWS service error."""
|
278
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
279
|
+
# Configure wrapper to raise AWS error
|
280
|
+
mock_wrapper = Mock()
|
281
|
+
mock_wrapper.analyze_nat_gateways.side_effect = Exception("AWS API Error")
|
282
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
283
|
+
mock_wrapper_class.return_value = mock_wrapper
|
284
|
+
|
285
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--profile", "test-profile"])
|
286
|
+
|
287
|
+
# Command should handle error gracefully
|
288
|
+
assert result.exit_code == 0 # CLI should not crash
|
289
|
+
|
290
|
+
def test_vpc_command_parameter_validation(self):
|
291
|
+
"""Test VPC command parameter validation."""
|
292
|
+
# Test invalid target reduction
|
293
|
+
result = self.runner.invoke(
|
294
|
+
main,
|
295
|
+
[
|
296
|
+
"vpc",
|
297
|
+
"optimize",
|
298
|
+
"--target-reduction",
|
299
|
+
"-10", # Negative value
|
300
|
+
],
|
301
|
+
)
|
302
|
+
|
303
|
+
# Should validate parameters
|
304
|
+
assert result.exit_code != 0 or "invalid" in result.output.lower()
|
305
|
+
|
306
|
+
# Test invalid account scope
|
307
|
+
result = self.runner.invoke(main, ["vpc", "heatmap", "--account-scope", "invalid-scope"])
|
308
|
+
|
309
|
+
# Should validate parameters
|
310
|
+
assert result.exit_code != 0 or "invalid" in result.output.lower()
|
311
|
+
|
312
|
+
@pytest.mark.integration
|
313
|
+
def test_vpc_analyze_full_output_verification(self):
|
314
|
+
"""Test VPC analyze command with comprehensive output verification."""
|
315
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
316
|
+
# Configure detailed mock response
|
317
|
+
mock_wrapper = Mock()
|
318
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
319
|
+
"nat_gateways": [
|
320
|
+
{
|
321
|
+
"id": "nat-0123456789abcdef0",
|
322
|
+
"state": "available",
|
323
|
+
"monthly_cost": 45.0,
|
324
|
+
"optimization": {"recommendation": "Optimize usage"},
|
325
|
+
}
|
326
|
+
],
|
327
|
+
"total_cost": 45.0,
|
328
|
+
"optimization_potential": 15.0,
|
329
|
+
"recommendations": ["Reduce idle time"],
|
330
|
+
}
|
331
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {
|
332
|
+
"vpc_endpoints": [
|
333
|
+
{
|
334
|
+
"id": "vpce-0123456789abcdef0",
|
335
|
+
"type": "Interface",
|
336
|
+
"service": "com.amazonaws.us-east-1.s3",
|
337
|
+
"monthly_cost": 20.0,
|
338
|
+
}
|
339
|
+
],
|
340
|
+
"total_cost": 20.0,
|
341
|
+
"optimization_potential": 5.0,
|
342
|
+
"recommendations": ["Reduce AZ coverage"],
|
343
|
+
}
|
344
|
+
mock_wrapper_class.return_value = mock_wrapper
|
345
|
+
|
346
|
+
result = self.runner.invoke(
|
347
|
+
main, ["vpc", "analyze", "--profile", "test-profile", "--output-format", "rich"]
|
348
|
+
)
|
349
|
+
|
350
|
+
assert result.exit_code == 0
|
351
|
+
|
352
|
+
# Verify both analysis methods were called
|
353
|
+
mock_wrapper.analyze_nat_gateways.assert_called_once()
|
354
|
+
mock_wrapper.analyze_vpc_endpoints.assert_called_once()
|
355
|
+
|
356
|
+
@pytest.mark.security
|
357
|
+
def test_vpc_cli_credential_handling(self):
|
358
|
+
"""Test that VPC CLI doesn't expose credentials in output."""
|
359
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
360
|
+
# Configure wrapper with potential credential exposure
|
361
|
+
mock_wrapper = Mock()
|
362
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
363
|
+
"profile_info": "AKIATEST123456", # Simulate credential leak
|
364
|
+
"nat_gateways": [],
|
365
|
+
}
|
366
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
367
|
+
mock_wrapper_class.return_value = mock_wrapper
|
368
|
+
|
369
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--profile", "test-profile"])
|
370
|
+
|
371
|
+
# Verify no credentials in output
|
372
|
+
sensitive_patterns = ["AKIA", "SECRET", "TOKEN"]
|
373
|
+
for pattern in sensitive_patterns:
|
374
|
+
assert pattern not in result.output.upper()
|
375
|
+
|
376
|
+
@pytest.mark.integration
|
377
|
+
def test_vpc_command_chaining_workflow(self):
|
378
|
+
"""Test VPC command workflow integration."""
|
379
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
380
|
+
mock_wrapper = Mock()
|
381
|
+
|
382
|
+
# Configure responses for chained operations
|
383
|
+
mock_wrapper.analyze_nat_gateways.return_value = {
|
384
|
+
"nat_gateways": [{"id": "nat-123", "monthly_cost": 45.0}],
|
385
|
+
"total_cost": 45.0,
|
386
|
+
"optimization_potential": 15.0,
|
387
|
+
}
|
388
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {
|
389
|
+
"vpc_endpoints": [{"id": "vpce-123", "monthly_cost": 10.0}],
|
390
|
+
"total_cost": 10.0,
|
391
|
+
"optimization_potential": 3.0,
|
392
|
+
}
|
393
|
+
mock_wrapper.optimize_networking_costs.return_value = {
|
394
|
+
"current_monthly_cost": 55.0,
|
395
|
+
"potential_savings": 18.0,
|
396
|
+
"recommendations": ["Remove idle NAT Gateway"],
|
397
|
+
}
|
398
|
+
mock_wrapper.generate_cost_heatmaps.return_value = {"heatmap_data": "comprehensive_data"}
|
399
|
+
mock_wrapper_class.return_value = mock_wrapper
|
400
|
+
|
401
|
+
# Execute analyze command
|
402
|
+
analyze_result = self.runner.invoke(main, ["vpc", "analyze", "--profile", "test-profile"])
|
403
|
+
assert analyze_result.exit_code == 0
|
404
|
+
|
405
|
+
# Execute optimize command
|
406
|
+
optimize_result = self.runner.invoke(
|
407
|
+
main, ["vpc", "optimize", "--profile", "test-profile", "--target-reduction", "30"]
|
408
|
+
)
|
409
|
+
assert optimize_result.exit_code == 0
|
410
|
+
|
411
|
+
# Execute heatmap command
|
412
|
+
heatmap_result = self.runner.invoke(main, ["vpc", "heatmap", "--profile", "test-profile"])
|
413
|
+
assert heatmap_result.exit_code == 0
|
414
|
+
|
415
|
+
# Verify all methods were called across commands
|
416
|
+
assert mock_wrapper.analyze_nat_gateways.call_count >= 2 # Called in analyze and optimize
|
417
|
+
assert mock_wrapper.analyze_vpc_endpoints.call_count >= 2 # Called in analyze and optimize
|
418
|
+
assert mock_wrapper.optimize_networking_costs.call_count == 1
|
419
|
+
assert mock_wrapper.generate_cost_heatmaps.call_count == 1
|
420
|
+
|
421
|
+
|
422
|
+
@pytest.mark.cli
|
423
|
+
class TestVPCCLIArgumentHandling:
|
424
|
+
"""Test VPC CLI argument handling and validation."""
|
425
|
+
|
426
|
+
def setup_method(self):
|
427
|
+
"""Set up test environment."""
|
428
|
+
self.runner = CliRunner()
|
429
|
+
|
430
|
+
def test_vpc_analyze_default_parameters(self):
|
431
|
+
"""Test VPC analyze with default parameters."""
|
432
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
433
|
+
mock_wrapper = Mock()
|
434
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
435
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
436
|
+
mock_wrapper_class.return_value = mock_wrapper
|
437
|
+
|
438
|
+
result = self.runner.invoke(main, ["vpc", "analyze"])
|
439
|
+
|
440
|
+
assert result.exit_code == 0
|
441
|
+
|
442
|
+
# Verify default parameters were used
|
443
|
+
init_args = mock_wrapper_class.call_args
|
444
|
+
# Profile should be None by default
|
445
|
+
assert init_args[1].get("profile") is None
|
446
|
+
# Region should default to us-east-1
|
447
|
+
assert init_args[1].get("region") == "us-east-1"
|
448
|
+
# Output format should default to rich
|
449
|
+
assert init_args[1].get("output_format") == "rich"
|
450
|
+
|
451
|
+
def test_vpc_analyze_custom_days_parameter(self):
|
452
|
+
"""Test VPC analyze with custom days parameter."""
|
453
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
454
|
+
mock_wrapper = Mock()
|
455
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
456
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
457
|
+
mock_wrapper_class.return_value = mock_wrapper
|
458
|
+
|
459
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--days", "60"])
|
460
|
+
|
461
|
+
assert result.exit_code == 0
|
462
|
+
|
463
|
+
# Verify custom days parameter was passed
|
464
|
+
mock_wrapper.analyze_nat_gateways.assert_called_once_with(days=60)
|
465
|
+
|
466
|
+
def test_vpc_optimize_target_reduction_bounds(self):
|
467
|
+
"""Test VPC optimize target reduction parameter bounds."""
|
468
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
469
|
+
mock_wrapper = Mock()
|
470
|
+
mock_wrapper.optimize_networking_costs.return_value = {"recommendations": []}
|
471
|
+
mock_wrapper_class.return_value = mock_wrapper
|
472
|
+
|
473
|
+
# Test maximum valid target reduction
|
474
|
+
result = self.runner.invoke(main, ["vpc", "optimize", "--target-reduction", "100"])
|
475
|
+
|
476
|
+
if result.exit_code == 0:
|
477
|
+
mock_wrapper.optimize_networking_costs.assert_called_with(target_reduction=100.0)
|
478
|
+
|
479
|
+
def test_vpc_heatmap_account_scope_validation(self):
|
480
|
+
"""Test VPC heatmap account scope validation."""
|
481
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
482
|
+
mock_wrapper = Mock()
|
483
|
+
mock_wrapper.generate_cost_heatmaps.return_value = {"heatmap_data": "test"}
|
484
|
+
mock_wrapper_class.return_value = mock_wrapper
|
485
|
+
|
486
|
+
# Test valid single scope
|
487
|
+
result = self.runner.invoke(main, ["vpc", "heatmap", "--account-scope", "single"])
|
488
|
+
assert result.exit_code == 0
|
489
|
+
|
490
|
+
# Test valid multi scope
|
491
|
+
result = self.runner.invoke(main, ["vpc", "heatmap", "--account-scope", "multi"])
|
492
|
+
assert result.exit_code == 0
|
493
|
+
|
494
|
+
def test_vpc_command_region_parameter(self):
|
495
|
+
"""Test VPC command with region parameter."""
|
496
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
497
|
+
mock_wrapper = Mock()
|
498
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
499
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
500
|
+
mock_wrapper_class.return_value = mock_wrapper
|
501
|
+
|
502
|
+
regions_to_test = ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
|
503
|
+
|
504
|
+
for region in regions_to_test:
|
505
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--region", region])
|
506
|
+
|
507
|
+
assert result.exit_code == 0
|
508
|
+
|
509
|
+
# Verify correct region was passed
|
510
|
+
init_args = mock_wrapper_class.call_args
|
511
|
+
assert init_args[1]["region"] == region
|
512
|
+
|
513
|
+
def test_vpc_command_output_format_parameter(self):
|
514
|
+
"""Test VPC command with different output formats."""
|
515
|
+
with patch("runbooks.vpc.VPCNetworkingWrapper") as mock_wrapper_class:
|
516
|
+
mock_wrapper = Mock()
|
517
|
+
mock_wrapper.analyze_nat_gateways.return_value = {"nat_gateways": []}
|
518
|
+
mock_wrapper.analyze_vpc_endpoints.return_value = {"vpc_endpoints": []}
|
519
|
+
mock_wrapper_class.return_value = mock_wrapper
|
520
|
+
|
521
|
+
output_formats = ["rich", "json", "csv"]
|
522
|
+
|
523
|
+
for output_format in output_formats:
|
524
|
+
result = self.runner.invoke(main, ["vpc", "analyze", "--output-format", output_format])
|
525
|
+
|
526
|
+
assert result.exit_code == 0
|
527
|
+
|
528
|
+
# Verify correct output format was passed
|
529
|
+
init_args = mock_wrapper_class.call_args
|
530
|
+
assert init_args[1]["output_format"] == output_format
|