package builtin import ( "context" "net/http" "net/http/httptest" "slices" "strings" "testing" "github.com/mark3labs/mcp-go/mcp" ) func TestNewHTTPServer(t *testing.T) { server, err := NewHTTPServer(nil) if err != nil { t.Fatalf("Failed to create HTTP server: %v", err) } if server == nil { t.Fatal("Expected server to be non-nil") } } func TestHTTPServerRegistry(t *testing.T) { registry := NewRegistry() // Test that HTTP server is registered servers := registry.ListServers() found := slices.Contains(servers, "http") if !found { t.Error("http server not found in registry") } // Test creating HTTP server through registry wrapper, err := registry.CreateServer("http", map[string]any{}, nil) if err != nil { t.Fatalf("Failed to create HTTP server through registry: %v", err) } if wrapper == nil { t.Fatal("Expected wrapper to be non-nil") } if wrapper.GetServer() == nil { t.Fatal("Expected wrapped server to be non-nil") } } func TestExecuteHTTPFetch(t *testing.T) { // Create a test HTTP server testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/html": w.Header().Set("Content-Type", "text/html") w.Write([]byte(` Test Page

Hello World

This is a test paragraph.

`)) case "/text": w.Header().Set("Content-Type", "text/plain") w.Write([]byte("This is plain text content")) case "/large": // Return content larger than 5MB w.Header().Set("Content-Type", "text/plain") largeContent := strings.Repeat("x", 6*1024*1024) w.Write([]byte(largeContent)) case "/error": w.WriteHeader(http.StatusInternalServerError) default: w.WriteHeader(http.StatusNotFound) } })) defer testServer.Close() tests := []struct { name string params map[string]any expectError bool checkResult func(t *testing.T, result *mcp.CallToolResult) }{ { name: "fetch HTML as HTML", params: map[string]any{ "url": testServer.URL + "/html", "format": "html", }, expectError: false, checkResult: func(t *testing.T, result *mcp.CallToolResult) { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { if !strings.Contains(textContent.Text, "

Hello World

") { t.Error("Expected HTML content to contain h1 tag") } } else { t.Error("Expected text content") } }, }, { name: "fetch HTML as markdown", params: map[string]any{ "url": testServer.URL + "/html", "format": "markdown", }, expectError: false, checkResult: func(t *testing.T, result *mcp.CallToolResult) { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { if !strings.Contains(textContent.Text, "# Hello World") { t.Error("Expected markdown content to contain heading") } } else { t.Error("Expected text content") } }, }, { name: "fetch HTML body only", params: map[string]any{ "url": testServer.URL + "/html", "format": "html", "bodyOnly": true, }, expectError: false, checkResult: func(t *testing.T, result *mcp.CallToolResult) { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { content := textContent.Text if strings.Contains(content, "") || strings.Contains(content, "") { t.Error("Expected body-only content to not contain html or head tags") } if !strings.Contains(content, "

Hello World

") { t.Error("Expected body-only content to contain h1 tag") } } else { t.Error("Expected text content") } }, }, { name: "fetch plain text as markdown", params: map[string]any{ "url": testServer.URL + "/text", "format": "markdown", }, expectError: false, checkResult: func(t *testing.T, result *mcp.CallToolResult) { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { if !strings.Contains(textContent.Text, "```") { t.Error("Expected plain text to be wrapped in code block for markdown") } } else { t.Error("Expected text content") } }, }, { name: "missing URL parameter", params: map[string]any{ "format": "html", }, expectError: true, }, { name: "missing format parameter", params: map[string]any{ "url": testServer.URL + "/html", }, expectError: true, }, { name: "invalid format", params: map[string]any{ "url": testServer.URL + "/html", "format": "invalid", }, expectError: true, }, { name: "server error response", params: map[string]any{ "url": testServer.URL + "/error", "format": "html", }, expectError: true, }, { name: "response too large", params: map[string]any{ "url": testServer.URL + "/large", "format": "html", }, expectError: true, }, { name: "invalid URL", params: map[string]any{ "url": "not-a-valid-url", "format": "html", }, expectError: true, }, { name: "with custom timeout", params: map[string]any{ "url": testServer.URL + "/html", "format": "html", "timeout": 10, }, expectError: false, checkResult: func(t *testing.T, result *mcp.CallToolResult) { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { if !strings.Contains(textContent.Text, "

Hello World

") { t.Error("Expected HTML content with custom timeout") } } else { t.Error("Expected text content") } }, }, } ctx := context.Background() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { request := mcp.CallToolRequest{ Params: mcp.CallToolParams{ Name: "fetch", Arguments: tt.params, }, } result, err := executeHTTPFetch(ctx, request) if err != nil { t.Fatalf("Unexpected error: %v", err) } if tt.expectError { if !result.IsError { t.Error("Expected error result but got success") } } else { if result.IsError { if textContent, ok := mcp.AsTextContent(result.Content[0]); ok { t.Errorf("Expected success but got error: %v", textContent.Text) } else { t.Error("Expected error to have text content") } } else if tt.checkResult != nil { tt.checkResult(t, result) } } }) } } func TestExtractBodyContent(t *testing.T) { tests := []struct { name string html string expected string }{ { name: "extract body content", html: ` Test

Content

Paragraph

`, expected: "\n

Content

\n

Paragraph

\n\n", }, { name: "no body tag", html: `
No body tag
`, expected: `
No body tag
`, }, { name: "empty body", html: ``, expected: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := extractBodyContent(tt.html) if err != nil { t.Fatalf("Unexpected error: %v", err) } if result != tt.expected { t.Errorf("Expected %q, got %q", tt.expected, result) } }) } }