Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,26 @@ make docker-mcp

# Cross-compile for all platforms
make docker-mcp-cross
```

## Testing

### Running Tests

# Run tests
```bash
# Run all unit tests
make test

# Run integration tests (requires Docker plugin to be installed)
make integration
```

### Test Coverage

```bash
# Generate HTML coverage report for ALL packages in one view
go test -cover -coverprofile=coverage.out ./... -short
go tool cover -html=coverage.out -o coverage.html && open coverage.html
```

## Code Quality
Expand Down
137 changes: 137 additions & 0 deletions cmd/docker-mcp/tools/call_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package tools

import (
"context"
"testing"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCallNoToolName(t *testing.T) {
err := Call(context.Background(), "2", []string{}, false, []string{})
require.Error(t, err)
assert.Equal(t, "no tool name provided", err.Error())
}

func TestParseArgs(t *testing.T) {
tests := []struct {
name string
args []string
expected map[string]any
}{
{
name: "empty args",
args: []string{},
expected: map[string]any{},
},
{
name: "simple key-value pairs",
args: []string{"key1=value1", "key2=value2"},
expected: map[string]any{
"key1": "value1",
"key2": "value2",
},
},
{
name: "key without value",
args: []string{"flag1", "key2=value2"},
expected: map[string]any{
"flag1": nil,
"key2": "value2",
},
},
{
name: "duplicate keys create array",
args: []string{"tag=red", "tag=blue", "tag=green"},
expected: map[string]any{
"tag": []any{"red", "blue", "green"},
},
},
{
name: "values with equals signs",
args: []string{"url=https://example.com/path?param=value"},
expected: map[string]any{
"url": "https://example.com/path?param=value",
},
},
{
name: "empty values",
args: []string{"empty=", "key=value"},
expected: map[string]any{
"empty": "",
"key": "value",
},
},
{
name: "mixed duplicate and single keys",
args: []string{"name=john", "tag=red", "tag=blue", "age=30"},
expected: map[string]any{
"name": "john",
"tag": []any{"red", "blue"},
"age": "30",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := parseArgs(tt.args)
assert.Equal(t, tt.expected, result)
})
}
}

func TestToText(t *testing.T) {
tests := []struct {
name string
response *mcp.CallToolResult
expected string
}{
{
name: "single text content",
response: &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Hello world"},
},
},
expected: "Hello world",
},
{
name: "multiple text contents",
response: &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "First line"},
&mcp.TextContent{Text: "Second line"},
},
},
expected: "First line\nSecond line",
},
{
name: "empty content",
response: &mcp.CallToolResult{
Content: []mcp.Content{},
},
expected: "",
},
{
name: "nil content",
response: &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Before nil"},
nil,
&mcp.TextContent{Text: "After nil"},
},
},
expected: "Before nil\n<nil>\nAfter nil",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := toText(tt.response)
assert.Equal(t, tt.expected, result)
})
}
}
File renamed without changes.
79 changes: 79 additions & 0 deletions cmd/docker-mcp/tools/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package tools

import (
"testing"

"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
)

func TestToolDescription(t *testing.T) {
tests := []struct {
name string
tool *mcp.Tool
expected string
}{
{
name: "uses title from annotations when available",
tool: &mcp.Tool{
Name: "test-tool",
Description: "Longer description",
Annotations: &mcp.ToolAnnotations{
Title: "Short Title",
},
},
expected: "Short Title",
},
{
name: "falls back to description when no title",
tool: &mcp.Tool{
Name: "test-tool",
Description: "Simple description.",
},
expected: "Simple description.",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := toolDescription(tt.tool)
assert.Equal(t, tt.expected, result)
})
}
}

func TestDescriptionSummary(t *testing.T) {
tests := []struct {
name string
description string
expected string
}{
{
name: "single sentence",
description: "This is a simple description.",
expected: "This is a simple description.",
},
{
name: "multiple sentences - takes first",
description: "First sentence. Second sentence.",
expected: "First sentence.",
},
{
name: "empty description",
description: "",
expected: "",
},
{
name: "stops at Error Responses",
description: "Tool description.\nError Responses:\n- 404 if not found",
expected: "Tool description.",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := descriptionSummary(tt.description)
assert.Equal(t, tt.expected, result)
})
}
}
Loading