Skip to content

Commit d9d35b9

Browse files
authored
test(toolsets): toolset specific metadata tests (#326)
- Refactor tests to use testify (more clarity+composability for complex tests) - Tests for default toolsets - Tests for configured, granular toolsets Signed-off-by: Marc Nuri <[email protected]>
1 parent 48cf204 commit d9d35b9

File tree

10 files changed

+788
-108
lines changed

10 files changed

+788
-108
lines changed

internal/test/kubernetes.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package test
2+
3+
import (
4+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
5+
)
6+
7+
func KubeConfigFake() *clientcmdapi.Config {
8+
fakeConfig := clientcmdapi.NewConfig()
9+
fakeConfig.Clusters["fake"] = clientcmdapi.NewCluster()
10+
fakeConfig.Clusters["fake"].Server = "https://127.0.0.1:6443"
11+
fakeConfig.Clusters["additional-cluster"] = clientcmdapi.NewCluster()
12+
fakeConfig.AuthInfos["fake"] = clientcmdapi.NewAuthInfo()
13+
fakeConfig.AuthInfos["additional-auth"] = clientcmdapi.NewAuthInfo()
14+
fakeConfig.Contexts["fake-context"] = clientcmdapi.NewContext()
15+
fakeConfig.Contexts["fake-context"].Cluster = "fake"
16+
fakeConfig.Contexts["fake-context"].AuthInfo = "fake"
17+
fakeConfig.Contexts["additional-context"] = clientcmdapi.NewContext()
18+
fakeConfig.Contexts["additional-context"].Cluster = "additional-cluster"
19+
fakeConfig.Contexts["additional-context"].AuthInfo = "additional-auth"
20+
fakeConfig.CurrentContext = "fake-context"
21+
return fakeConfig
22+
}

internal/test/mcp.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package test
2+
3+
import (
4+
"net/http/httptest"
5+
"testing"
6+
7+
"github.com/mark3labs/mcp-go/client"
8+
"github.com/mark3labs/mcp-go/mcp"
9+
"github.com/mark3labs/mcp-go/server"
10+
"github.com/stretchr/testify/require"
11+
"golang.org/x/net/context"
12+
)
13+
14+
type McpClient struct {
15+
ctx context.Context
16+
testServer *httptest.Server
17+
*client.Client
18+
}
19+
20+
func NewMcpClient(t *testing.T, mcpHttpServer *server.StreamableHTTPServer) *McpClient {
21+
require.NotNil(t, mcpHttpServer, "McpHttpServer must be provided")
22+
var err error
23+
ret := &McpClient{ctx: t.Context()}
24+
ret.testServer = httptest.NewServer(mcpHttpServer)
25+
ret.Client, err = client.NewStreamableHttpClient(ret.testServer.URL + "/mcp")
26+
require.NoError(t, err, "Expected no error creating MCP client")
27+
err = ret.Start(t.Context())
28+
require.NoError(t, err, "Expected no error starting MCP client")
29+
initRequest := mcp.InitializeRequest{}
30+
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
31+
initRequest.Params.ClientInfo = mcp.Implementation{Name: "test", Version: "1.33.7"}
32+
_, err = ret.Initialize(t.Context(), initRequest)
33+
require.NoError(t, err, "Expected no error initializing MCP client")
34+
return ret
35+
}
36+
37+
func (m *McpClient) Close() {
38+
if m.Client != nil {
39+
_ = m.Client.Close()
40+
}
41+
if m.testServer != nil {
42+
m.testServer.Close()
43+
}
44+
}
45+
46+
// CallTool helper function to call a tool by name with arguments
47+
func (m *McpClient) CallTool(name string, args map[string]interface{}) (*mcp.CallToolResult, error) {
48+
callToolRequest := mcp.CallToolRequest{}
49+
callToolRequest.Params.Name = name
50+
callToolRequest.Params.Arguments = args
51+
return m.Client.CallTool(m.ctx, callToolRequest)
52+
}

internal/test/mock_server.go

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ import (
66
"io"
77
"net/http"
88
"net/http/httptest"
9+
"path/filepath"
10+
"testing"
911

12+
"github.com/stretchr/testify/require"
1013
v1 "k8s.io/api/core/v1"
1114
apierrors "k8s.io/apimachinery/pkg/api/errors"
1215
"k8s.io/apimachinery/pkg/runtime"
1316
"k8s.io/apimachinery/pkg/runtime/serializer"
1417
"k8s.io/apimachinery/pkg/util/httpstream"
1518
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
1619
"k8s.io/client-go/rest"
20+
"k8s.io/client-go/tools/clientcmd"
1721
"k8s.io/client-go/tools/clientcmd/api"
1822
)
1923

@@ -46,7 +50,9 @@ func NewMockServer() *MockServer {
4650
}
4751

4852
func (m *MockServer) Close() {
49-
m.server.Close()
53+
if m.server != nil {
54+
m.server.Close()
55+
}
5056
}
5157

5258
func (m *MockServer) Handle(handler http.Handler) {
@@ -57,21 +63,22 @@ func (m *MockServer) Config() *rest.Config {
5763
return m.config
5864
}
5965

60-
func (m *MockServer) KubeConfig() *api.Config {
61-
fakeConfig := api.NewConfig()
62-
fakeConfig.Clusters["fake"] = api.NewCluster()
66+
func (m *MockServer) Kubeconfig() *api.Config {
67+
fakeConfig := KubeConfigFake()
6368
fakeConfig.Clusters["fake"].Server = m.config.Host
6469
fakeConfig.Clusters["fake"].CertificateAuthorityData = m.config.CAData
65-
fakeConfig.AuthInfos["fake"] = api.NewAuthInfo()
6670
fakeConfig.AuthInfos["fake"].ClientKeyData = m.config.KeyData
6771
fakeConfig.AuthInfos["fake"].ClientCertificateData = m.config.CertData
68-
fakeConfig.Contexts["fake-context"] = api.NewContext()
69-
fakeConfig.Contexts["fake-context"].Cluster = "fake"
70-
fakeConfig.Contexts["fake-context"].AuthInfo = "fake"
71-
fakeConfig.CurrentContext = "fake-context"
7272
return fakeConfig
7373
}
7474

75+
func (m *MockServer) KubeconfigFile(t *testing.T) string {
76+
kubeconfig := filepath.Join(t.TempDir(), "config")
77+
err := clientcmd.WriteToFile(*m.Kubeconfig(), kubeconfig)
78+
require.NoError(t, err, "Expected no error writing kubeconfig file")
79+
return kubeconfig
80+
}
81+
7582
func WriteObject(w http.ResponseWriter, obj runtime.Object) {
7683
w.Header().Set("Content-Type", runtime.ContentTypeJSON)
7784
if err := json.NewEncoder(w).Encode(obj); err != nil {
@@ -170,3 +177,38 @@ WaitForStreams:
170177

171178
return ctx, nil
172179
}
180+
181+
type InOpenShiftHandler struct {
182+
}
183+
184+
var _ http.Handler = (*InOpenShiftHandler)(nil)
185+
186+
func (h *InOpenShiftHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
187+
w.Header().Set("Content-Type", "application/json")
188+
// Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)
189+
if req.URL.Path == "/api" {
190+
_, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`))
191+
return
192+
}
193+
// Request Performed by DiscoveryClient to Kube API (Get API Groups)
194+
if req.URL.Path == "/apis" {
195+
_, _ = w.Write([]byte(`{
196+
"kind":"APIGroupList",
197+
"groups":[{
198+
"name":"project.openshift.io",
199+
"versions":[{"groupVersion":"project.openshift.io/v1","version":"v1"}],
200+
"preferredVersion":{"groupVersion":"project.openshift.io/v1","version":"v1"}
201+
}]}`))
202+
return
203+
}
204+
if req.URL.Path == "/apis/project.openshift.io/v1" {
205+
_, _ = w.Write([]byte(`{
206+
"kind":"APIResourceList",
207+
"apiVersion":"v1",
208+
"groupVersion":"project.openshift.io/v1",
209+
"resources":[
210+
{"name":"projects","singularName":"","namespaced":false,"kind":"Project","verbs":["create","delete","get","list","patch","update","watch"],"shortNames":["pr"]}
211+
]}`))
212+
return
213+
}
214+
}

pkg/http/http_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"net/http"
1414
"net/http/httptest"
1515
"os"
16-
"path/filepath"
1716
"regexp"
1817
"strconv"
1918
"strings"
@@ -24,7 +23,6 @@ import (
2423
"github.com/coreos/go-oidc/v3/oidc"
2524
"github.com/coreos/go-oidc/v3/oidc/oidctest"
2625
"golang.org/x/sync/errgroup"
27-
"k8s.io/client-go/tools/clientcmd"
2826
"k8s.io/klog/v2"
2927
"k8s.io/klog/v2/textlogger"
3028

@@ -66,10 +64,7 @@ func (c *httpContext) beforeEach(t *testing.T) {
6664
}
6765
c.mockServer = test.NewMockServer()
6866
// Fake Kubernetes configuration
69-
mockKubeConfig := c.mockServer.KubeConfig()
70-
kubeConfig := filepath.Join(t.TempDir(), "config")
71-
_ = clientcmd.WriteToFile(*mockKubeConfig, kubeConfig)
72-
c.StaticConfig.KubeConfig = kubeConfig
67+
c.StaticConfig.KubeConfig = c.mockServer.KubeconfigFile(t)
7368
// Capture logging
7469
c.klogState = klog.CaptureState()
7570
flags := flag.NewFlagSet("test", flag.ContinueOnError)

pkg/mcp/common_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"sigs.k8s.io/controller-runtime/tools/setup-envtest/versions"
4343
"sigs.k8s.io/controller-runtime/tools/setup-envtest/workflows"
4444

45+
"github.com/containers/kubernetes-mcp-server/internal/test"
4546
"github.com/containers/kubernetes-mcp-server/pkg/config"
4647
"github.com/containers/kubernetes-mcp-server/pkg/output"
4748
)
@@ -82,11 +83,9 @@ func TestMain(m *testing.M) {
8283
BinaryAssetsDirectory: filepath.Join(envTestDir, "k8s", versionDir),
8384
}
8485
adminSystemMasterBaseConfig, _ := envTest.Start()
85-
au, err := envTest.AddUser(envTestUser, adminSystemMasterBaseConfig)
86-
if err != nil {
87-
panic(err)
88-
}
86+
au := test.Must(envTest.AddUser(envTestUser, adminSystemMasterBaseConfig))
8987
envTestRestConfig = au.Config()
88+
envTest.KubeConfig = test.Must(au.KubeConfig())
9089

9190
//Create test data as administrator
9291
ctx := context.Background()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[
2+
{
3+
"annotations": {
4+
"title": "Configuration: View",
5+
"readOnlyHint": true,
6+
"destructiveHint": false,
7+
"idempotentHint": false,
8+
"openWorldHint": true
9+
},
10+
"description": "Get the current Kubernetes configuration content as a kubeconfig YAML",
11+
"inputSchema": {
12+
"type": "object",
13+
"properties": {
14+
"minified": {
15+
"description": "Return a minified version of the configuration. If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. (Optional, default true)",
16+
"type": "boolean"
17+
}
18+
}
19+
},
20+
"name": "configuration_view"
21+
}
22+
]

0 commit comments

Comments
 (0)