diff --git a/pkg/mcp/mock_server_test.go b/internal/test/mock_server.go similarity index 82% rename from pkg/mcp/mock_server_test.go rename to internal/test/mock_server.go index 124e5ab5..44269915 100644 --- a/pkg/mcp/mock_server_test.go +++ b/internal/test/mock_server.go @@ -1,9 +1,12 @@ -package mcp +package test import ( "encoding/json" "errors" "io" + "net/http" + "net/http/httptest" + v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -11,8 +14,6 @@ import ( "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/httpstream/spdy" "k8s.io/client-go/rest" - "net/http" - "net/http/httptest" ) type MockServer struct { @@ -51,7 +52,11 @@ func (m *MockServer) Handle(handler http.Handler) { m.restHandlers = append(m.restHandlers, handler.ServeHTTP) } -func writeObject(w http.ResponseWriter, obj runtime.Object) { +func (m *MockServer) Config() *rest.Config { + return m.config +} + +func WriteObject(w http.ResponseWriter, obj runtime.Object) { w.Header().Set("Content-Type", runtime.ContentTypeJSON) if err := json.NewEncoder(w).Encode(obj); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -63,11 +68,11 @@ type streamAndReply struct { replySent <-chan struct{} } -type streamContext struct { - conn io.Closer - stdinStream io.ReadCloser - stdoutStream io.WriteCloser - stderrStream io.WriteCloser +type StreamContext struct { + Closer io.Closer + StdinStream io.ReadCloser + StdoutStream io.WriteCloser + StderrStream io.WriteCloser writeStatus func(status *apierrors.StatusError) error } @@ -87,7 +92,7 @@ func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) err return err } } -func createHTTPStreams(w http.ResponseWriter, req *http.Request, opts *StreamOptions) (*streamContext, error) { +func CreateHTTPStreams(w http.ResponseWriter, req *http.Request, opts *StreamOptions) (*StreamContext, error) { _, err := httpstream.Handshake(req, w, []string{"v4.channel.k8s.io"}) if err != nil { return nil, err @@ -95,12 +100,12 @@ func createHTTPStreams(w http.ResponseWriter, req *http.Request, opts *StreamOpt upgrader := spdy.NewResponseUpgrader() streamCh := make(chan streamAndReply) - conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { + connection := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { streamCh <- streamAndReply{Stream: stream, replySent: replySent} return nil }) - ctx := &streamContext{ - conn: conn, + ctx := &StreamContext{ + Closer: connection, } // wait for stream @@ -128,13 +133,13 @@ WaitForStreams: ctx.writeStatus = v4WriteStatusFunc(stream) case v1.StreamTypeStdout: replyChan <- struct{}{} - ctx.stdoutStream = stream + ctx.StdoutStream = stream case v1.StreamTypeStdin: replyChan <- struct{}{} - ctx.stdinStream = stream + ctx.StdinStream = stream case v1.StreamTypeStderr: replyChan <- struct{}{} - ctx.stderrStream = stream + ctx.StderrStream = stream default: // add other stream ... return nil, errors.New("unimplemented stream type") diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index 9b2c78ef..7be9a423 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/client" "github.com/mark3labs/mcp-go/mcp" ) @@ -48,10 +49,10 @@ func TestWatchKubeConfig(t *testing.T) { } func TestSseHeaders(t *testing.T) { - mockServer := NewMockServer() + mockServer := test.NewMockServer() defer mockServer.Close() before := func(c *mcpContext) { - c.withKubeConfig(mockServer.config) + c.withKubeConfig(mockServer.Config()) c.clientOptions = append(c.clientOptions, client.WithHeaders(map[string]string{"kubernetes-authorization": "Bearer a-token-from-mcp-client"})) } pathHeaders := make(map[string]http.Header, 0) diff --git a/pkg/mcp/pods_exec_test.go b/pkg/mcp/pods_exec_test.go index 919e80b2..de5c00bc 100644 --- a/pkg/mcp/pods_exec_test.go +++ b/pkg/mcp/pods_exec_test.go @@ -2,27 +2,29 @@ package mcp import ( "bytes" - "github.com/containers/kubernetes-mcp-server/pkg/config" - "github.com/mark3labs/mcp-go/mcp" "io" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" "strings" "testing" + + "github.com/containers/kubernetes-mcp-server/internal/test" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/mark3labs/mcp-go/mcp" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestPodsExec(t *testing.T) { testCase(t, func(c *mcpContext) { - mockServer := NewMockServer() + mockServer := test.NewMockServer() defer mockServer.Close() - c.withKubeConfig(mockServer.config) + c.withKubeConfig(mockServer.Config()) mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" { return } var stdin, stdout bytes.Buffer - ctx, err := createHTTPStreams(w, req, &StreamOptions{ + ctx, err := test.CreateHTTPStreams(w, req, &test.StreamOptions{ Stdin: &stdin, Stdout: &stdout, }) @@ -31,15 +33,15 @@ func TestPodsExec(t *testing.T) { _, _ = w.Write([]byte(err.Error())) return } - defer func(conn io.Closer) { _ = conn.Close() }(ctx.conn) - _, _ = io.WriteString(ctx.stdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n") - _, _ = io.WriteString(ctx.stdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n") + defer func(conn io.Closer) { _ = conn.Close() }(ctx.Closer) + _, _ = io.WriteString(ctx.StdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n") + _, _ = io.WriteString(ctx.StdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n") })) mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" { return } - writeObject(w, &v1.Pod{ + test.WriteObject(w, &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "pod-to-exec", diff --git a/pkg/mcp/pods_test.go b/pkg/mcp/pods_test.go index de61dbe7..de65afa4 100644 --- a/pkg/mcp/pods_test.go +++ b/pkg/mcp/pods_test.go @@ -1,12 +1,13 @@ package mcp import ( - "github.com/containers/kubernetes-mcp-server/pkg/config" - "github.com/containers/kubernetes-mcp-server/pkg/output" "regexp" "strings" "testing" + "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/containers/kubernetes-mcp-server/pkg/output" + "github.com/mark3labs/mcp-go/mcp" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" diff --git a/pkg/mcp/pods_top_test.go b/pkg/mcp/pods_top_test.go index 0b63cac8..1023d96f 100644 --- a/pkg/mcp/pods_top_test.go +++ b/pkg/mcp/pods_top_test.go @@ -5,6 +5,7 @@ import ( "regexp" "testing" + "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/mcp" "github.com/containers/kubernetes-mcp-server/pkg/config" @@ -12,9 +13,9 @@ import ( func TestPodsTopMetricsUnavailable(t *testing.T) { testCase(t, func(c *mcpContext) { - mockServer := NewMockServer() + mockServer := test.NewMockServer() defer mockServer.Close() - c.withKubeConfig(mockServer.config) + c.withKubeConfig(mockServer.Config()) mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) @@ -45,9 +46,9 @@ func TestPodsTopMetricsUnavailable(t *testing.T) { func TestPodsTopMetricsAvailable(t *testing.T) { testCase(t, func(c *mcpContext) { - mockServer := NewMockServer() + mockServer := test.NewMockServer() defer mockServer.Close() - c.withKubeConfig(mockServer.config) + c.withKubeConfig(mockServer.Config()) mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { println("Request received:", req.Method, req.URL.Path) // TODO: REMOVE LINE w.Header().Set("Content-Type", "application/json") @@ -211,9 +212,9 @@ func TestPodsTopMetricsAvailable(t *testing.T) { func TestPodsTopDenied(t *testing.T) { deniedResourcesServer := &config.StaticConfig{DeniedResources: []config.GroupVersionKind{{Group: "metrics.k8s.io", Version: "v1beta1"}}} testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - mockServer := NewMockServer() + mockServer := test.NewMockServer() defer mockServer.Close() - c.withKubeConfig(mockServer.config) + c.withKubeConfig(mockServer.Config()) mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)