
- Production-ready MCP server for Name Cheap API integration - Domain management (registration, renewal, availability checking) - DNS management (records, nameserver configuration) - SSL certificate management and monitoring - Account information and balance checking - Smart identifier resolution for improved UX - Comprehensive error handling with specific exception types - 80%+ test coverage with unit, integration, and MCP tests - CLI and MCP server interfaces - FastMCP 2.10.5+ implementation with full MCP spec compliance 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
250 lines
11 KiB
Python
250 lines
11 KiB
Python
"""Test high-level Name Cheap client."""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, patch, AsyncMock
|
|
|
|
from mcp_namecheap.client import NameCheapClient, is_uuid_format
|
|
from mcp_namecheap.server import NameCheapNotFoundError, NameCheapValidationError
|
|
|
|
|
|
class TestNameCheapClient:
|
|
"""Test Name Cheap client functionality."""
|
|
|
|
@pytest.mark.unit
|
|
def test_is_uuid_format(self):
|
|
"""Test UUID format detection (for Name Cheap, numeric IDs)."""
|
|
assert is_uuid_format("12345") is True
|
|
assert is_uuid_format("0") is True
|
|
assert is_uuid_format("example.com") is False
|
|
assert is_uuid_format("") is False
|
|
assert is_uuid_format("abc123") is False
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_domain_id_exact_match(self, mock_client, sample_domains_response):
|
|
"""Test domain ID resolution with exact match."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
# Should return the domain name itself for exact match
|
|
domain_id = await mock_client.get_domain_id("example.com")
|
|
assert domain_id == "example.com"
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_domain_id_case_insensitive(self, mock_client, sample_domains_response):
|
|
"""Test domain ID resolution is case insensitive."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
domain_id = await mock_client.get_domain_id("EXAMPLE.COM")
|
|
assert domain_id == "example.com"
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_domain_id_not_found(self, mock_client, sample_domains_response):
|
|
"""Test domain ID resolution when domain not found."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
with pytest.raises(NameCheapNotFoundError):
|
|
await mock_client.get_domain_id("nonexistent.com")
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_ssl_id_by_id(self, mock_client, sample_ssl_certificates_response):
|
|
"""Test SSL ID resolution by certificate ID."""
|
|
mock_client.server.list_ssl_certificates.return_value = sample_ssl_certificates_response["SSLListResult"]["SSL"]
|
|
|
|
# Numeric ID should be returned as-is
|
|
ssl_id = await mock_client.get_ssl_id("111111")
|
|
assert ssl_id == "111111"
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_ssl_id_by_hostname(self, mock_client, sample_ssl_certificates_response):
|
|
"""Test SSL ID resolution by hostname."""
|
|
mock_client.server.list_ssl_certificates.return_value = sample_ssl_certificates_response["SSLListResult"]["SSL"]
|
|
|
|
ssl_id = await mock_client.get_ssl_id("example.com")
|
|
assert ssl_id == "111111"
|
|
|
|
@pytest.mark.unit
|
|
async def test_get_ssl_id_not_found(self, mock_client, sample_ssl_certificates_response):
|
|
"""Test SSL ID resolution when certificate not found."""
|
|
mock_client.server.list_ssl_certificates.return_value = sample_ssl_certificates_response["SSLListResult"]["SSL"]
|
|
|
|
with pytest.raises(NameCheapNotFoundError):
|
|
await mock_client.get_ssl_id("nonexistent.com")
|
|
|
|
@pytest.mark.unit
|
|
async def test_list_domains_enhanced(self, mock_client, sample_domains_response):
|
|
"""Test enhanced domain listing."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
domains = await mock_client.list_domains()
|
|
|
|
assert len(domains) == 2
|
|
assert domains[0]["_display_name"] == "example.com"
|
|
assert domains[0]["_is_expired"] is False
|
|
assert domains[1]["_is_locked"] is True
|
|
|
|
@pytest.mark.unit
|
|
async def test_check_domain_availability_validation(self, mock_client):
|
|
"""Test domain availability check with validation."""
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.check_domain_availability(["invalid..domain"])
|
|
|
|
@pytest.mark.unit
|
|
async def test_register_domain_validation(self, mock_client):
|
|
"""Test domain registration validation."""
|
|
contacts = {
|
|
"FirstName": "Test",
|
|
"LastName": "User",
|
|
"Address1": "123 Main St",
|
|
"City": "Test City",
|
|
"StateProvince": "CA",
|
|
"PostalCode": "12345",
|
|
"Country": "US",
|
|
"Phone": "+1.5551234567",
|
|
"EmailAddress": "test@example.com"
|
|
}
|
|
|
|
# Invalid domain format
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.register_domain("invalid..domain", 1, contacts)
|
|
|
|
# Invalid years
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.register_domain("example.com", 15, contacts)
|
|
|
|
@pytest.mark.unit
|
|
async def test_set_dns_records_validation(self, mock_client, sample_domains_response):
|
|
"""Test DNS records validation."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
# Invalid record type
|
|
records = [{
|
|
"hostname": "@",
|
|
"record_type": "INVALID",
|
|
"address": "192.168.1.1",
|
|
"ttl": 1800
|
|
}]
|
|
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.set_dns_records("example.com", records)
|
|
|
|
@pytest.mark.unit
|
|
async def test_set_custom_nameservers_validation(self, mock_client, sample_domains_response):
|
|
"""Test custom nameservers validation."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
# Too few nameservers
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.set_custom_nameservers("example.com", ["ns1.example.com"])
|
|
|
|
# Too many nameservers
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.set_custom_nameservers("example.com", [f"ns{i}.example.com" for i in range(1, 15)])
|
|
|
|
# Invalid nameserver format
|
|
with pytest.raises(NameCheapValidationError):
|
|
await mock_client.set_custom_nameservers("example.com", ["ns1.example.com", "invalid..nameserver"])
|
|
|
|
@pytest.mark.unit
|
|
def test_is_valid_domain_format(self, mock_client):
|
|
"""Test domain format validation."""
|
|
assert mock_client._is_valid_domain_format("example.com") is True
|
|
assert mock_client._is_valid_domain_format("sub.example.com") is True
|
|
assert mock_client._is_valid_domain_format("test-domain.co.uk") is True
|
|
assert mock_client._is_valid_domain_format("invalid..domain") is False
|
|
assert mock_client._is_valid_domain_format("") is False
|
|
assert mock_client._is_valid_domain_format("a" * 254) is False # Too long
|
|
|
|
@pytest.mark.unit
|
|
async def test_cache_clearing(self, mock_client, sample_domains_response):
|
|
"""Test cache clearing after operations."""
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
mock_client.server.register_domain.return_value = {"DomainID": "12347"}
|
|
|
|
# Populate cache
|
|
await mock_client.list_domains()
|
|
assert mock_client._domain_cache is not None
|
|
|
|
# Register domain should clear cache
|
|
contacts = {
|
|
"FirstName": "Test",
|
|
"LastName": "User",
|
|
"Address1": "123 Main St",
|
|
"City": "Test City",
|
|
"StateProvince": "CA",
|
|
"PostalCode": "12345",
|
|
"Country": "US",
|
|
"Phone": "+1.5551234567",
|
|
"EmailAddress": "test@example.com"
|
|
}
|
|
|
|
await mock_client.register_domain("new-domain.com", 1, contacts)
|
|
assert mock_client._domain_cache is None
|
|
|
|
@pytest.mark.unit
|
|
async def test_list_ssl_certificates_enhanced(self, mock_client, sample_ssl_certificates_response):
|
|
"""Test enhanced SSL certificate listing."""
|
|
mock_client.server.list_ssl_certificates.return_value = sample_ssl_certificates_response["SSLListResult"]["SSL"]
|
|
|
|
certificates = await mock_client.list_ssl_certificates()
|
|
|
|
assert len(certificates) == 1
|
|
assert certificates[0]["_display_name"] == "example.com"
|
|
assert certificates[0]["_is_expired"] is False
|
|
|
|
|
|
class TestClientIntegration:
|
|
"""Integration tests for client operations."""
|
|
|
|
@pytest.mark.integration
|
|
async def test_domain_workflow(self, mock_client, sample_domains_response, sample_domain_check_response):
|
|
"""Test complete domain workflow."""
|
|
# Setup mocks
|
|
mock_client.server.check_domain_availability.return_value = sample_domain_check_response["DomainCheckResult"]
|
|
mock_client.server.register_domain.return_value = {"DomainID": "12347"}
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
|
|
# Check availability
|
|
results = await mock_client.check_domain_availability(["available-domain.com"])
|
|
assert results[0]["@Available"] == "true"
|
|
|
|
# Register domain
|
|
contacts = {
|
|
"FirstName": "Test",
|
|
"LastName": "User",
|
|
"Address1": "123 Main St",
|
|
"City": "Test City",
|
|
"StateProvince": "CA",
|
|
"PostalCode": "12345",
|
|
"Country": "US",
|
|
"Phone": "+1.5551234567",
|
|
"EmailAddress": "test@example.com"
|
|
}
|
|
|
|
result = await mock_client.register_domain("available-domain.com", 1, contacts)
|
|
assert "DomainID" in result
|
|
|
|
# List domains
|
|
domains = await mock_client.list_domains()
|
|
assert len(domains) == 2
|
|
|
|
@pytest.mark.integration
|
|
async def test_dns_workflow(self, mock_client, sample_domains_response, sample_dns_records_response):
|
|
"""Test complete DNS workflow."""
|
|
# Setup mocks
|
|
mock_client.server.list_domains.return_value = sample_domains_response["DomainGetListResult"]["Domain"]
|
|
mock_client.server.get_dns_records.return_value = sample_dns_records_response["DomainDNSGetHostsResult"]["host"]
|
|
mock_client.server.set_dns_records.return_value = {"Status": "OK"}
|
|
|
|
# Get DNS records
|
|
records = await mock_client.get_dns_records("example.com")
|
|
assert len(records) == 2
|
|
|
|
# Set DNS records
|
|
new_records = [{
|
|
"hostname": "@",
|
|
"record_type": "A",
|
|
"address": "192.168.1.100",
|
|
"ttl": 3600
|
|
}]
|
|
|
|
result = await mock_client.set_dns_records("example.com", new_records)
|
|
assert result["Status"] == "OK" |