mcp-name-cheap/tests/test_client.py
Claude Code f5e63c888d Initial commit: MCP Name Cheap server implementation
- 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>
2025-07-20 03:43:11 -06:00

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"