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: "\nContent
\nParagraph
\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)
}
})
}
}