mcp-vultr/tests/test_cli.py
2025-06-11 16:16:34 -06:00

452 lines
18 KiB
Python

"""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__])