mirror of
				https://github.com/containers/kubernetes-mcp-server.git
				synced 2025-10-23 01:22:57 +03:00 
			
		
		
		
	test: extract mock-server for reutilization (#247)
Signed-off-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
		| @@ -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") | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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" | ||||
|   | ||||
| @@ -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-) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Marc Nuri
					Marc Nuri