Skip to main content
Glama
patrickdappollonio

Domain Tools (WHOIS + DNS)

resolver_test.go11.7 kB
package resolver import ( "context" "encoding/json" "net" "testing" "time" "github.com/mark3labs/mcp-go/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestHandleHostnameResolution_ParameterValidation(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} t.Run("missing hostname parameter", func(t *testing.T) { request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "ip_version": "ipv4", }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), `parameter "hostname" is required`) }) t.Run("empty hostname parameter", func(t *testing.T) { request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "", "ip_version": "ipv4", }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), `parameter "hostname" is required`) }) t.Run("invalid json arguments", func(t *testing.T) { // This would simulate malformed JSON arguments request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": 12345, // Wrong type }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "failed to parse tool input") }) } func TestHandleHostnameResolution_DefaultIPVersion(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "localhost", // ip_version is omitted - should default to ipv4 }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "localhost", responseData["hostname"]) assert.Equal(t, "ipv4", responseData["ip_version"]) assert.Equal(t, false, responseData["failed"]) } func TestHandleHostnameResolution_SuccessfulIPv4Resolution(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "localhost", "ip_version": "ipv4", }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "localhost", responseData["hostname"]) assert.Equal(t, "ipv4", responseData["ip_version"]) assert.Equal(t, false, responseData["failed"]) assert.Contains(t, responseData, "ipv4_addresses") assert.NotContains(t, responseData, "error") // Check that we got IPv4 addresses addresses, ok := responseData["ipv4_addresses"].([]interface{}) assert.True(t, ok) assert.NotEmpty(t, addresses) } func TestHandleHostnameResolution_SuccessfulIPv6Resolution(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "localhost", "ip_version": "ipv6", }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "localhost", responseData["hostname"]) assert.Equal(t, "ipv6", responseData["ip_version"]) assert.Equal(t, false, responseData["failed"]) // IPv6 might not always be available, so we check if it's there or if there's an error if ipv6Addresses, exists := responseData["ipv6_addresses"]; exists { addresses, ok := ipv6Addresses.([]interface{}) assert.True(t, ok) assert.NotEmpty(t, addresses) assert.NotContains(t, responseData, "error") } else { // If IPv6 is not available, there should be an error assert.Contains(t, responseData, "error") assert.Equal(t, true, responseData["failed"]) } } func TestHandleHostnameResolution_BothVersions(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "localhost", "ip_version": "both", }, }, } result, err := HandleHostnameResolution(ctx, request, config) require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "localhost", responseData["hostname"]) assert.Equal(t, "both", responseData["ip_version"]) // Should have IPv4 addresses (localhost should always resolve to IPv4) assert.Contains(t, responseData, "ipv4_addresses") ipv4Addresses, ok := responseData["ipv4_addresses"].([]interface{}) assert.True(t, ok) assert.NotEmpty(t, ipv4Addresses) } func TestHandleHostnameResolution_NonExistentDomain(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "this-domain-definitely-does-not-exist-12345.invalid", "ip_version": "ipv4", }, }, } result, err := HandleHostnameResolution(ctx, request, config) // Should NOT return an error - should return JSON with failed: true require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "this-domain-definitely-does-not-exist-12345.invalid", responseData["hostname"]) assert.Equal(t, "ipv4", responseData["ip_version"]) assert.Equal(t, true, responseData["failed"]) assert.Contains(t, responseData, "error") assert.NotContains(t, responseData, "ipv4_addresses") errorMsg, ok := responseData["error"].(string) assert.True(t, ok) assert.NotEmpty(t, errorMsg) } func TestHandleHostnameResolution_NonExistentDomainBothVersions(t *testing.T) { ctx := context.Background() config := &Config{Timeout: 5 * time.Second} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "this-domain-definitely-does-not-exist-67890.invalid", "ip_version": "both", }, }, } result, err := HandleHostnameResolution(ctx, request, config) // Should NOT return an error - should return JSON with failed: true require.NoError(t, err) require.NotNil(t, result) // Parse the JSON response require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) assert.Equal(t, "this-domain-definitely-does-not-exist-67890.invalid", responseData["hostname"]) assert.Equal(t, "both", responseData["ip_version"]) assert.Equal(t, true, responseData["failed"]) assert.Contains(t, responseData, "error") assert.NotContains(t, responseData, "ipv4_addresses") assert.NotContains(t, responseData, "ipv6_addresses") } func TestHandleHostnameResolution_ContextTimeout(t *testing.T) { // Create a context that times out very quickly ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) defer cancel() config := &Config{Timeout: 1 * time.Nanosecond} request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Arguments: map[string]interface{}{ "hostname": "google.com", "ip_version": "ipv4", }, }, } // This should likely timeout, but the exact behavior might vary by system result, err := HandleHostnameResolution(ctx, request, config) if err != nil { // If it returns an error, it should be a timeout/context error (critical error) assert.Contains(t, err.Error(), "failed to resolve ipv4 addresses") } else { // If it returns a result, it might have succeeded very quickly or failed with host not found require.NotNil(t, result) require.Len(t, result.Content, 1) textContent, ok := mcp.AsTextContent(result.Content[0]) require.True(t, ok) var responseData map[string]interface{} err = json.Unmarshal([]byte(textContent.Text), &responseData) require.NoError(t, err) // Either succeeded or failed with host not found assert.Contains(t, responseData, "failed") } } func TestIsHostNotFoundError(t *testing.T) { t.Run("DNSError with IsNotFound true", func(t *testing.T) { dnsErr := &net.DNSError{ Err: "no such host", Name: "example.invalid", Server: "8.8.8.8", IsNotFound: true, IsTimeout: false, IsTemporary: false, } assert.True(t, isHostNotFoundError(dnsErr)) }) t.Run("DNSError with IsNotFound false", func(t *testing.T) { dnsErr := &net.DNSError{ Err: "server misbehaving", Name: "example.com", Server: "8.8.8.8", IsNotFound: false, IsTimeout: false, IsTemporary: true, } assert.False(t, isHostNotFoundError(dnsErr)) }) t.Run("non-DNS error", func(t *testing.T) { err := context.DeadlineExceeded assert.False(t, isHostNotFoundError(err)) }) t.Run("nil error", func(t *testing.T) { assert.False(t, isHostNotFoundError(nil)) }) } func TestLookupIPAddresses(t *testing.T) { ctx := context.Background() t.Run("successful IPv4 lookup", func(t *testing.T) { addresses, err := lookupIPAddresses(ctx, "localhost", "ipv4") require.NoError(t, err) assert.NotEmpty(t, addresses) // Check that all addresses are valid IPv4 for _, addr := range addresses { ip := net.ParseIP(addr) assert.NotNil(t, ip) assert.NotNil(t, ip.To4()) // Should be IPv4 } }) t.Run("successful IPv6 lookup", func(t *testing.T) { addresses, err := lookupIPAddresses(ctx, "localhost", "ipv6") if err != nil { // IPv6 might not be available on all systems t.Skipf("IPv6 lookup failed (might not be available): %v", err) } assert.NotEmpty(t, addresses) // Check that all addresses are valid IPv6 for _, addr := range addresses { ip := net.ParseIP(addr) assert.NotNil(t, ip) assert.Nil(t, ip.To4()) // Should NOT be IPv4 (i.e., should be IPv6) } }) t.Run("non-existent host", func(t *testing.T) { addresses, err := lookupIPAddresses(ctx, "this-does-not-exist-98765.invalid", "ipv4") require.Error(t, err) assert.Nil(t, addresses) assert.True(t, isHostNotFoundError(err)) }) }

Latest Blog Posts

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/patrickdappollonio/mcp-domaintools'

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