diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49f8f4f1..aac1253f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,26 @@ make format make lint ``` +## Testing + +### Running Tests + +```bash +# Run all unit tests +make test + +# Run integration tests +make integration +``` + +### Unit 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 +``` + ## Open a Pull Request 1. Fork the repository diff --git a/cmd/docker-mcp/secret-management/secret/secret_test.go b/cmd/docker-mcp/secret-management/secret/secret_test.go new file mode 100644 index 00000000..24577d8c --- /dev/null +++ b/cmd/docker-mcp/secret-management/secret/secret_test.go @@ -0,0 +1,63 @@ +package secret + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetSecretKey(t *testing.T) { + result := getSecretKey("mykey") + assert.Equal(t, "sm_mykey", result) +} + +func TestParseArg(t *testing.T) { + // Test key=value parsing + secret, err := ParseArg("key=value", SetOpts{Provider: Credstore}) + require.NoError(t, err) + assert.Equal(t, "key", secret.key) + assert.Equal(t, "value", secret.val) + + // Test key-only for non-direct providers + secret, err = ParseArg("keyname", SetOpts{Provider: "oauth/github"}) + require.NoError(t, err) + assert.Equal(t, "keyname", secret.key) + assert.Empty(t, secret.val) + + // Test error on key=value with non-direct provider + _, err = ParseArg("key=value", SetOpts{Provider: "oauth/github"}) + assert.Error(t, err) +} + +func TestIsDirectValueProvider(t *testing.T) { + assert.True(t, isDirectValueProvider("")) + assert.True(t, isDirectValueProvider(Credstore)) + assert.False(t, isDirectValueProvider("oauth/github")) +} + +func TestIsValidProvider(t *testing.T) { + // Valid providers + assert.True(t, IsValidProvider("")) + assert.True(t, IsValidProvider(Credstore)) + assert.True(t, IsValidProvider("oauth/github")) + assert.True(t, IsValidProvider("oauth/google")) + + // Invalid providers + assert.False(t, IsValidProvider("invalid")) + assert.False(t, IsValidProvider("oauth")) +} + +func TestIsErrDecryption(t *testing.T) { + // Test decryption error detection + decryptErr := errors.New("gpg: decryption failed: No secret key") + assert.True(t, isErrDecryption(decryptErr)) + + // Test other errors + otherErr := errors.New("some other error") + assert.False(t, isErrDecryption(otherErr)) + + // Test nil + assert.False(t, isErrDecryption(nil)) +} diff --git a/cmd/docker-mcp/tools/tools_test.go b/cmd/docker-mcp/tools/tools_test.go index 685004c9..6f4fb6d8 100644 --- a/cmd/docker-mcp/tools/tools_test.go +++ b/cmd/docker-mcp/tools/tools_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/docker/docker/api/types/volume" + "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -318,3 +319,57 @@ func withSampleCatalog() option { writeFile(t, filepath.Join(home, ".docker/mcp/catalogs/docker-mcp.yaml"), []byte(catalogContent)) } } + +// Unit tests for call + +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 TestToText(t *testing.T) { + // Test basic functionality - joining multiple text contents + response := &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "First"}, + &mcp.TextContent{Text: "Second"}, + }, + } + result := toText(response) + assert.Equal(t, "First\nSecond", result) +} + +func TestParseArgs(t *testing.T) { + // Test key=value parsing + result := parseArgs([]string{"key1=value1", "key2=value2"}) + expected := map[string]any{"key1": "value1", "key2": "value2"} + assert.Equal(t, expected, result) + + // Test duplicate keys become arrays + result = parseArgs([]string{"tag=red", "tag=blue"}) + expected = map[string]any{"tag": []any{"red", "blue"}} + assert.Equal(t, expected, result) +} + +// Unit tests for list + +func TestToolDescription(t *testing.T) { + // Test that title annotation takes precedence over description + tool := &mcp.Tool{ + Description: "Longer description", + Annotations: &mcp.ToolAnnotations{Title: "Short Title"}, + } + result := toolDescription(tool) + assert.Equal(t, "Short Title", result) +} + +func TestDescriptionSummary(t *testing.T) { + // Test key behavior: stops at first sentence + result := descriptionSummary("First sentence. Second sentence.") + assert.Equal(t, "First sentence.", result) + + // Test key behavior: stops at "Error Responses:" + result = descriptionSummary("Tool description.\nError Responses:\n- 404 if not found") + assert.Equal(t, "Tool description.", result) +}