|
|
|
|
|
""" |
|
|
End-to-End Testing for FRED ML System |
|
|
Tests the complete workflow: Streamlit β Lambda β S3 β Reports |
|
|
""" |
|
|
|
|
|
import pytest |
|
|
import boto3 |
|
|
import json |
|
|
import time |
|
|
import os |
|
|
import sys |
|
|
from datetime import datetime, timedelta |
|
|
from pathlib import Path |
|
|
import requests |
|
|
import subprocess |
|
|
import tempfile |
|
|
import shutil |
|
|
|
|
|
|
|
|
project_root = Path(__file__).parent.parent.parent |
|
|
sys.path.append(str(project_root)) |
|
|
|
|
|
|
|
|
|
|
|
class TestFredMLEndToEnd: |
|
|
"""End-to-end test suite for FRED ML system""" |
|
|
|
|
|
@pytest.fixture(scope="class") |
|
|
def aws_clients(self): |
|
|
"""Initialize AWS clients""" |
|
|
return { |
|
|
's3': boto3.client('s3', region_name='us-west-2'), |
|
|
'lambda': boto3.client('lambda', region_name='us-west-2'), |
|
|
'ssm': boto3.client('ssm', region_name='us-west-2') |
|
|
} |
|
|
|
|
|
@pytest.fixture(scope="class") |
|
|
def test_config(self): |
|
|
"""Test configuration""" |
|
|
return { |
|
|
's3_bucket': 'fredmlv1', |
|
|
'lambda_function': 'fred-ml-processor', |
|
|
'region': 'us-west-2', |
|
|
'test_indicators': ['GDP', 'UNRATE'], |
|
|
'test_start_date': '2024-01-01', |
|
|
'test_end_date': '2024-01-31' |
|
|
} |
|
|
|
|
|
@pytest.fixture(scope="class") |
|
|
def test_report_id(self): |
|
|
"""Generate unique test report ID""" |
|
|
return f"test_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}" |
|
|
|
|
|
def test_01_aws_credentials(self, aws_clients): |
|
|
"""Test AWS credentials and permissions""" |
|
|
print("\nπ Testing AWS credentials...") |
|
|
|
|
|
|
|
|
try: |
|
|
response = aws_clients['s3'].list_objects_v2( |
|
|
Bucket='fredmlv1', |
|
|
MaxKeys=1 |
|
|
) |
|
|
print("β
S3 access verified") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β S3 access failed: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
response = aws_clients['lambda'].list_functions(MaxItems=1) |
|
|
print("β
Lambda access verified") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β Lambda access failed: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
response = aws_clients['ssm'].describe_parameters(MaxResults=1) |
|
|
print("β
SSM access verified") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β SSM access failed: {e}") |
|
|
|
|
|
def test_02_s3_bucket_exists(self, aws_clients, test_config): |
|
|
"""Test S3 bucket exists and is accessible""" |
|
|
print("\nπ¦ Testing S3 bucket...") |
|
|
|
|
|
try: |
|
|
response = aws_clients['s3'].head_bucket(Bucket=test_config['s3_bucket']) |
|
|
print(f"β
S3 bucket '{test_config['s3_bucket']}' exists and is accessible") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β S3 bucket access failed: {e}") |
|
|
|
|
|
def test_03_lambda_function_exists(self, aws_clients, test_config): |
|
|
"""Test Lambda function exists""" |
|
|
print("\nβ‘ Testing Lambda function...") |
|
|
|
|
|
try: |
|
|
response = aws_clients['lambda'].get_function( |
|
|
FunctionName=test_config['lambda_function'] |
|
|
) |
|
|
print(f"β
Lambda function '{test_config['lambda_function']}' exists") |
|
|
print(f" Runtime: {response['Configuration']['Runtime']}") |
|
|
print(f" Memory: {response['Configuration']['MemorySize']} MB") |
|
|
print(f" Timeout: {response['Configuration']['Timeout']} seconds") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β Lambda function not found: {e}") |
|
|
|
|
|
def test_04_fred_api_key_configured(self, aws_clients): |
|
|
"""Test FRED API key is configured in SSM""" |
|
|
print("\nπ Testing FRED API key...") |
|
|
|
|
|
try: |
|
|
response = aws_clients['ssm'].get_parameter( |
|
|
Name='/fred-ml/api-key', |
|
|
WithDecryption=True |
|
|
) |
|
|
api_key = response['Parameter']['Value'] |
|
|
|
|
|
if api_key and api_key != 'your-fred-api-key-here': |
|
|
print("β
FRED API key is configured") |
|
|
else: |
|
|
pytest.fail("β FRED API key not properly configured") |
|
|
except Exception as e: |
|
|
pytest.fail(f"β FRED API key not found in SSM: {e}") |
|
|
|
|
|
def test_05_lambda_function_invocation(self, aws_clients, test_config, test_report_id): |
|
|
"""Test Lambda function invocation with test data""" |
|
|
print("\nπ Testing Lambda function invocation...") |
|
|
|
|
|
|
|
|
test_payload = { |
|
|
'indicators': test_config['test_indicators'], |
|
|
'start_date': test_config['test_start_date'], |
|
|
'end_date': test_config['test_end_date'], |
|
|
'options': { |
|
|
'visualizations': True, |
|
|
'correlation': True, |
|
|
'forecasting': False, |
|
|
'statistics': True |
|
|
} |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
response = aws_clients['lambda'].invoke( |
|
|
FunctionName=test_config['lambda_function'], |
|
|
InvocationType='RequestResponse', |
|
|
Payload=json.dumps(test_payload) |
|
|
) |
|
|
|
|
|
|
|
|
response_payload = json.loads(response['Payload'].read().decode('utf-8')) |
|
|
|
|
|
if response['StatusCode'] == 200 and response_payload.get('status') == 'success': |
|
|
print("β
Lambda function executed successfully") |
|
|
print(f" Report ID: {response_payload.get('report_id')}") |
|
|
print(f" Report Key: {response_payload.get('report_key')}") |
|
|
return response_payload |
|
|
else: |
|
|
pytest.fail(f"β Lambda function failed: {response_payload}") |
|
|
|
|
|
except Exception as e: |
|
|
pytest.fail(f"β Lambda invocation failed: {e}") |
|
|
|
|
|
def test_06_s3_report_storage(self, aws_clients, test_config, test_report_id): |
|
|
"""Test S3 report storage""" |
|
|
print("\nπ Testing S3 report storage...") |
|
|
|
|
|
try: |
|
|
|
|
|
response = aws_clients['s3'].list_objects_v2( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Prefix='reports/' |
|
|
) |
|
|
|
|
|
if 'Contents' in response: |
|
|
print(f"β
Found {len(response['Contents'])} report(s) in S3") |
|
|
|
|
|
|
|
|
latest_report = max(response['Contents'], key=lambda x: x['LastModified']) |
|
|
print(f" Latest report: {latest_report['Key']}") |
|
|
print(f" Size: {latest_report['Size']} bytes") |
|
|
print(f" Last modified: {latest_report['LastModified']}") |
|
|
|
|
|
|
|
|
report_response = aws_clients['s3'].get_object( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Key=latest_report['Key'] |
|
|
) |
|
|
|
|
|
report_data = json.loads(report_response['Body'].read().decode('utf-8')) |
|
|
|
|
|
|
|
|
required_fields = ['report_id', 'timestamp', 'indicators', 'statistics', 'data'] |
|
|
for field in required_fields: |
|
|
assert field in report_data, f"Missing required field: {field}" |
|
|
|
|
|
print("β
Report structure is valid") |
|
|
print(f" Indicators: {report_data['indicators']}") |
|
|
print(f" Data points: {len(report_data['data'])}") |
|
|
|
|
|
return latest_report['Key'] |
|
|
else: |
|
|
pytest.fail("β No reports found in S3") |
|
|
|
|
|
except Exception as e: |
|
|
pytest.fail(f"β S3 report verification failed: {e}") |
|
|
|
|
|
def test_07_s3_visualization_storage(self, aws_clients, test_config): |
|
|
"""Test S3 visualization storage""" |
|
|
print("\nπ Testing S3 visualization storage...") |
|
|
|
|
|
try: |
|
|
|
|
|
response = aws_clients['s3'].list_objects_v2( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Prefix='visualizations/' |
|
|
) |
|
|
|
|
|
if 'Contents' in response: |
|
|
print(f"β
Found {len(response['Contents'])} visualization(s) in S3") |
|
|
|
|
|
|
|
|
visualization_types = ['time_series.png', 'correlation.png'] |
|
|
for viz_type in visualization_types: |
|
|
viz_objects = [obj for obj in response['Contents'] if viz_type in obj['Key']] |
|
|
if viz_objects: |
|
|
print(f" β
{viz_type}: {len(viz_objects)} file(s)") |
|
|
else: |
|
|
print(f" β οΈ {viz_type}: No files found") |
|
|
|
|
|
return True |
|
|
else: |
|
|
print("β οΈ No visualizations found in S3 (this might be expected for test runs)") |
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
pytest.fail(f"β S3 visualization verification failed: {e}") |
|
|
|
|
|
def test_08_streamlit_frontend_simulation(self, test_config): |
|
|
"""Simulate Streamlit frontend functionality""" |
|
|
print("\nπ¨ Testing Streamlit frontend simulation...") |
|
|
|
|
|
try: |
|
|
|
|
|
sys.path.append(str(project_root / 'frontend')) |
|
|
|
|
|
|
|
|
from frontend.app import load_config |
|
|
config = load_config() |
|
|
|
|
|
assert config['s3_bucket'] == test_config['s3_bucket'], "S3 bucket mismatch" |
|
|
assert config['lambda_function'] == test_config['lambda_function'], "Lambda function mismatch" |
|
|
|
|
|
print("β
Streamlit configuration is correct") |
|
|
|
|
|
|
|
|
from frontend.app import init_aws_clients |
|
|
s3_client, lambda_client = init_aws_clients() |
|
|
|
|
|
if s3_client and lambda_client: |
|
|
print("β
AWS clients initialized successfully") |
|
|
else: |
|
|
pytest.fail("β Failed to initialize AWS clients") |
|
|
|
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
pytest.fail(f"β Streamlit frontend simulation failed: {e}") |
|
|
|
|
|
def test_09_data_quality_verification(self, aws_clients, test_config): |
|
|
"""Verify data quality and completeness""" |
|
|
print("\nπ Testing data quality...") |
|
|
|
|
|
try: |
|
|
|
|
|
response = aws_clients['s3'].list_objects_v2( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Prefix='reports/' |
|
|
) |
|
|
|
|
|
if 'Contents' in response: |
|
|
latest_report = max(response['Contents'], key=lambda x: x['LastModified']) |
|
|
|
|
|
|
|
|
report_response = aws_clients['s3'].get_object( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Key=latest_report['Key'] |
|
|
) |
|
|
|
|
|
report_data = json.loads(report_response['Body'].read().decode('utf-8')) |
|
|
|
|
|
|
|
|
assert len(report_data['data']) > 0, "No data points found" |
|
|
assert len(report_data['statistics']) > 0, "No statistics found" |
|
|
|
|
|
|
|
|
for indicator in test_config['test_indicators']: |
|
|
assert indicator in report_data['indicators'], f"Missing indicator: {indicator}" |
|
|
|
|
|
|
|
|
assert report_data['start_date'] == test_config['test_start_date'], "Start date mismatch" |
|
|
assert report_data['end_date'] == test_config['test_end_date'], "End date mismatch" |
|
|
|
|
|
print("β
Data quality verification passed") |
|
|
print(f" Data points: {len(report_data['data'])}") |
|
|
print(f" Indicators: {report_data['indicators']}") |
|
|
print(f" Date range: {report_data['start_date']} to {report_data['end_date']}") |
|
|
|
|
|
return True |
|
|
else: |
|
|
pytest.fail("β No reports found for data quality verification") |
|
|
|
|
|
except Exception as e: |
|
|
pytest.fail(f"β Data quality verification failed: {e}") |
|
|
|
|
|
def test_10_performance_metrics(self, aws_clients, test_config): |
|
|
"""Test performance metrics""" |
|
|
print("\nβ‘ Testing performance metrics...") |
|
|
|
|
|
try: |
|
|
|
|
|
end_time = datetime.now() |
|
|
start_time = end_time - timedelta(hours=1) |
|
|
|
|
|
cloudwatch = boto3.client('cloudwatch', region_name=test_config['region']) |
|
|
|
|
|
|
|
|
response = cloudwatch.get_metric_statistics( |
|
|
Namespace='AWS/Lambda', |
|
|
MetricName='Invocations', |
|
|
Dimensions=[{'Name': 'FunctionName', 'Value': test_config['lambda_function']}], |
|
|
StartTime=start_time, |
|
|
EndTime=end_time, |
|
|
Period=300, |
|
|
Statistics=['Sum'] |
|
|
) |
|
|
|
|
|
if response['Datapoints']: |
|
|
invocations = sum(point['Sum'] for point in response['Datapoints']) |
|
|
print(f"β
Lambda invocations: {invocations}") |
|
|
else: |
|
|
print("β οΈ No Lambda invocation metrics found") |
|
|
|
|
|
|
|
|
response = cloudwatch.get_metric_statistics( |
|
|
Namespace='AWS/Lambda', |
|
|
MetricName='Duration', |
|
|
Dimensions=[{'Name': 'FunctionName', 'Value': test_config['lambda_function']}], |
|
|
StartTime=start_time, |
|
|
EndTime=end_time, |
|
|
Period=300, |
|
|
Statistics=['Average', 'Maximum'] |
|
|
) |
|
|
|
|
|
if response['Datapoints']: |
|
|
avg_duration = sum(point['Average'] for point in response['Datapoints']) / len(response['Datapoints']) |
|
|
max_duration = max(point['Maximum'] for point in response['Datapoints']) |
|
|
print(f"β
Average duration: {avg_duration:.2f}ms") |
|
|
print(f"β
Maximum duration: {max_duration:.2f}ms") |
|
|
else: |
|
|
print("β οΈ No Lambda duration metrics found") |
|
|
|
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β οΈ Performance metrics test failed: {e}") |
|
|
return True |
|
|
|
|
|
def test_11_error_handling(self, aws_clients, test_config): |
|
|
"""Test error handling scenarios""" |
|
|
print("\nπ¨ Testing error handling...") |
|
|
|
|
|
try: |
|
|
|
|
|
invalid_payload = { |
|
|
'indicators': ['INVALID_INDICATOR'], |
|
|
'start_date': '2024-01-01', |
|
|
'end_date': '2024-01-31', |
|
|
'options': { |
|
|
'visualizations': False, |
|
|
'correlation': False, |
|
|
'statistics': True |
|
|
} |
|
|
} |
|
|
|
|
|
response = aws_clients['lambda'].invoke( |
|
|
FunctionName=test_config['lambda_function'], |
|
|
InvocationType='RequestResponse', |
|
|
Payload=json.dumps(invalid_payload) |
|
|
) |
|
|
|
|
|
response_payload = json.loads(response['Payload'].read().decode('utf-8')) |
|
|
|
|
|
|
|
|
if response['StatusCode'] == 200: |
|
|
print("β
Error handling works correctly") |
|
|
else: |
|
|
print(f"β οΈ Unexpected response: {response_payload}") |
|
|
|
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β οΈ Error handling test failed: {e}") |
|
|
return True |
|
|
|
|
|
def test_12_cleanup_test_data(self, aws_clients, test_config, test_report_id): |
|
|
"""Clean up test data (optional)""" |
|
|
print("\nπ§Ή Testing cleanup...") |
|
|
|
|
|
try: |
|
|
|
|
|
response = aws_clients['s3'].list_objects_v2( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Prefix=f'reports/{test_report_id}/' |
|
|
) |
|
|
|
|
|
if 'Contents' in response: |
|
|
print(f"Found {len(response['Contents'])} test objects to clean up") |
|
|
|
|
|
|
|
|
for obj in response['Contents']: |
|
|
aws_clients['s3'].delete_object( |
|
|
Bucket=test_config['s3_bucket'], |
|
|
Key=obj['Key'] |
|
|
) |
|
|
|
|
|
print("β
Test data cleaned up") |
|
|
else: |
|
|
print("β
No test data to clean up") |
|
|
|
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
print(f"β οΈ Cleanup failed: {e}") |
|
|
return True |
|
|
|
|
|
def run_e2e_tests(): |
|
|
"""Run all end-to-end tests""" |
|
|
print("π Starting FRED ML End-to-End Tests") |
|
|
print("=" * 50) |
|
|
|
|
|
|
|
|
pytest.main([ |
|
|
__file__, |
|
|
'-v', |
|
|
'--tb=short', |
|
|
'--disable-warnings' |
|
|
]) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
run_e2e_tests() |