Skip to main content
Glama

AWS Model Context Protocol Server

by alexei-led
test_resources.py28.2 kB
"""Unit tests for AWS MCP Server resources module. These tests verify that the resources functionality in the resources module works correctly, with appropriate mocking to avoid actual AWS API calls. """ import os from unittest.mock import MagicMock, patch import pytest from botocore.exceptions import ClientError from aws_mcp_server.resources import ( _get_region_description, _get_region_geographic_location, _mask_key, get_aws_account_info, get_aws_environment, get_aws_profiles, get_aws_regions, get_region_available_services, get_region_details, register_resources, ) @pytest.fixture def mock_config_files(monkeypatch, tmp_path): """Create mock AWS config and credentials files for testing.""" config_dir = tmp_path / ".aws" config_dir.mkdir() # Create mock config file config_file = config_dir / "config" config_file.write_text("[default]\nregion = us-west-2\n\n[profile dev]\nregion = us-east-1\n\n[profile prod]\nregion = eu-west-1\n") # Create mock credentials file creds_file = config_dir / "credentials" creds_file.write_text( "[default]\n" "aws_access_key_id = AKIADEFAULT000000000\n" "aws_secret_access_key = 1234567890abcdef1234567890abcdef\n" "\n" "[dev]\n" "aws_access_key_id = AKIADEV0000000000000\n" "aws_secret_access_key = abcdef1234567890abcdef1234567890\n" "\n" "[test]\n" # Profile in credentials but not in config "aws_access_key_id = AKIATEST000000000000\n" "aws_secret_access_key = test1234567890abcdef1234567890ab\n" ) # Set environment to use these files monkeypatch.setenv("HOME", str(tmp_path)) return tmp_path def test_get_aws_profiles(mock_config_files): """Test retrieving AWS profiles from config files.""" profiles = get_aws_profiles() # Should find all profiles from both files, with no duplicates assert set(profiles) == {"default", "dev", "prod", "test"} @patch("boto3.session.Session") def test_get_aws_regions(mock_session): """Test retrieving AWS regions with mocked boto3.""" # Mock boto3 session and client response mock_ec2 = MagicMock() mock_session.return_value.client.return_value = mock_ec2 mock_ec2.describe_regions.return_value = { "Regions": [ {"RegionName": "us-east-1"}, {"RegionName": "us-west-2"}, {"RegionName": "eu-central-1"}, ] } regions = get_aws_regions() # Check regions are properly formatted assert len(regions) == 3 assert regions[0]["RegionName"] == "eu-central-1" # Sorted alphabetically assert regions[0]["RegionDescription"] == "EU Central (Frankfurt)" assert regions[1]["RegionName"] == "us-east-1" assert regions[2]["RegionName"] == "us-west-2" # Verify correct session and client creation mock_session.assert_called_once() mock_session.return_value.client.assert_called_once_with("ec2") @patch("boto3.session.Session") def test_get_aws_regions_fallback(mock_session): """Test fallback behavior when region retrieval fails.""" # Mock boto3 to raise an exception mock_session.return_value.client.side_effect = ClientError({"Error": {"Code": "AccessDenied", "Message": "Access denied"}}, "DescribeRegions") regions = get_aws_regions() # Should fall back to static region list assert len(regions) >= 12 # Should include at least the major regions assert any(r["RegionName"] == "us-east-1" for r in regions) assert any(r["RegionName"] == "eu-west-1" for r in regions) @patch("boto3.session.Session") def test_get_aws_environment(mock_session, monkeypatch): """Test retrieving AWS environment information.""" # Set up environment variables monkeypatch.setenv("AWS_PROFILE", "test-profile") monkeypatch.setenv("AWS_REGION", "us-west-2") # Mock boto3 credentials mock_credentials = MagicMock() mock_credentials.method = "shared-credentials-file" mock_session.return_value.get_credentials.return_value = mock_credentials env_info = get_aws_environment() # Check environment information assert env_info["aws_profile"] == "test-profile" assert env_info["aws_region"] == "us-west-2" assert env_info["has_credentials"] is True assert env_info["credentials_source"] == "profile" @patch("boto3.session.Session") def test_get_aws_environment_no_credentials(mock_session, monkeypatch): """Test environment info with no credentials.""" # Clear relevant environment variables for var in ["AWS_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", "AWS_ACCESS_KEY_ID"]: if var in os.environ: monkeypatch.delenv(var) # No credentials available mock_session.return_value.get_credentials.return_value = None env_info = get_aws_environment() # Check environment information with defaults assert env_info["aws_profile"] == "default" assert env_info["aws_region"] == "us-east-1" assert env_info["has_credentials"] is False assert env_info["credentials_source"] == "none" @patch("boto3.session.Session") def test_get_aws_account_info(mock_session): """Test retrieving AWS account information.""" # Mock boto3 clients mock_sts = MagicMock() mock_iam = MagicMock() mock_org = MagicMock() mock_session.return_value.client.side_effect = lambda service: {"sts": mock_sts, "iam": mock_iam, "organizations": mock_org}[service] # Mock API responses mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} mock_iam.list_account_aliases.return_value = {"AccountAliases": ["my-account"]} mock_org.describe_organization.return_value = {"OrganizationId": "o-abcdef1234"} account_info = get_aws_account_info() # Check account information assert account_info["account_id"] == "123456789012" assert account_info["account_alias"] == "my-account" assert account_info["organization_id"] == "o-abcdef1234" @patch("boto3.session.Session") def test_get_aws_account_info_minimal(mock_session): """Test account info with minimal permissions.""" # Mock boto3 sts client, but iam/org calls fail mock_sts = MagicMock() def mock_client(service): if service == "sts": return mock_sts elif service == "iam": raise ClientError({"Error": {"Code": "AccessDenied"}}, "ListAccountAliases") else: raise ClientError({"Error": {"Code": "AccessDenied"}}, "DescribeAccount") mock_session.return_value.client.side_effect = mock_client # Mock API response mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} account_info = get_aws_account_info() # Should have account ID but not alias or org ID assert account_info["account_id"] == "123456789012" assert account_info["account_alias"] is None assert account_info["organization_id"] is None def test_register_resources(): """Test registering MCP resources.""" # Mock MCP instance mock_mcp = MagicMock() # Register resources register_resources(mock_mcp) # Verify resource registration assert mock_mcp.resource.call_count == 5 # Should register 5 resources # Check that resource was called with the correct URIs, names and descriptions expected_resources = [ {"uri": "aws://config/profiles", "name": "aws_profiles", "description": "Get available AWS profiles"}, {"uri": "aws://config/regions", "name": "aws_regions", "description": "Get available AWS regions"}, {"uri": "aws://config/regions/{region}", "name": "aws_region_details", "description": "Get detailed information about a specific AWS region"}, {"uri": "aws://config/environment", "name": "aws_environment", "description": "Get AWS environment information"}, {"uri": "aws://config/account", "name": "aws_account", "description": "Get AWS account information"}, ] # Extract parameters from each call for call in mock_mcp.resource.call_args_list: found = False for resource in expected_resources: if resource["uri"] == call.kwargs.get("uri"): # Check name and description too assert call.kwargs.get("name") == resource["name"] assert call.kwargs.get("description") == resource["description"] found = True break assert found, f"URI {call.kwargs.get('uri')} not found in expected resources" def test_get_region_description(): """Test the region description utility function.""" # Test known regions assert _get_region_description("us-east-1") == "US East (N. Virginia)" assert _get_region_description("eu-west-2") == "EU West (London)" assert _get_region_description("ap-southeast-1") == "Asia Pacific (Singapore)" # Test unknown regions assert _get_region_description("unknown-region-1") == "AWS Region unknown-region-1" assert _get_region_description("test-region-2") == "AWS Region test-region-2" def test_mask_key(): """Test the key masking utility function.""" # Test empty input assert _mask_key("") == "" # Test short keys (less than 3 chars) assert _mask_key("a") == "a" assert _mask_key("ab") == "ab" # Test longer keys assert _mask_key("abc") == "abc" assert _mask_key("abcd") == "abc*" assert _mask_key("abcdef") == "abc***" assert _mask_key("AKIAIOSFODNN7EXAMPLE") == "AKI*****************" @patch("configparser.ConfigParser") @patch("os.path.exists") def test_get_aws_profiles_exception(mock_exists, mock_config_parser): """Test exception handling in get_aws_profiles.""" # Setup mocks mock_exists.return_value = True mock_parser_instance = MagicMock() mock_config_parser.return_value = mock_parser_instance # Simulate an exception when reading the config mock_parser_instance.read.side_effect = Exception("Config file error") # Call function profiles = get_aws_profiles() # Verify profiles contains only the default profile assert profiles == ["default"] assert mock_parser_instance.read.called @patch("boto3.session.Session") def test_get_aws_regions_generic_exception(mock_session): """Test general exception handling in get_aws_regions.""" # Mock boto3 to raise a generic exception (not ClientError) mock_session.return_value.client.side_effect = Exception("Generic error") # Call function regions = get_aws_regions() # Should return empty list for general exceptions assert len(regions) == 0 assert isinstance(regions, list) @patch("boto3.session.Session") def test_get_aws_environment_credential_methods(mock_session): """Test different credential methods in get_aws_environment.""" # Set up different credential methods to test test_cases = [ ("environment", "environment"), ("iam-role", "instance-profile"), ("assume-role", "assume-role"), ("container-role", "container-role"), ("unknown-method", "profile"), # Should fall back to "profile" for unknown methods ] for method, expected_source in test_cases: # Reset mock mock_session.reset_mock() # Set up mock credentials mock_credentials = MagicMock() mock_credentials.method = method mock_session.return_value.get_credentials.return_value = mock_credentials # Call function env_info = get_aws_environment() # Verify credential source assert env_info["has_credentials"] is True assert env_info["credentials_source"] == expected_source @patch("boto3.session.Session") def test_get_aws_environment_exception(mock_session): """Test exception handling in get_aws_environment.""" # Mock boto3 to raise an exception mock_session.return_value.get_credentials.side_effect = Exception("Credential error") # Call function env_info = get_aws_environment() # Should still return valid env info with default values assert env_info["aws_profile"] == "default" assert env_info["aws_region"] == "us-east-1" assert env_info["has_credentials"] is False assert env_info["credentials_source"] == "none" @patch("boto3.session.Session") def test_get_aws_account_info_with_org(mock_session): """Test AWS account info with organization access.""" # Mock boto3 clients mock_sts = MagicMock() mock_iam = MagicMock() mock_org = MagicMock() mock_session.return_value.client.side_effect = lambda service: {"sts": mock_sts, "iam": mock_iam, "organizations": mock_org}[service] # Mock API responses mock_sts.get_caller_identity.return_value = {"Account": "123456789012"} mock_iam.list_account_aliases.return_value = {"AccountAliases": ["my-account"]} # Mock org response for describe_organization mock_org.describe_organization.return_value = {"OrganizationId": None} # Call function account_info = get_aws_account_info() # Verify account info (organization_id should be None) assert account_info["account_id"] == "123456789012" assert account_info["account_alias"] == "my-account" assert account_info["organization_id"] is None @patch("boto3.session.Session") def test_get_aws_account_info_general_exception(mock_session): """Test general exception handling in get_aws_account_info.""" # Mock boto3 to raise a generic exception mock_session.return_value.client.side_effect = Exception("Generic error") # Call function account_info = get_aws_account_info() # All fields should be None assert account_info["account_id"] is None assert account_info["account_alias"] is None assert account_info["organization_id"] is None @patch("aws_mcp_server.resources.get_aws_profiles") @patch("os.environ.get") def test_resource_aws_profiles(mock_environ_get, mock_get_aws_profiles): """Test the aws_profiles resource function implementation.""" # Set up environment mocks mock_environ_get.return_value = "test-profile" # Set up profiles mock mock_get_aws_profiles.return_value = ["default", "test-profile", "dev"] # Create a mock function that simulates the decorated function # Note: We need to call the mocked functions, not the original ones async def mock_resource_function(): profiles = mock_get_aws_profiles.return_value current_profile = mock_environ_get.return_value return {"profiles": [{"name": profile, "is_current": profile == current_profile} for profile in profiles]} # Call the function import asyncio result = asyncio.run(mock_resource_function()) # Verify the result assert "profiles" in result assert len(result["profiles"]) == 3 # Check that current profile is marked current_profile = None for profile in result["profiles"]: if profile["is_current"]: current_profile = profile["name"] assert current_profile == "test-profile" @patch("aws_mcp_server.resources.get_aws_regions") @patch("os.environ.get") def test_resource_aws_regions(mock_environ_get, mock_get_aws_regions): """Test the aws_regions resource function implementation.""" # Set up environment mocks to return us-west-2 for either AWS_REGION or AWS_DEFAULT_REGION mock_environ_get.side_effect = lambda key, default=None: "us-west-2" if key in ("AWS_REGION", "AWS_DEFAULT_REGION") else default # Set up regions mock mock_get_aws_regions.return_value = [ {"RegionName": "us-east-1", "RegionDescription": "US East (N. Virginia)"}, {"RegionName": "us-west-2", "RegionDescription": "US West (Oregon)"}, ] # Create a mock function that simulates the decorated function # Note: We need to call the mocked functions, not the original ones async def mock_resource_function(): regions = mock_get_aws_regions.return_value current_region = "us-west-2" # From the mock_environ_get.side_effect return { "regions": [ { "name": region["RegionName"], "description": region["RegionDescription"], "is_current": region["RegionName"] == current_region, } for region in regions ] } # Call the function import asyncio result = asyncio.run(mock_resource_function()) # Verify the result assert "regions" in result assert len(result["regions"]) == 2 # Check that current region is marked current_region = None for region in result["regions"]: if region["is_current"]: current_region = region["name"] assert current_region == "us-west-2" @patch("aws_mcp_server.resources.get_aws_environment") def test_resource_aws_environment(mock_get_aws_environment): """Test the aws_environment resource function implementation.""" # Set up environment mock mock_env = { "aws_profile": "test-profile", "aws_region": "us-west-2", "has_credentials": True, "credentials_source": "profile", } mock_get_aws_environment.return_value = mock_env # Create a mock function that simulates the decorated function # Note: We need to call the mocked function, not the original one async def mock_resource_function(): return mock_get_aws_environment.return_value # Call the function import asyncio result = asyncio.run(mock_resource_function()) # Verify the result is the same as the mock env assert result == mock_env @patch("aws_mcp_server.resources.get_aws_account_info") def test_resource_aws_account(mock_get_aws_account_info): """Test the aws_account resource function implementation.""" # Set up account info mock mock_account_info = { "account_id": "123456789012", "account_alias": "test-account", "organization_id": "o-abcdef123456", } mock_get_aws_account_info.return_value = mock_account_info # Create a mock function that simulates the decorated function # Note: We need to call the mocked function, not the original one async def mock_resource_function(): return mock_get_aws_account_info.return_value # Call the function import asyncio result = asyncio.run(mock_resource_function()) # Verify the result is the same as the mock account info assert result == mock_account_info def test_get_region_geographic_location(): """Test the region geographic location utility function.""" # Test known regions us_east_1 = _get_region_geographic_location("us-east-1") assert us_east_1["continent"] == "North America" assert us_east_1["country"] == "United States" assert us_east_1["city"] == "Ashburn, Virginia" eu_west_2 = _get_region_geographic_location("eu-west-2") assert eu_west_2["continent"] == "Europe" assert eu_west_2["country"] == "United Kingdom" assert eu_west_2["city"] == "London" # Test unknown region unknown = _get_region_geographic_location("unknown-region") assert unknown["continent"] == "Unknown" assert unknown["country"] == "Unknown" assert unknown["city"] == "Unknown" @patch("boto3.session.Session") def test_get_region_available_services(mock_session): """Test retrieving available AWS services for a region using Service Quotas API.""" # Mock the Service Quotas client mock_quotas_client = MagicMock() # Set up the mock session to return our mock clients def mock_client(service_name, **kwargs): if service_name == "service-quotas": return mock_quotas_client return MagicMock() mock_session.return_value.client.side_effect = mock_client # Mock the Service Quotas API response mock_quotas_client.list_services.return_value = { "Services": [ {"ServiceCode": "AWS.EC2", "ServiceName": "Amazon Elastic Compute Cloud"}, {"ServiceCode": "AWS.S3", "ServiceName": "Amazon Simple Storage Service"}, {"ServiceCode": "Lambda", "ServiceName": "AWS Lambda"}, {"ServiceCode": "Organizations", "ServiceName": "AWS Organizations"}, {"ServiceCode": "AWS.CloudFormation", "ServiceName": "AWS CloudFormation"}, ], "NextToken": None, } # Call the function services = get_region_available_services(mock_session.return_value, "us-east-1") # Verify the results assert len(services) == 5 # Verify service code transformations assert {"id": "ec2", "name": "Amazon Elastic Compute Cloud"} in services assert {"id": "s3", "name": "Amazon Simple Storage Service"} in services assert {"id": "lambda", "name": "AWS Lambda"} in services assert {"id": "organizations", "name": "AWS Organizations"} in services assert {"id": "cloudformation", "name": "AWS CloudFormation"} in services # Verify the API was called correctly mock_quotas_client.list_services.assert_called_once() @patch("boto3.session.Session") def test_get_region_available_services_pagination(mock_session): """Test pagination handling in Service Quotas API.""" # Mock the Service Quotas client mock_quotas_client = MagicMock() # Set up the mock session to return our mock client mock_session.return_value.client.return_value = mock_quotas_client # Mock paginated responses mock_quotas_client.list_services.side_effect = [ { "Services": [ {"ServiceCode": "AWS.EC2", "ServiceName": "Amazon Elastic Compute Cloud"}, {"ServiceCode": "AWS.S3", "ServiceName": "Amazon Simple Storage Service"}, ], "NextToken": "next-token-1", }, { "Services": [{"ServiceCode": "Lambda", "ServiceName": "AWS Lambda"}, {"ServiceCode": "AWS.DynamoDB", "ServiceName": "Amazon DynamoDB"}], "NextToken": None, }, ] # Call the function services = get_region_available_services(mock_session.return_value, "us-east-1") # Verify the results assert len(services) == 4 # Verify the pagination was handled correctly assert mock_quotas_client.list_services.call_count == 2 # First call should have no NextToken mock_quotas_client.list_services.assert_any_call() # Second call should include the NextToken mock_quotas_client.list_services.assert_any_call(NextToken="next-token-1") @patch("boto3.session.Session") def test_get_region_available_services_fallback(mock_session): """Test fallback to client creation when Service Quotas API fails.""" # Mock the session to raise an exception for Service Quotas def mock_client(service_name, **kwargs): if service_name == "service-quotas": raise ClientError({"Error": {"Code": "AccessDenied"}}, "ListServices") # For other services, return a mock to simulate success return MagicMock() mock_session.return_value.client.side_effect = mock_client # Call the function services = get_region_available_services(mock_session.return_value, "us-east-1") # Verify we got results from fallback method assert len(services) > 0 # At least these common services should be in the result common_service_ids = [service["id"] for service in services] for service_id in ["ec2", "s3", "lambda"]: assert service_id in common_service_ids # Verify services have the correct structure for service in services: assert "id" in service assert "name" in service @patch("aws_mcp_server.resources.get_region_available_services") @patch("boto3.session.Session") def test_get_region_details(mock_session, mock_get_region_available_services): """Test retrieving detailed AWS region information.""" # Mock the boto3 session and clients mock_ec2 = MagicMock() # Handle different boto3 client calls def mock_client(service_name, **kwargs): if service_name == "ec2": return mock_ec2 # Return a mock for other services return MagicMock() mock_session.return_value.client.side_effect = mock_client # Mock EC2 availability zones response mock_ec2.describe_availability_zones.return_value = { "AvailabilityZones": [ {"ZoneName": "us-east-1a", "State": "available", "ZoneId": "use1-az1", "ZoneType": "availability-zone"}, {"ZoneName": "us-east-1b", "State": "available", "ZoneId": "use1-az2", "ZoneType": "availability-zone"}, ] } # Mock the services list mock_services = [{"id": "ec2", "name": "EC2"}, {"id": "s3", "name": "S3"}, {"id": "lambda", "name": "Lambda"}] mock_get_region_available_services.return_value = mock_services # Call the function being tested region_details = get_region_details("us-east-1") # Verify basic region information assert region_details["code"] == "us-east-1" assert region_details["name"] == "US East (N. Virginia)" # Verify geographic location information geo_location = region_details["geographic_location"] assert geo_location["continent"] == "North America" assert geo_location["country"] == "United States" assert geo_location["city"] == "Ashburn, Virginia" # Verify availability zones assert len(region_details["availability_zones"]) == 2 assert region_details["availability_zones"][0]["name"] == "us-east-1a" assert region_details["availability_zones"][1]["name"] == "us-east-1b" # Verify services assert region_details["services"] == mock_services mock_get_region_available_services.assert_called_once_with(mock_session.return_value, "us-east-1") @patch("aws_mcp_server.resources.get_region_available_services") @patch("boto3.session.Session") def test_get_region_details_with_error(mock_session, mock_get_region_available_services): """Test region details with API errors.""" # Mock boto3 to raise an exception mock_session.return_value.client.side_effect = ClientError({"Error": {"Code": "AccessDenied", "Message": "Access denied"}}, "DescribeAvailabilityZones") # Mock the get_region_available_services function to return an empty list mock_get_region_available_services.return_value = [] # Call the function being tested region_details = get_region_details("us-east-1") # Should still return basic information even if AWS APIs fail assert region_details["code"] == "us-east-1" assert region_details["name"] == "US East (N. Virginia)" assert "geographic_location" in region_details assert len(region_details["availability_zones"]) == 0 assert region_details["services"] == [] mock_get_region_available_services.assert_called_once_with(mock_session.return_value, "us-east-1") @patch("aws_mcp_server.resources.get_region_details") def test_resource_aws_region_details(mock_get_region_details): """Test the aws_region_details resource function implementation.""" # Set up region details mock mock_region_details = { "code": "us-east-1", "name": "US East (N. Virginia)", "geographic_location": {"continent": "North America", "country": "United States", "city": "Ashburn, Virginia"}, "availability_zones": [ {"name": "us-east-1a", "state": "available", "zone_id": "use1-az1", "zone_type": "availability-zone"}, {"name": "us-east-1b", "state": "available", "zone_id": "use1-az2", "zone_type": "availability-zone"}, ], "services": [{"id": "ec2", "name": "EC2"}, {"id": "s3", "name": "S3"}, {"id": "lambda", "name": "Lambda"}], "is_current": True, } mock_get_region_details.return_value = mock_region_details # Create a mock function that simulates the decorated function async def mock_resource_function(region: str): return mock_get_region_details(region) # Call the function import asyncio result = asyncio.run(mock_resource_function("us-east-1")) # Verify the function was called with the correct region code mock_get_region_details.assert_called_once_with("us-east-1") # Verify the result is the same as the mock details assert result == mock_region_details

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/alexei-led/aws-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server