Skip to main content
Glama

Vultr MCP

by rsp2k
test_cli_error_scenarios.pyโ€ข21.1 kB
""" Comprehensive CLI error handling tests. This module tests error scenarios specific to the command-line interface, focusing on argument validation, user input handling, and error reporting. """ import pytest from unittest.mock import Mock, patch from click.testing import CliRunner from mcp_vultr.cli_main import cli from mcp_vultr.server import ( VultrAPIError, VultrAuthError, VultrRateLimitError, VultrResourceNotFoundError, VultrValidationError, ) class TestCLIAuthenticationErrors: """Test CLI authentication error scenarios.""" def test_cli_missing_api_key_environment_variable(self): """Test CLI behavior when VULTR_API_KEY environment variable is missing.""" runner = CliRunner() # Test with no API key in environment with patch.dict('os.environ', {}, clear=True): result = runner.invoke(cli, ['domains', 'list']) assert result.exit_code != 0 assert "api key" in result.output.lower() or "environment" in result.output.lower() def test_cli_empty_api_key_environment_variable(self): """Test CLI behavior with empty VULTR_API_KEY environment variable.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': ''}): result = runner.invoke(cli, ['domains', 'list']) assert result.exit_code != 0 assert "api key" in result.output.lower() or "empty" in result.output.lower() def test_cli_invalid_api_key_format(self): """Test CLI behavior with invalid API key format.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'invalid-key-format'}): with patch('mcp_vultr.client.VultrDNSClient') as mock_client: mock_client.side_effect = ValueError("Invalid API key format") result = runner.invoke(cli, ['domains', 'list']) assert result.exit_code != 0 assert "invalid" in result.output.lower() or "format" in result.output.lower() class TestDomainCommandErrors: """Test domain command error scenarios.""" @pytest.fixture def mock_client(self): """Mock client for testing.""" with patch('mcp_vultr.client.VultrDNSClient') as mock: yield mock.return_value def test_domains_list_network_error(self, mock_client): """Test domains list with network error.""" runner = CliRunner() mock_client.list_domains.side_effect = VultrAPIError(500, "Network error") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'list']) assert result.exit_code != 0 assert "error" in result.output.lower() def test_domains_list_auth_error(self, mock_client): """Test domains list with authentication error.""" runner = CliRunner() mock_client.list_domains.side_effect = VultrAuthError(401, "Invalid API key") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'list']) assert result.exit_code != 0 assert "auth" in result.output.lower() or "401" in result.output def test_domains_get_not_found_error(self, mock_client): """Test domains get with domain not found.""" runner = CliRunner() mock_client.get_domain.side_effect = VultrResourceNotFoundError(404, "Domain not found") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'get', 'nonexistent.com']) assert result.exit_code != 0 assert "not found" in result.output.lower() def test_domains_create_validation_error(self, mock_client): """Test domains create with validation error.""" runner = CliRunner() mock_client.create_domain.side_effect = VultrValidationError(400, "Invalid domain name") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'create', 'invalid..domain.com']) assert result.exit_code != 0 assert "invalid" in result.output.lower() or "validation" in result.output.lower() def test_domains_create_duplicate_error(self, mock_client): """Test domains create with duplicate domain error.""" runner = CliRunner() mock_client.create_domain.side_effect = VultrValidationError(400, "Domain already exists") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'create', 'existing.com']) assert result.exit_code != 0 assert "exists" in result.output.lower() or "duplicate" in result.output.lower() def test_domains_delete_permission_error(self, mock_client): """Test domains delete with permission error.""" runner = CliRunner() mock_client.delete_domain.side_effect = VultrAuthError(403, "Insufficient permissions") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'delete', 'protected.com']) assert result.exit_code != 0 assert "permission" in result.output.lower() or "403" in result.output def test_domains_delete_missing_confirmation(self): """Test domains delete without confirmation flag.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'delete', 'example.com']) # Should prompt for confirmation or fail without --yes flag assert result.exit_code != 0 or "confirm" in result.output.lower() class TestRecordCommandErrors: """Test record command error scenarios.""" @pytest.fixture def mock_client(self): """Mock client for testing.""" with patch('mcp_vultr.client.VultrDNSClient') as mock: yield mock.return_value def test_records_list_rate_limit_error(self, mock_client): """Test records list with rate limit error.""" runner = CliRunner() mock_client.list_records.side_effect = VultrRateLimitError(429, "Rate limit exceeded") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['records', 'list', 'example.com']) assert result.exit_code != 0 assert "rate limit" in result.output.lower() or "429" in result.output def test_records_create_invalid_record_type(self, mock_client): """Test records create with invalid record type.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'create', 'example.com', 'INVALID', 'test', '192.168.1.1' ]) # Should fail validation before making API call assert result.exit_code != 0 assert "invalid" in result.output.lower() or "type" in result.output.lower() def test_records_create_invalid_ip_address(self, mock_client): """Test records create with invalid IP address.""" runner = CliRunner() mock_client.create_record.side_effect = VultrValidationError(400, "Invalid IP address") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'create', 'example.com', 'A', 'test', '999.999.999.999' ]) assert result.exit_code != 0 assert "ip" in result.output.lower() or "invalid" in result.output.lower() def test_records_create_missing_required_arguments(self): """Test records create with missing required arguments.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): # Missing data argument result = runner.invoke(cli, [ 'records', 'create', 'example.com', 'A', 'test' ]) assert result.exit_code != 0 assert "usage" in result.output.lower() or "missing" in result.output.lower() def test_records_update_record_not_found(self, mock_client): """Test records update with record not found.""" runner = CliRunner() mock_client.update_record.side_effect = VultrResourceNotFoundError(404, "Record not found") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'update', 'example.com', 'invalid_id', '--name', 'test', '--data', '192.168.1.1' ]) assert result.exit_code != 0 assert "not found" in result.output.lower() def test_records_delete_missing_record_id(self): """Test records delete with missing record ID.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'delete', 'example.com' ]) assert result.exit_code != 0 assert "usage" in result.output.lower() or "missing" in result.output.lower() def test_records_update_no_changes_specified(self): """Test records update with no changes specified.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'update', 'example.com', 'record_id' ]) # Should require at least one change parameter assert result.exit_code != 0 assert "change" in result.output.lower() or "specify" in result.output.lower() class TestSetupCommandErrors: """Test setup command error scenarios.""" @pytest.fixture def mock_client(self): """Mock client for testing.""" with patch('mcp_vultr.client.VultrDNSClient') as mock: yield mock.return_value def test_setup_website_invalid_ip_format(self, mock_client): """Test setup website with invalid IP address format.""" runner = CliRunner() mock_client.setup_basic_website.side_effect = VultrValidationError(400, "Invalid IP address") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'setup', 'website', 'example.com', 'not.an.ip.address' ]) assert result.exit_code != 0 assert "ip" in result.output.lower() or "invalid" in result.output.lower() def test_setup_website_partial_failures(self, mock_client): """Test setup website with partial record creation failures.""" runner = CliRunner() # Mock partial success response mock_client.setup_basic_website.return_value = { "created": {"@": "record_1"}, "failed": { "www": "Duplicate record exists", "*": "Rate limit exceeded" } } with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'setup', 'website', 'example.com', '192.168.1.1' ]) # Should report both successes and failures assert "created" in result.output.lower() assert "failed" in result.output.lower() or "error" in result.output.lower() def test_setup_email_invalid_mx_server(self, mock_client): """Test setup email with invalid MX server format.""" runner = CliRunner() mock_client.setup_email_dns.side_effect = VultrValidationError(400, "Invalid MX server") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'setup', 'email', 'example.com', 'invalid..mx.server' ]) assert result.exit_code != 0 assert "mx" in result.output.lower() or "invalid" in result.output.lower() def test_setup_email_all_records_failed(self, mock_client): """Test setup email when all record creations fail.""" runner = CliRunner() mock_client.setup_email_dns.return_value = { "created": {}, "failed": { "MX": "Permission denied", "TXT": "Permission denied" } } with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'setup', 'email', 'example.com', 'mail.example.com' ]) assert result.exit_code != 0 assert "failed" in result.output.lower() or "error" in result.output.lower() class TestAnalyzeCommandErrors: """Test analyze command error scenarios.""" @pytest.fixture def mock_client(self): """Mock client for testing.""" with patch('mcp_vultr.client.VultrDNSClient') as mock: yield mock.return_value def test_analyze_domain_not_found(self, mock_client): """Test analyze with domain not found.""" runner = CliRunner() mock_client.analyze_dns_config.side_effect = VultrResourceNotFoundError(404, "Domain not found") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['analyze', 'nonexistent.com']) assert result.exit_code != 0 assert "not found" in result.output.lower() def test_analyze_empty_domain(self, mock_client): """Test analyze with domain that has no records.""" runner = CliRunner() mock_client.analyze_dns_config.return_value = { "total_records": 0, "record_types": {}, "issues": ["No DNS records found for this domain"], "score": 0, "recommendations": ["Add basic A records to make domain accessible"] } with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['analyze', 'empty.com']) # Should report the analysis even for empty domain assert result.exit_code == 0 assert "no records" in result.output.lower() or "empty" in result.output.lower() def test_analyze_api_error(self, mock_client): """Test analyze with API error.""" runner = CliRunner() mock_client.analyze_dns_config.side_effect = VultrAPIError(500, "Internal server error") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['analyze', 'example.com']) assert result.exit_code != 0 assert "error" in result.output.lower() class TestServerCommandErrors: """Test server command error scenarios.""" def test_server_command_missing_api_key(self): """Test server command without API key.""" runner = CliRunner() with patch.dict('os.environ', {}, clear=True): result = runner.invoke(cli, ['server']) assert result.exit_code != 0 assert "api key" in result.output.lower() def test_server_command_startup_error(self): """Test server command with startup error.""" runner = CliRunner() with patch('mcp_vultr.server.create_mcp_server') as mock_create: mock_create.side_effect = ValueError("Failed to initialize server") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['server']) assert result.exit_code != 0 assert "error" in result.output.lower() or "failed" in result.output.lower() class TestCLIArgumentValidation: """Test CLI argument validation errors.""" def test_invalid_command(self): """Test invalid CLI command.""" runner = CliRunner() result = runner.invoke(cli, ['invalid_command']) assert result.exit_code != 0 assert "usage" in result.output.lower() or "no such command" in result.output.lower() def test_invalid_subcommand(self): """Test invalid CLI subcommand.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'invalid_subcommand']) assert result.exit_code != 0 assert "usage" in result.output.lower() or "no such command" in result.output.lower() def test_missing_required_domain_argument(self): """Test commands missing required domain argument.""" runner = CliRunner() commands_requiring_domain = [ ['records', 'list'], ['records', 'create', 'A', 'test', '192.168.1.1'], ['domains', 'get'], ['analyze'] ] for command in commands_requiring_domain: with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, command) assert result.exit_code != 0 assert "usage" in result.output.lower() or "missing" in result.output.lower() def test_invalid_record_type_argument(self): """Test records create with invalid record type.""" runner = CliRunner() with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, [ 'records', 'create', 'example.com', 'INVALIDTYPE', 'test', '192.168.1.1' ]) assert result.exit_code != 0 assert "invalid" in result.output.lower() or "type" in result.output.lower() def test_help_command_functionality(self): """Test that help commands work correctly.""" runner = CliRunner() help_commands = [ ['--help'], ['domains', '--help'], ['records', '--help'], ['setup', '--help'], ['analyze', '--help'] ] for command in help_commands: result = runner.invoke(cli, command) assert result.exit_code == 0 assert "usage" in result.output.lower() or "help" in result.output.lower() class TestCLIOutputFormatting: """Test CLI output formatting in error scenarios.""" @pytest.fixture def mock_client(self): """Mock client for testing.""" with patch('mcp_vultr.client.VultrDNSClient') as mock: yield mock.return_value def test_json_output_format_with_error(self, mock_client): """Test JSON output format during error scenarios.""" runner = CliRunner() mock_client.list_domains.side_effect = VultrAPIError(500, "Server error") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'list', '--format', 'json']) assert result.exit_code != 0 # Error output might still be in JSON format or regular text assert "error" in result.output.lower() def test_table_output_format_with_empty_results(self, mock_client): """Test table output format with empty results.""" runner = CliRunner() mock_client.list_domains.return_value = [] with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'list', '--format', 'table']) assert result.exit_code == 0 assert "no domains" in result.output.lower() or len(result.output.strip()) == 0 def test_verbose_output_with_errors(self, mock_client): """Test verbose output during error scenarios.""" runner = CliRunner() mock_client.get_domain.side_effect = VultrResourceNotFoundError(404, "Domain not found") with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = runner.invoke(cli, ['domains', 'get', 'nonexistent.com', '--verbose']) assert result.exit_code != 0 # Verbose mode should include more details about the error assert "not found" in result.output.lower() # Verbose output might include more technical 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/rsp2k/mcp-vultr'

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