From 9f88459c305cbd804af77e2bba39f8e737e2e602 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:56:40 +0200 Subject: [PATCH 1/4] build(deps): bump sigstore/cosign-installer from 3.10.0 to 4.0.0 (#1264) Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 3.10.0 to 4.0.0. - [Release notes](https://github.com/sigstore/cosign-installer/releases) - [Commits](https://github.com/sigstore/cosign-installer/compare/d7543c93d881b35a8faa02e8e3605f69b7a1ce62...faadad0cce49287aee09b3a48701e75088a2c6ad) --- updated-dependencies: - dependency-name: sigstore/cosign-installer dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 6505d8c04..28c7f00a0 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -46,7 +46,7 @@ jobs: # https://github.com/sigstore/cosign-installer - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 #v3.10.0 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad #v4.0.0 with: cosign-release: "v2.2.4" From 9fc2640b41f93d82f70339f426ff38f0cf8a556c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:57:48 +0200 Subject: [PATCH 2/4] build(deps): bump github/codeql-action from 3 to 4 (#1213) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/code-scanning.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index 0cf08cb39..2dcb43003 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -38,7 +38,7 @@ jobs: uses: actions/checkout@v5 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -52,13 +52,13 @@ jobs: threat-models: [ ] - name: Setup proxy for registries id: proxy - uses: github/codeql-action/start-proxy@v3 + uses: github/codeql-action/start-proxy@v4 with: registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }} language: ${{ matrix.language }} - name: Configure - uses: github/codeql-action/resolve-environment@v3 + uses: github/codeql-action/resolve-environment@v4 id: resolve-environment with: language: ${{ matrix.language }} @@ -70,10 +70,10 @@ jobs: cache: false - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 env: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} From 3ddf649d8a8d92751e751b75b05ba3d9c287381b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:01:45 +0200 Subject: [PATCH 3/4] build(deps): bump golang from 1.25.1-alpine to 1.25.3-alpine (#1263) Bumps golang from 1.25.1-alpine to 1.25.3-alpine. --- updated-dependencies: - dependency-name: golang dependency-version: 1.25.3-alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9d865cb21..db467947e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.1-alpine AS build +FROM golang:1.25.3-alpine AS build ARG VERSION="dev" # Set the working directory From c01959536b5a53d19ba0e8ebbaadbb20e253532d Mon Sep 17 00:00:00 2001 From: Tony Truong Date: Tue, 21 Oct 2025 15:57:39 +0200 Subject: [PATCH 4/4] Split up tooling utility (#1273) * split up functionality for reusability * Update pkg/github/tools.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/ghmcp/server.go | 73 +++------ internal/ghmcp/server_test.go | 278 --------------------------------- pkg/github/tools.go | 74 +++++++++ pkg/github/tools_test.go | 282 ++++++++++++++++++++++++++++++++++ 4 files changed, 374 insertions(+), 333 deletions(-) delete mode 100644 internal/ghmcp/server_test.go create mode 100644 pkg/github/tools_test.go diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 5b4c5c158..4b406f9ea 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -106,7 +106,24 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) { }, } - enabledToolsets, invalidToolsets := cleanToolsets(cfg.EnabledToolsets, cfg.DynamicToolsets) + enabledToolsets := cfg.EnabledToolsets + + // If dynamic toolsets are enabled, remove "all" from the enabled toolsets + if cfg.DynamicToolsets { + enabledToolsets = github.RemoveToolset(enabledToolsets, github.ToolsetMetadataAll.ID) + } + + // Clean up the passed toolsets + enabledToolsets, invalidToolsets := github.CleanToolsets(enabledToolsets) + + // If "all" is present, override all other toolsets + if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataAll.ID) { + enabledToolsets = []string{github.ToolsetMetadataAll.ID} + } + // If "default" is present, expand to real toolset IDs + if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataDefault.ID) { + enabledToolsets = github.AddDefaultToolset(enabledToolsets) + } if len(invalidToolsets) > 0 { fmt.Fprintf(os.Stderr, "Invalid toolsets ignored: %s\n", strings.Join(invalidToolsets, ", ")) @@ -465,57 +482,3 @@ func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro req.Header.Set("Authorization", "Bearer "+t.token) return t.transport.RoundTrip(req) } - -// cleanToolsets cleans and handles special toolset keywords: -// - Duplicates are removed from the result -// - Removes whitespaces -// - Validates toolset names and returns invalid ones separately -// - "all": Returns ["all"] immediately, ignoring all other toolsets -// - when dynamicToolsets is true, filters out "all" from the enabled toolsets -// - "default": Replaces with the actual default toolset IDs from GetDefaultToolsetIDs() -// Returns: (validToolsets, invalidToolsets) -func cleanToolsets(enabledToolsets []string, dynamicToolsets bool) ([]string, []string) { - seen := make(map[string]bool) - result := make([]string, 0, len(enabledToolsets)) - invalid := make([]string, 0) - validIDs := github.GetValidToolsetIDs() - - // Add non-default toolsets, removing duplicates and trimming whitespace - for _, toolset := range enabledToolsets { - trimmed := strings.TrimSpace(toolset) - if trimmed == "" { - continue - } - if !seen[trimmed] { - seen[trimmed] = true - if trimmed != github.ToolsetMetadataDefault.ID && trimmed != github.ToolsetMetadataAll.ID { - // Validate the toolset name - if validIDs[trimmed] { - result = append(result, trimmed) - } else { - invalid = append(invalid, trimmed) - } - } - } - } - - hasDefault := seen[github.ToolsetMetadataDefault.ID] - hasAll := seen[github.ToolsetMetadataAll.ID] - - // Handle "all" keyword - return early if not in dynamic mode - if hasAll && !dynamicToolsets { - return []string{github.ToolsetMetadataAll.ID}, invalid - } - - // Expand "default" keyword to actual default toolsets - if hasDefault { - for _, defaultToolset := range github.GetDefaultToolsetIDs() { - if !seen[defaultToolset] { - result = append(result, defaultToolset) - seen[defaultToolset] = true - } - } - } - - return result, invalid -} diff --git a/internal/ghmcp/server_test.go b/internal/ghmcp/server_test.go deleted file mode 100644 index c675306f6..000000000 --- a/internal/ghmcp/server_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package ghmcp - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestCleanToolsets(t *testing.T) { - tests := []struct { - name string - input []string - dynamicToolsets bool - expected []string - expectedInvalid []string - }{ - { - name: "empty slice", - input: []string{}, - dynamicToolsets: false, - expected: []string{}, - }, - { - name: "nil input slice", - input: nil, - dynamicToolsets: false, - expected: []string{}, - }, - // all test cases - { - name: "all only", - input: []string{"all"}, - dynamicToolsets: false, - expected: []string{"all"}, - }, - { - name: "all appears multiple times", - input: []string{"all", "actions", "all"}, - dynamicToolsets: false, - expected: []string{"all"}, - }, - { - name: "all with other toolsets", - input: []string{"all", "actions", "gists"}, - dynamicToolsets: false, - expected: []string{"all"}, - }, - { - name: "all with default", - input: []string{"default", "all", "actions"}, - dynamicToolsets: false, - expected: []string{"all"}, - }, - // default test cases - { - name: "default only", - input: []string{"default"}, - dynamicToolsets: false, - expected: []string{ - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "default with additional toolsets", - input: []string{"default", "actions", "gists"}, - dynamicToolsets: false, - expected: []string{ - "actions", - "gists", - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "no default present", - input: []string{"actions", "gists", "notifications"}, - dynamicToolsets: false, - expected: []string{"actions", "gists", "notifications"}, - }, - { - name: "duplicate toolsets without default", - input: []string{"actions", "gists", "actions"}, - dynamicToolsets: false, - expected: []string{"actions", "gists"}, - }, - { - name: "duplicate toolsets with default", - input: []string{"context", "repos", "issues", "pull_requests", "users", "default"}, - dynamicToolsets: false, - expected: []string{ - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "default appears multiple times with different toolsets in between", - input: []string{"default", "actions", "default", "gists", "default"}, - dynamicToolsets: false, - expected: []string{ - "actions", - "gists", - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - // Dynamic toolsets test cases - { - name: "dynamic toolsets - all only should be filtered", - input: []string{"all"}, - dynamicToolsets: true, - expected: []string{}, - }, - { - name: "dynamic toolsets - all with other toolsets", - input: []string{"all", "actions", "gists"}, - dynamicToolsets: true, - expected: []string{"actions", "gists"}, - }, - { - name: "dynamic toolsets - all with default", - input: []string{"all", "default", "actions"}, - dynamicToolsets: true, - expected: []string{ - "actions", - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "dynamic toolsets - no all present", - input: []string{"actions", "gists"}, - dynamicToolsets: true, - expected: []string{"actions", "gists"}, - }, - { - name: "dynamic toolsets - default only", - input: []string{"default"}, - dynamicToolsets: true, - expected: []string{ - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "only special keywords with dynamic mode", - input: []string{"all", "default"}, - dynamicToolsets: true, - expected: []string{ - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "all with default and overlapping default toolsets in dynamic mode", - input: []string{"all", "default", "issues", "repos"}, - dynamicToolsets: true, - expected: []string{ - "issues", - "repos", - "context", - "pull_requests", - "users", - }, - }, - // Whitespace test cases - { - name: "whitespace check - leading and trailing whitespace on regular toolsets", - input: []string{" actions ", " gists ", "notifications"}, - dynamicToolsets: false, - expected: []string{"actions", "gists", "notifications"}, - }, - { - name: "whitespace check - default toolset", - input: []string{" actions ", " default ", "notifications"}, - dynamicToolsets: false, - expected: []string{ - "actions", - "notifications", - "context", - "repos", - "issues", - "pull_requests", - "users", - }, - }, - { - name: "whitespace check - all toolset", - input: []string{" actions ", " gists ", "notifications", " all "}, - dynamicToolsets: false, - expected: []string{"all"}, - }, - // Invalid toolset test cases - { - name: "mix of valid and invalid toolsets", - input: []string{"actions", "invalid_toolset", "gists", "typo_repo"}, - dynamicToolsets: false, - expected: []string{"actions", "gists"}, - expectedInvalid: []string{"invalid_toolset", "typo_repo"}, - }, - { - name: "invalid with whitespace", - input: []string{" invalid_tool ", " actions ", " typo_gist "}, - dynamicToolsets: false, - expected: []string{"actions"}, - expectedInvalid: []string{"invalid_tool", "typo_gist"}, - }, - { - name: "empty string in toolsets", - input: []string{"", "actions", " ", "gists"}, - dynamicToolsets: false, - expected: []string{"actions", "gists"}, - expectedInvalid: []string{}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, invalid := cleanToolsets(tt.input, tt.dynamicToolsets) - - require.Len(t, result, len(tt.expected), "result length should match expected length") - - if tt.expectedInvalid == nil { - tt.expectedInvalid = []string{} - } - require.Len(t, invalid, len(tt.expectedInvalid), "invalid length should match expected invalid length") - - resultMap := make(map[string]bool) - for _, toolset := range result { - resultMap[toolset] = true - } - - expectedMap := make(map[string]bool) - for _, toolset := range tt.expected { - expectedMap[toolset] = true - } - - invalidMap := make(map[string]bool) - for _, toolset := range invalid { - invalidMap[toolset] = true - } - - expectedInvalidMap := make(map[string]bool) - for _, toolset := range tt.expectedInvalid { - expectedInvalidMap[toolset] = true - } - - assert.Equal(t, expectedMap, resultMap, "result should contain all expected toolsets without duplicates") - assert.Equal(t, expectedInvalidMap, invalidMap, "invalid should contain all expected invalid toolsets") - - assert.Len(t, resultMap, len(result), "result should not contain duplicates") - - assert.False(t, resultMap["default"], "result should not contain 'default'") - }) - } -} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index a0b1690c9..31138258a 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -445,3 +445,77 @@ func GenerateToolsetsHelp() string { return toolsetsHelp } + +// AddDefaultToolset removes the default toolset and expands it to the actual default toolset IDs +func AddDefaultToolset(result []string) []string { + hasDefault := false + seen := make(map[string]bool) + for _, toolset := range result { + seen[toolset] = true + if toolset == ToolsetMetadataDefault.ID { + hasDefault = true + } + } + + // Only expand if "default" keyword was found + if !hasDefault { + return result + } + + result = RemoveToolset(result, ToolsetMetadataDefault.ID) + + for _, defaultToolset := range GetDefaultToolsetIDs() { + if !seen[defaultToolset] { + result = append(result, defaultToolset) + } + } + return result +} + +// cleanToolsets cleans and handles special toolset keywords: +// - Duplicates are removed from the result +// - Removes whitespaces +// - Validates toolset names and returns invalid ones separately - for warning reporting +// Returns: (toolsets, invalidToolsets) +func CleanToolsets(enabledToolsets []string) ([]string, []string) { + seen := make(map[string]bool) + result := make([]string, 0, len(enabledToolsets)) + invalid := make([]string, 0) + validIDs := GetValidToolsetIDs() + + // Add non-default toolsets, removing duplicates and trimming whitespace + for _, toolset := range enabledToolsets { + trimmed := strings.TrimSpace(toolset) + if trimmed == "" { + continue + } + if !seen[trimmed] { + seen[trimmed] = true + result = append(result, trimmed) + if !validIDs[trimmed] { + invalid = append(invalid, trimmed) + } + } + } + + return result, invalid +} + +func RemoveToolset(tools []string, toRemove string) []string { + result := make([]string, 0, len(tools)) + for _, tool := range tools { + if tool != toRemove { + result = append(result, tool) + } + } + return result +} + +func ContainsToolset(tools []string, toCheck string) bool { + for _, tool := range tools { + if tool == toCheck { + return true + } + } + return false +} diff --git a/pkg/github/tools_test.go b/pkg/github/tools_test.go new file mode 100644 index 000000000..45c1e746f --- /dev/null +++ b/pkg/github/tools_test.go @@ -0,0 +1,282 @@ +package github + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCleanToolsets(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + expectedInvalid []string + }{ + { + name: "empty slice", + input: []string{}, + expected: []string{}, + }, + { + name: "nil input slice", + input: nil, + expected: []string{}, + }, + // CleanToolsets only cleans - it does NOT filter out special keywords + { + name: "default keyword preserved", + input: []string{"default"}, + expected: []string{"default"}, + }, + { + name: "default with additional toolsets", + input: []string{"default", "actions", "gists"}, + expected: []string{"default", "actions", "gists"}, + }, + { + name: "all keyword preserved", + input: []string{"all", "actions"}, + expected: []string{"all", "actions"}, + }, + { + name: "no special keywords", + input: []string{"actions", "gists", "notifications"}, + expected: []string{"actions", "gists", "notifications"}, + }, + { + name: "duplicate toolsets without special keywords", + input: []string{"actions", "gists", "actions"}, + expected: []string{"actions", "gists"}, + }, + { + name: "duplicate toolsets with default", + input: []string{"context", "repos", "issues", "pull_requests", "users", "default"}, + expected: []string{"context", "repos", "issues", "pull_requests", "users", "default"}, + }, + { + name: "default appears multiple times - duplicates removed", + input: []string{"default", "actions", "default", "gists", "default"}, + expected: []string{"default", "actions", "gists"}, + }, + // Whitespace test cases + { + name: "whitespace check - leading and trailing whitespace on regular toolsets", + input: []string{" actions ", " gists ", "notifications"}, + expected: []string{"actions", "gists", "notifications"}, + }, + { + name: "whitespace check - default toolset with whitespace", + input: []string{" actions ", " default ", "notifications"}, + expected: []string{"actions", "default", "notifications"}, + }, + { + name: "whitespace check - all toolset with whitespace", + input: []string{" all ", " actions "}, + expected: []string{"all", "actions"}, + }, + // Invalid toolset test cases + { + name: "mix of valid and invalid toolsets", + input: []string{"actions", "invalid_toolset", "gists", "typo_repo"}, + expected: []string{"actions", "invalid_toolset", "gists", "typo_repo"}, + expectedInvalid: []string{"invalid_toolset", "typo_repo"}, + }, + { + name: "invalid with whitespace", + input: []string{" invalid_tool ", " actions ", " typo_gist "}, + expected: []string{"invalid_tool", "actions", "typo_gist"}, + expectedInvalid: []string{"invalid_tool", "typo_gist"}, + }, + { + name: "empty string in toolsets", + input: []string{"", "actions", " ", "gists"}, + expected: []string{"actions", "gists"}, + expectedInvalid: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, invalid := CleanToolsets(tt.input) + + require.Len(t, result, len(tt.expected), "result length should match expected length") + + if tt.expectedInvalid == nil { + tt.expectedInvalid = []string{} + } + require.Len(t, invalid, len(tt.expectedInvalid), "invalid length should match expected invalid length") + + resultMap := make(map[string]bool) + for _, toolset := range result { + resultMap[toolset] = true + } + + expectedMap := make(map[string]bool) + for _, toolset := range tt.expected { + expectedMap[toolset] = true + } + + invalidMap := make(map[string]bool) + for _, toolset := range invalid { + invalidMap[toolset] = true + } + + expectedInvalidMap := make(map[string]bool) + for _, toolset := range tt.expectedInvalid { + expectedInvalidMap[toolset] = true + } + + assert.Equal(t, expectedMap, resultMap, "result should contain all expected toolsets without duplicates") + assert.Equal(t, expectedInvalidMap, invalidMap, "invalid should contain all expected invalid toolsets") + + assert.Len(t, resultMap, len(result), "result should not contain duplicates") + }) + } +} + +func TestAddDefaultToolset(t *testing.T) { + tests := []struct { + name string + input []string + expected []string + }{ + { + name: "no default keyword - return unchanged", + input: []string{"actions", "gists"}, + expected: []string{"actions", "gists"}, + }, + { + name: "default keyword present - expand and remove default", + input: []string{"default"}, + expected: []string{ + "context", + "repos", + "issues", + "pull_requests", + "users", + }, + }, + { + name: "default with additional toolsets", + input: []string{"default", "actions", "gists"}, + expected: []string{ + "actions", + "gists", + "context", + "repos", + "issues", + "pull_requests", + "users", + }, + }, + { + name: "default with overlapping toolsets - should not duplicate", + input: []string{"default", "context", "repos"}, + expected: []string{ + "context", + "repos", + "issues", + "pull_requests", + "users", + }, + }, + { + name: "empty input", + input: []string{}, + expected: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := AddDefaultToolset(tt.input) + + require.Len(t, result, len(tt.expected), "result length should match expected length") + + resultMap := make(map[string]bool) + for _, toolset := range result { + resultMap[toolset] = true + } + + expectedMap := make(map[string]bool) + for _, toolset := range tt.expected { + expectedMap[toolset] = true + } + + assert.Equal(t, expectedMap, resultMap, "result should contain all expected toolsets") + assert.False(t, resultMap["default"], "result should not contain 'default' keyword") + }) + } +} + +func TestRemoveToolset(t *testing.T) { + tests := []struct { + name string + tools []string + toRemove string + expected []string + }{ + { + name: "remove existing toolset", + tools: []string{"actions", "gists", "notifications"}, + toRemove: "gists", + expected: []string{"actions", "notifications"}, + }, + { + name: "remove from empty slice", + tools: []string{}, + toRemove: "actions", + expected: []string{}, + }, + { + name: "remove duplicate entries", + tools: []string{"actions", "gists", "actions", "notifications"}, + toRemove: "actions", + expected: []string{"gists", "notifications"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := RemoveToolset(tt.tools, tt.toRemove) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestContainsToolset(t *testing.T) { + tests := []struct { + name string + tools []string + toCheck string + expected bool + }{ + { + name: "toolset exists", + tools: []string{"actions", "gists", "notifications"}, + toCheck: "gists", + expected: true, + }, + { + name: "toolset does not exist", + tools: []string{"actions", "gists", "notifications"}, + toCheck: "repos", + expected: false, + }, + { + name: "empty slice", + tools: []string{}, + toCheck: "actions", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ContainsToolset(tt.tools, tt.toCheck) + assert.Equal(t, tt.expected, result) + }) + } +}