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,477 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Integration Tests for FinOps Dashboard with AWS Service Mocking.
|
4
|
+
|
5
|
+
This module provides integration testing using moto to mock AWS services,
|
6
|
+
enabling comprehensive testing of AWS interactions without real credentials
|
7
|
+
or costs.
|
8
|
+
|
9
|
+
Test Coverage:
|
10
|
+
- AWS profile validation and session creation
|
11
|
+
- Cost Explorer API integration
|
12
|
+
- Organizations API integration
|
13
|
+
- EC2, S3, RDS service discovery
|
14
|
+
- Multi-account role assumption
|
15
|
+
- Error handling with AWS service failures
|
16
|
+
|
17
|
+
Author: CloudOps Runbooks Team
|
18
|
+
Version: 0.7.8
|
19
|
+
"""
|
20
|
+
|
21
|
+
import json
|
22
|
+
from datetime import datetime, timedelta
|
23
|
+
from unittest.mock import Mock, patch
|
24
|
+
|
25
|
+
import boto3
|
26
|
+
import pytest
|
27
|
+
|
28
|
+
try:
|
29
|
+
from moto import mock_ec2, mock_organizations, mock_rds, mock_s3, mock_sts
|
30
|
+
|
31
|
+
# Try to import mock_costexplorer if available
|
32
|
+
try:
|
33
|
+
from moto import mock_costexplorer
|
34
|
+
except ImportError:
|
35
|
+
# Define mock_costexplorer as a no-op decorator for compatibility
|
36
|
+
def mock_costexplorer(func):
|
37
|
+
def wrapper(*args, **kwargs):
|
38
|
+
return func(*args, **kwargs)
|
39
|
+
|
40
|
+
return wrapper
|
41
|
+
except ImportError:
|
42
|
+
# If moto is not available, define all as no-op decorators
|
43
|
+
def mock_ec2(func):
|
44
|
+
return func
|
45
|
+
|
46
|
+
def mock_organizations(func):
|
47
|
+
return func
|
48
|
+
|
49
|
+
def mock_rds(func):
|
50
|
+
return func
|
51
|
+
|
52
|
+
def mock_s3(func):
|
53
|
+
return func
|
54
|
+
|
55
|
+
def mock_sts(func):
|
56
|
+
return func
|
57
|
+
|
58
|
+
def mock_costexplorer(func):
|
59
|
+
return func
|
60
|
+
|
61
|
+
|
62
|
+
# Import the components we're testing
|
63
|
+
from runbooks.finops.finops_dashboard import (
|
64
|
+
EnterpriseDiscovery,
|
65
|
+
FinOpsConfig,
|
66
|
+
MultiAccountCostTrendAnalyzer,
|
67
|
+
run_complete_finops_analysis,
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
class TestAWSIntegrationWithMoto:
|
72
|
+
"""Integration tests using moto to mock AWS services."""
|
73
|
+
|
74
|
+
@mock_sts
|
75
|
+
@mock_organizations
|
76
|
+
def test_discovery_with_aws_organizations(self):
|
77
|
+
"""Test account discovery with mocked AWS Organizations."""
|
78
|
+
# Setup mocked Organizations
|
79
|
+
client = boto3.client("organizations", region_name="us-east-1")
|
80
|
+
|
81
|
+
# Create a mock organization
|
82
|
+
org_response = client.create_organization(FeatureSet="ALL")
|
83
|
+
org_id = org_response["Organization"]["Id"]
|
84
|
+
|
85
|
+
# Create mock accounts
|
86
|
+
account_names = ["production-account", "staging-account", "development-account"]
|
87
|
+
created_accounts = []
|
88
|
+
|
89
|
+
for account_name in account_names:
|
90
|
+
response = client.create_account(AccountName=account_name, Email=f"{account_name}@example.com")
|
91
|
+
created_accounts.append(response)
|
92
|
+
|
93
|
+
# Test discovery with real AWS client
|
94
|
+
with patch("runbooks.finops.finops_dashboard.AWS_AVAILABLE", True):
|
95
|
+
config = FinOpsConfig()
|
96
|
+
config.dry_run = False # Enable live mode for integration test
|
97
|
+
|
98
|
+
discovery = EnterpriseDiscovery(config)
|
99
|
+
|
100
|
+
# Mock the get_account_id function to return different account IDs
|
101
|
+
with patch("runbooks.finops.finops_dashboard.get_account_id") as mock_get_account:
|
102
|
+
mock_get_account.side_effect = ["123456789012", "234567890123", "345678901234"]
|
103
|
+
|
104
|
+
results = discovery.discover_accounts()
|
105
|
+
|
106
|
+
# Verify discovery succeeded
|
107
|
+
assert "account_info" in results
|
108
|
+
assert results["discovery_mode"] == "LIVE"
|
109
|
+
|
110
|
+
# Verify each configured profile was checked
|
111
|
+
account_info = results["account_info"]
|
112
|
+
for profile_type in ["billing", "management", "operational"]:
|
113
|
+
assert profile_type in account_info
|
114
|
+
info = account_info[profile_type]
|
115
|
+
assert info["status"] in ["✅ Connected", "❌ Error"]
|
116
|
+
if info["status"] == "✅ Connected":
|
117
|
+
assert "account_id" in info
|
118
|
+
assert len(info["account_id"]) == 12 # AWS account ID format
|
119
|
+
|
120
|
+
@mock_costexplorer
|
121
|
+
@mock_organizations
|
122
|
+
def test_cost_analysis_with_cost_explorer(self):
|
123
|
+
"""Test cost analysis with mocked Cost Explorer."""
|
124
|
+
# Setup mocked Cost Explorer
|
125
|
+
ce_client = boto3.client("ce", region_name="us-east-1")
|
126
|
+
|
127
|
+
# Mock cost data response
|
128
|
+
mock_cost_data = {
|
129
|
+
"ResultsByTime": [
|
130
|
+
{
|
131
|
+
"TimePeriod": {"Start": "2024-01-01", "End": "2024-01-31"},
|
132
|
+
"Total": {"UnblendedCost": {"Amount": "50000.00", "Unit": "USD"}},
|
133
|
+
"Groups": [
|
134
|
+
{"Keys": ["EC2-Instance"], "Metrics": {"UnblendedCost": {"Amount": "20000.00", "Unit": "USD"}}},
|
135
|
+
{"Keys": ["S3"], "Metrics": {"UnblendedCost": {"Amount": "10000.00", "Unit": "USD"}}},
|
136
|
+
],
|
137
|
+
}
|
138
|
+
]
|
139
|
+
}
|
140
|
+
|
141
|
+
# Test cost analysis
|
142
|
+
config = FinOpsConfig()
|
143
|
+
analyzer = MultiAccountCostTrendAnalyzer(config)
|
144
|
+
|
145
|
+
# Run analysis (will use simulated data since we're not mocking the internal methods)
|
146
|
+
results = analyzer.analyze_cost_trends()
|
147
|
+
|
148
|
+
# Verify results structure
|
149
|
+
assert results["status"] == "completed"
|
150
|
+
assert "cost_trends" in results
|
151
|
+
assert "optimization_opportunities" in results
|
152
|
+
|
153
|
+
# Verify cost trends data
|
154
|
+
cost_trends = results["cost_trends"]
|
155
|
+
assert cost_trends["total_accounts"] >= config.min_account_threshold
|
156
|
+
assert cost_trends["total_monthly_spend"] > 0
|
157
|
+
|
158
|
+
# Verify optimization data
|
159
|
+
optimization = results["optimization_opportunities"]
|
160
|
+
assert optimization["total_potential_savings"] > 0
|
161
|
+
assert 0 <= optimization["savings_percentage"] <= 100
|
162
|
+
|
163
|
+
@mock_ec2
|
164
|
+
@mock_s3
|
165
|
+
@mock_rds
|
166
|
+
def test_multi_service_resource_discovery(self):
|
167
|
+
"""Test resource discovery across multiple AWS services."""
|
168
|
+
# Setup mocked EC2
|
169
|
+
ec2 = boto3.client("ec2", region_name="us-east-1")
|
170
|
+
|
171
|
+
# Create mock EC2 instances
|
172
|
+
reservation = ec2.run_instances(ImageId="ami-12345678", MinCount=2, MaxCount=2, InstanceType="t2.micro")
|
173
|
+
instance_ids = [i["InstanceId"] for i in reservation["Instances"]]
|
174
|
+
|
175
|
+
# Tag instances
|
176
|
+
ec2.create_tags(
|
177
|
+
Resources=instance_ids,
|
178
|
+
Tags=[{"Key": "Environment", "Value": "production"}, {"Key": "Application", "Value": "web-server"}],
|
179
|
+
)
|
180
|
+
|
181
|
+
# Setup mocked S3
|
182
|
+
s3 = boto3.client("s3", region_name="us-east-1")
|
183
|
+
bucket_names = ["prod-data-bucket", "staging-logs-bucket", "dev-temp-bucket"]
|
184
|
+
|
185
|
+
for bucket_name in bucket_names:
|
186
|
+
s3.create_bucket(Bucket=bucket_name)
|
187
|
+
|
188
|
+
# Add bucket tagging
|
189
|
+
s3.put_bucket_tagging(
|
190
|
+
Bucket=bucket_name,
|
191
|
+
Tagging={
|
192
|
+
"TagSet": [
|
193
|
+
{"Key": "Environment", "Value": "production" if "prod" in bucket_name else "staging"},
|
194
|
+
{"Key": "CostCenter", "Value": "engineering"},
|
195
|
+
]
|
196
|
+
},
|
197
|
+
)
|
198
|
+
|
199
|
+
# Setup mocked RDS
|
200
|
+
rds = boto3.client("rds", region_name="us-east-1")
|
201
|
+
|
202
|
+
# Create mock RDS instances
|
203
|
+
db_instances = ["prod-database", "staging-database"]
|
204
|
+
for db_name in db_instances:
|
205
|
+
rds.create_db_instance(
|
206
|
+
DBInstanceIdentifier=db_name,
|
207
|
+
DBInstanceClass="db.t3.micro",
|
208
|
+
Engine="mysql",
|
209
|
+
MasterUsername="admin",
|
210
|
+
MasterUserPassword="password123",
|
211
|
+
AllocatedStorage=20,
|
212
|
+
)
|
213
|
+
|
214
|
+
# Test resource discovery simulation
|
215
|
+
# Since our dashboard uses simulated data, we'll verify the mocked services are available
|
216
|
+
|
217
|
+
# Verify EC2 instances
|
218
|
+
instances = ec2.describe_instances()
|
219
|
+
assert len(instances["Reservations"]) == 1
|
220
|
+
assert len(instances["Reservations"][0]["Instances"]) == 2
|
221
|
+
|
222
|
+
# Verify S3 buckets
|
223
|
+
buckets = s3.list_buckets()
|
224
|
+
assert len(buckets["Buckets"]) == 3
|
225
|
+
bucket_list = [b["Name"] for b in buckets["Buckets"]]
|
226
|
+
for expected_bucket in bucket_names:
|
227
|
+
assert expected_bucket in bucket_list
|
228
|
+
|
229
|
+
# Verify RDS instances
|
230
|
+
db_list = rds.describe_db_instances()
|
231
|
+
assert len(db_list["DBInstances"]) == 2
|
232
|
+
db_identifiers = [db["DBInstanceIdentifier"] for db in db_list["DBInstances"]]
|
233
|
+
for expected_db in db_instances:
|
234
|
+
assert expected_db in db_identifiers
|
235
|
+
|
236
|
+
@mock_sts
|
237
|
+
def test_cross_account_role_assumption(self):
|
238
|
+
"""Test cross-account role assumption scenarios."""
|
239
|
+
sts_client = boto3.client("sts", region_name="us-east-1")
|
240
|
+
|
241
|
+
# Mock assume role response
|
242
|
+
mock_credentials = {
|
243
|
+
"Credentials": {
|
244
|
+
"AccessKeyId": "ASSUMED-ACCESS-KEY",
|
245
|
+
"SecretAccessKey": "assumed-secret-key",
|
246
|
+
"SessionToken": "assumed-session-token",
|
247
|
+
"Expiration": datetime.now() + timedelta(hours=1),
|
248
|
+
},
|
249
|
+
"AssumedRoleUser": {
|
250
|
+
"AssumedRoleId": "AROA123456789:test-session",
|
251
|
+
"Arn": "arn:aws:sts::123456789012:assumed-role/TestRole/test-session",
|
252
|
+
},
|
253
|
+
}
|
254
|
+
|
255
|
+
# Test role assumption with discovery
|
256
|
+
config = FinOpsConfig()
|
257
|
+
config.enable_cross_account = True
|
258
|
+
|
259
|
+
discovery = EnterpriseDiscovery(config)
|
260
|
+
|
261
|
+
# Mock successful role assumption
|
262
|
+
with patch("runbooks.finops.finops_dashboard.get_account_id") as mock_get_account:
|
263
|
+
mock_get_account.return_value = "123456789012"
|
264
|
+
|
265
|
+
results = discovery.discover_accounts()
|
266
|
+
|
267
|
+
# Verify cross-account discovery succeeded
|
268
|
+
assert "account_info" in results
|
269
|
+
for profile_type, info in results["account_info"].items():
|
270
|
+
if info["status"] == "✅ Connected":
|
271
|
+
assert "account_id" in info
|
272
|
+
# In simulation mode, should have simulated account
|
273
|
+
# In real integration, would have actual account ID
|
274
|
+
|
275
|
+
def test_aws_api_error_handling(self):
|
276
|
+
"""Test handling of various AWS API errors."""
|
277
|
+
config = FinOpsConfig()
|
278
|
+
discovery = EnterpriseDiscovery(config)
|
279
|
+
|
280
|
+
# Test with mocked AWS service errors
|
281
|
+
with patch("runbooks.finops.finops_dashboard.get_account_id") as mock_get_account:
|
282
|
+
# Mock different types of AWS errors
|
283
|
+
mock_get_account.side_effect = [
|
284
|
+
Exception("UnauthorizedOperation: You are not authorized to perform this operation"),
|
285
|
+
Exception("AccessDenied: User is not authorized to assume role"),
|
286
|
+
"123456789012", # Success for third profile
|
287
|
+
]
|
288
|
+
|
289
|
+
results = discovery.discover_accounts()
|
290
|
+
|
291
|
+
# Verify error handling
|
292
|
+
assert "account_info" in results
|
293
|
+
account_info = results["account_info"]
|
294
|
+
|
295
|
+
# Should have errors for first two profiles, success for third
|
296
|
+
error_count = sum(1 for info in account_info.values() if info["status"] == "❌ Error")
|
297
|
+
success_count = sum(1 for info in account_info.values() if "✅" in info["status"])
|
298
|
+
|
299
|
+
assert error_count >= 0 # May have errors depending on mock behavior
|
300
|
+
assert success_count >= 0 # May have successes depending on mock behavior
|
301
|
+
|
302
|
+
|
303
|
+
class TestPerformanceWithLargeDatasets:
|
304
|
+
"""Test performance with large multi-account datasets."""
|
305
|
+
|
306
|
+
def test_large_account_analysis_performance(self):
|
307
|
+
"""Test performance with large number of accounts."""
|
308
|
+
import time
|
309
|
+
|
310
|
+
config = FinOpsConfig()
|
311
|
+
|
312
|
+
# Test with maximum account count
|
313
|
+
with patch("runbooks.finops.finops_dashboard.random.randint") as mock_randint:
|
314
|
+
mock_randint.return_value = 85 # Maximum accounts
|
315
|
+
|
316
|
+
analyzer = MultiAccountCostTrendAnalyzer(config)
|
317
|
+
|
318
|
+
start_time = time.time()
|
319
|
+
results = analyzer.analyze_cost_trends()
|
320
|
+
end_time = time.time()
|
321
|
+
|
322
|
+
# Performance assertions
|
323
|
+
execution_time = end_time - start_time
|
324
|
+
assert execution_time < 10.0 # Should complete within 10 seconds
|
325
|
+
|
326
|
+
# Verify results with large dataset
|
327
|
+
assert results["status"] == "completed"
|
328
|
+
cost_trends = results["cost_trends"]
|
329
|
+
assert cost_trends["total_accounts"] == 85
|
330
|
+
assert len(cost_trends["account_data"]) == 85
|
331
|
+
|
332
|
+
# Memory usage should be reasonable
|
333
|
+
import sys
|
334
|
+
|
335
|
+
assert sys.getsizeof(results) < 50_000_000 # Less than 50MB
|
336
|
+
|
337
|
+
def test_resource_heatmap_with_many_resources(self):
|
338
|
+
"""Test resource heatmap generation with many resources."""
|
339
|
+
config = FinOpsConfig()
|
340
|
+
|
341
|
+
# Create large trend data
|
342
|
+
large_account_data = []
|
343
|
+
for i in range(50): # 50 accounts
|
344
|
+
large_account_data.append(
|
345
|
+
{
|
346
|
+
"account_id": f"large-account-{i:03d}",
|
347
|
+
"account_type": "production",
|
348
|
+
"monthly_spend": 50000.0, # High spend to generate many resources
|
349
|
+
}
|
350
|
+
)
|
351
|
+
|
352
|
+
trend_data = {"cost_trends": {"account_data": large_account_data}}
|
353
|
+
|
354
|
+
from runbooks.finops.finops_dashboard import ResourceUtilizationHeatmapAnalyzer
|
355
|
+
|
356
|
+
analyzer = ResourceUtilizationHeatmapAnalyzer(config, trend_data)
|
357
|
+
|
358
|
+
import time
|
359
|
+
|
360
|
+
start_time = time.time()
|
361
|
+
results = analyzer.analyze_resource_utilization()
|
362
|
+
end_time = time.time()
|
363
|
+
|
364
|
+
# Performance assertions
|
365
|
+
execution_time = end_time - start_time
|
366
|
+
assert execution_time < 15.0 # Should complete within 15 seconds
|
367
|
+
|
368
|
+
# Verify results with large resource count
|
369
|
+
assert results["status"] == "completed"
|
370
|
+
heatmap_data = results["heatmap_data"]
|
371
|
+
assert heatmap_data["total_accounts"] == 50
|
372
|
+
assert heatmap_data["total_resources"] > 1000 # Should have many resources
|
373
|
+
|
374
|
+
def test_complete_workflow_performance(self):
|
375
|
+
"""Test complete workflow performance."""
|
376
|
+
import time
|
377
|
+
|
378
|
+
start_time = time.time()
|
379
|
+
results = run_complete_finops_analysis()
|
380
|
+
end_time = time.time()
|
381
|
+
|
382
|
+
# Performance assertions
|
383
|
+
execution_time = end_time - start_time
|
384
|
+
assert execution_time < 30.0 # Complete workflow should finish within 30 seconds
|
385
|
+
|
386
|
+
# Verify workflow completed successfully
|
387
|
+
assert results["workflow_status"] == "completed"
|
388
|
+
|
389
|
+
# Verify all components ran
|
390
|
+
assert "discovery_results" in results
|
391
|
+
assert "cost_analysis" in results
|
392
|
+
assert "audit_results" in results
|
393
|
+
assert "executive_summary" in results
|
394
|
+
assert "export_status" in results
|
395
|
+
|
396
|
+
|
397
|
+
class TestRealWorldScenarios:
|
398
|
+
"""Test scenarios that simulate real-world usage patterns."""
|
399
|
+
|
400
|
+
def test_mixed_account_types_analysis(self):
|
401
|
+
"""Test analysis with realistic mix of account types and sizes."""
|
402
|
+
config = FinOpsConfig()
|
403
|
+
analyzer = MultiAccountCostTrendAnalyzer(config)
|
404
|
+
|
405
|
+
# Override random generation for realistic test
|
406
|
+
def mock_choice(choices):
|
407
|
+
# Return realistic distribution of account types
|
408
|
+
import random
|
409
|
+
|
410
|
+
weights = [0.2, 0.3, 0.25, 0.15, 0.1, 0.05] # Weighted by typical usage
|
411
|
+
return random.choices(choices, weights=weights)[0]
|
412
|
+
|
413
|
+
with patch("runbooks.finops.finops_dashboard.random.choice", side_effect=mock_choice):
|
414
|
+
results = analyzer.analyze_cost_trends()
|
415
|
+
|
416
|
+
assert results["status"] == "completed"
|
417
|
+
|
418
|
+
# Verify realistic account distribution
|
419
|
+
cost_trends = results["cost_trends"]
|
420
|
+
account_types = [account["account_type"] for account in cost_trends["account_data"]]
|
421
|
+
|
422
|
+
# Should have variety of account types
|
423
|
+
unique_types = set(account_types)
|
424
|
+
assert len(unique_types) > 2 # At least 3 different types
|
425
|
+
|
426
|
+
# Should have reasonable cost distribution
|
427
|
+
monthly_spends = [account["monthly_spend"] for account in cost_trends["account_data"]]
|
428
|
+
min_spend = min(monthly_spends)
|
429
|
+
max_spend = max(monthly_spends)
|
430
|
+
|
431
|
+
assert min_spend > 0
|
432
|
+
assert max_spend > min_spend * 2 # Should have significant variation
|
433
|
+
|
434
|
+
def test_compliance_audit_realistic_findings(self):
|
435
|
+
"""Test audit with realistic compliance findings."""
|
436
|
+
from runbooks.finops.finops_dashboard import EnterpriseResourceAuditor
|
437
|
+
|
438
|
+
config = FinOpsConfig()
|
439
|
+
auditor = EnterpriseResourceAuditor(config)
|
440
|
+
|
441
|
+
results = auditor.run_compliance_audit()
|
442
|
+
|
443
|
+
assert results["status"] == "completed"
|
444
|
+
audit_data = results["audit_data"]
|
445
|
+
|
446
|
+
# Verify realistic audit metrics
|
447
|
+
assert audit_data["total_resources_scanned"] > 1000 # Realistic resource count
|
448
|
+
assert audit_data["accounts_audited"] >= 5 # Multi-account scope
|
449
|
+
assert audit_data["regions_covered"] >= 3 # Multi-region coverage
|
450
|
+
|
451
|
+
# Verify realistic compliance findings
|
452
|
+
findings = audit_data["compliance_findings"]
|
453
|
+
assert findings["untagged_resources"]["count"] > 50 # Common issue
|
454
|
+
assert findings["unused_resources"]["count"] > 20 # Typical waste
|
455
|
+
assert findings["security_groups"]["overly_permissive"] > 5 # Security gaps
|
456
|
+
|
457
|
+
# Verify risk scoring is realistic
|
458
|
+
risk_score = audit_data["risk_score"]
|
459
|
+
assert 30 <= risk_score["overall"] <= 90 # Realistic range
|
460
|
+
|
461
|
+
# Verify breakdown scores
|
462
|
+
breakdown = risk_score["breakdown"]
|
463
|
+
for category, score in breakdown.items():
|
464
|
+
assert 0 <= score <= 100
|
465
|
+
assert isinstance(score, (int, float))
|
466
|
+
|
467
|
+
|
468
|
+
if __name__ == "__main__":
|
469
|
+
"""
|
470
|
+
Run the integration test suite directly.
|
471
|
+
|
472
|
+
Usage:
|
473
|
+
python test_integration.py
|
474
|
+
pytest test_integration.py -v
|
475
|
+
pytest test_integration.py::TestAWSIntegrationWithMoto -v
|
476
|
+
"""
|
477
|
+
pytest.main([__file__, "-v"])
|