"""Tests for the CLI module.""" import pytest from unittest.mock import patch, AsyncMock, MagicMock from click.testing import CliRunner from vultr_dns_mcp.cli import cli, main @pytest.fixture def cli_runner(): """Create a CLI test runner.""" return CliRunner() @pytest.fixture def mock_client_for_cli(): """Create a mock VultrDNSClient for CLI tests.""" mock_client = AsyncMock() # Configure mock responses mock_client.domains.return_value = [ {"domain": "example.com", "date_created": "2024-01-01"}, {"domain": "test.com", "date_created": "2024-01-02"} ] mock_client.get_domain_summary.return_value = { "domain": "example.com", "total_records": 5, "record_types": {"A": 2, "MX": 1, "TXT": 2}, "configuration": { "has_root_record": True, "has_www_subdomain": True, "has_email_setup": True } } mock_client.records.return_value = [ {"id": "rec1", "type": "A", "name": "@", "data": "192.168.1.100", "ttl": 300}, {"id": "rec2", "type": "A", "name": "www", "data": "192.168.1.100", "ttl": 300} ] mock_client.add_domain.return_value = {"domain": "newdomain.com"} mock_client.add_record.return_value = {"id": "new-rec", "type": "A", "name": "www", "data": "192.168.1.100"} mock_client.remove_record.return_value = True mock_client.setup_basic_website.return_value = { "domain": "example.com", "created_records": ["A record for root domain", "A record for www subdomain"], "errors": [] } mock_client.setup_email.return_value = { "domain": "example.com", "created_records": ["MX record for mail.example.com"], "errors": [] } return mock_client @pytest.mark.unit class TestCLIBasics: """Test basic CLI functionality.""" def test_cli_help(self, cli_runner): """Test CLI help output.""" result = cli_runner.invoke(cli, ['--help']) assert result.exit_code == 0 assert "Vultr DNS MCP" in result.output def test_cli_version(self, cli_runner): """Test CLI version output.""" result = cli_runner.invoke(cli, ['--version']) assert result.exit_code == 0 def test_cli_without_api_key(self, cli_runner): """Test CLI behavior without API key.""" with patch.dict('os.environ', {}, clear=True): result = cli_runner.invoke(cli, ['domains', 'list']) assert result.exit_code == 1 assert "VULTR_API_KEY is required" in result.output @pytest.mark.unit class TestServerCommand: """Test the server command.""" def test_server_command_without_api_key(self, cli_runner): """Test server command without API key.""" with patch.dict('os.environ', {}, clear=True): result = cli_runner.invoke(cli, ['server']) assert result.exit_code == 1 assert "VULTR_API_KEY is required" in result.output @patch('vultr_dns_mcp.cli.run_server') def test_server_command_with_api_key(self, mock_run_server, cli_runner): """Test server command with API key.""" with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): # Mock run_server to avoid actually starting the server mock_run_server.side_effect = KeyboardInterrupt() result = cli_runner.invoke(cli, ['server']) assert "Starting Vultr DNS MCP Server" in result.output mock_run_server.assert_called_once_with('test-key') @patch('vultr_dns_mcp.cli.run_server') def test_server_command_with_error(self, mock_run_server, cli_runner): """Test server command with error.""" with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): mock_run_server.side_effect = Exception("Server error") result = cli_runner.invoke(cli, ['server']) assert result.exit_code == 1 assert "Server error" in result.output @pytest.mark.unit class TestDomainsCommands: """Test domain management commands.""" @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_list_domains(self, mock_client_class, cli_runner, mock_client_for_cli): """Test domains list command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'list']) assert result.exit_code == 0 assert "example.com" in result.output assert "test.com" in result.output mock_client_for_cli.domains.assert_called_once() @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_list_domains_empty(self, mock_client_class, cli_runner): """Test domains list command with no domains.""" mock_client = AsyncMock() mock_client.domains.return_value = [] mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'list']) assert result.exit_code == 0 assert "No domains found" in result.output @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_domain_info(self, mock_client_class, cli_runner, mock_client_for_cli): """Test domains info command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'info', 'example.com']) assert result.exit_code == 0 assert "example.com" in result.output assert "Total Records: 5" in result.output mock_client_for_cli.get_domain_summary.assert_called_once_with('example.com') @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_domain_info_error(self, mock_client_class, cli_runner): """Test domains info command with error.""" mock_client = AsyncMock() mock_client.get_domain_summary.return_value = {"error": "Domain not found"} mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'info', 'nonexistent.com']) assert result.exit_code == 1 assert "Domain not found" in result.output @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_create_domain(self, mock_client_class, cli_runner, mock_client_for_cli): """Test domains create command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'create', 'newdomain.com', '192.168.1.100']) assert result.exit_code == 0 assert "Created domain newdomain.com" in result.output mock_client_for_cli.add_domain.assert_called_once_with('newdomain.com', '192.168.1.100') @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_create_domain_error(self, mock_client_class, cli_runner): """Test domains create command with error.""" mock_client = AsyncMock() mock_client.add_domain.return_value = {"error": "Domain already exists"} mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'create', 'existing.com', '192.168.1.100']) assert result.exit_code == 1 assert "Domain already exists" in result.output @pytest.mark.unit class TestRecordsCommands: """Test DNS records commands.""" @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_list_records(self, mock_client_class, cli_runner, mock_client_for_cli): """Test records list command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['records', 'list', 'example.com']) assert result.exit_code == 0 assert "example.com" in result.output assert "rec1" in result.output mock_client_for_cli.records.assert_called_once_with('example.com') @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_list_records_filtered(self, mock_client_class, cli_runner, mock_client_for_cli): """Test records list command with type filter.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['records', 'list', 'example.com', '--type', 'A']) assert result.exit_code == 0 mock_client_for_cli.find_records_by_type.assert_called_once_with('example.com', 'A') @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_list_records_empty(self, mock_client_class, cli_runner): """Test records list command with no records.""" mock_client = AsyncMock() mock_client.records.return_value = [] mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['records', 'list', 'example.com']) assert result.exit_code == 0 assert "No records found" in result.output @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_add_record(self, mock_client_class, cli_runner, mock_client_for_cli): """Test records add command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'records', 'add', 'example.com', 'A', 'www', '192.168.1.100' ]) assert result.exit_code == 0 assert "Created A record" in result.output mock_client_for_cli.add_record.assert_called_once_with( 'example.com', 'A', 'www', '192.168.1.100', None, None ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_add_record_with_ttl_and_priority(self, mock_client_class, cli_runner, mock_client_for_cli): """Test records add command with TTL and priority.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'records', 'add', 'example.com', 'MX', '@', 'mail.example.com', '--ttl', '600', '--priority', '10' ]) assert result.exit_code == 0 mock_client_for_cli.add_record.assert_called_once_with( 'example.com', 'MX', '@', 'mail.example.com', 600, 10 ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_add_record_error(self, mock_client_class, cli_runner): """Test records add command with error.""" mock_client = AsyncMock() mock_client.add_record.return_value = {"error": "Invalid record"} mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'records', 'add', 'example.com', 'A', 'www', 'invalid-ip' ]) assert result.exit_code == 1 assert "Invalid record" in result.output @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_delete_record(self, mock_client_class, cli_runner, mock_client_for_cli): """Test records delete command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'records', 'delete', 'example.com', 'record-123' ], input='y\n') # Confirm deletion assert result.exit_code == 0 assert "Deleted record record-123" in result.output mock_client_for_cli.remove_record.assert_called_once_with('example.com', 'record-123') @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_delete_record_failure(self, mock_client_class, cli_runner): """Test records delete command failure.""" mock_client = AsyncMock() mock_client.remove_record.return_value = False mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'records', 'delete', 'example.com', 'record-123' ], input='y\n') assert result.exit_code == 1 assert "Failed to delete" in result.output @pytest.mark.unit class TestSetupCommands: """Test setup utility commands.""" @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_website(self, mock_client_class, cli_runner, mock_client_for_cli): """Test setup-website command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-website', 'example.com', '192.168.1.100' ]) assert result.exit_code == 0 assert "Setting up website" in result.output assert "Website setup complete" in result.output mock_client_for_cli.setup_basic_website.assert_called_once_with( 'example.com', '192.168.1.100', True, None ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_website_no_www(self, mock_client_class, cli_runner, mock_client_for_cli): """Test setup-website command without www.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-website', 'example.com', '192.168.1.100', '--no-www' ]) assert result.exit_code == 0 mock_client_for_cli.setup_basic_website.assert_called_once_with( 'example.com', '192.168.1.100', False, None ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_website_with_ttl(self, mock_client_class, cli_runner, mock_client_for_cli): """Test setup-website command with custom TTL.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-website', 'example.com', '192.168.1.100', '--ttl', '600' ]) assert result.exit_code == 0 mock_client_for_cli.setup_basic_website.assert_called_once_with( 'example.com', '192.168.1.100', True, 600 ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_website_with_errors(self, mock_client_class, cli_runner): """Test setup-website command with errors.""" mock_client = AsyncMock() mock_client.setup_basic_website.return_value = { "domain": "example.com", "created_records": ["A record for root domain"], "errors": ["Failed to create www record"] } mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-website', 'example.com', '192.168.1.100' ]) assert result.exit_code == 0 assert "Setup completed with some errors" in result.output @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_email(self, mock_client_class, cli_runner, mock_client_for_cli): """Test setup-email command.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-email', 'example.com', 'mail.example.com' ]) assert result.exit_code == 0 assert "Setting up email" in result.output assert "Email setup complete" in result.output mock_client_for_cli.setup_email.assert_called_once_with( 'example.com', 'mail.example.com', 10, None ) @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_setup_email_custom_priority(self, mock_client_class, cli_runner, mock_client_for_cli): """Test setup-email command with custom priority.""" mock_client_class.return_value = mock_client_for_cli with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, [ 'setup-email', 'example.com', 'mail.example.com', '--priority', '5' ]) assert result.exit_code == 0 mock_client_for_cli.setup_email.assert_called_once_with( 'example.com', 'mail.example.com', 5, None ) @pytest.mark.unit class TestCLIErrorHandling: """Test CLI error handling.""" @patch('vultr_dns_mcp.cli.VultrDNSClient') def test_api_exception_handling(self, mock_client_class, cli_runner): """Test CLI handling of API exceptions.""" mock_client = AsyncMock() mock_client.domains.side_effect = Exception("Network error") mock_client_class.return_value = mock_client with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'list']) assert result.exit_code == 1 assert "Network error" in result.output def test_missing_arguments(self, cli_runner): """Test CLI behavior with missing arguments.""" with patch.dict('os.environ', {'VULTR_API_KEY': 'test-key'}): result = cli_runner.invoke(cli, ['domains', 'info']) assert result.exit_code == 2 # Click argument error def test_invalid_command(self, cli_runner): """Test CLI behavior with invalid command.""" result = cli_runner.invoke(cli, ['invalid-command']) assert result.exit_code == 2 if __name__ == "__main__": pytest.main([__file__])