mirror of
https://github.com/openshift/openshift-mcp-server.git
synced 2025-10-17 14:27:48 +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