Skip to content

Commit 73e7ee3

Browse files
committed
test(pods): update PodsExec tests to use testify and improve readability
Signed-off-by: Marc Nuri <[email protected]>
1 parent 56f7ede commit 73e7ee3

File tree

2 files changed

+95
-88
lines changed

2 files changed

+95
-88
lines changed

internal/test/mcp.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package test
22

33
import (
4+
"net/http"
45
"net/http/httptest"
56
"testing"
67

78
"github.com/mark3labs/mcp-go/client"
89
"github.com/mark3labs/mcp-go/mcp"
9-
"github.com/mark3labs/mcp-go/server"
1010
"github.com/stretchr/testify/require"
1111
"golang.org/x/net/context"
1212
)
@@ -17,7 +17,7 @@ type McpClient struct {
1717
*client.Client
1818
}
1919

20-
func NewMcpClient(t *testing.T, mcpHttpServer *server.StreamableHTTPServer) *McpClient {
20+
func NewMcpClient(t *testing.T, mcpHttpServer http.Handler) *McpClient {
2121
require.NotNil(t, mcpHttpServer, "McpHttpServer must be provided")
2222
var err error
2323
ret := &McpClient{ctx: t.Context()}

pkg/mcp/pods_exec_test.go

Lines changed: 93 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -7,125 +7,132 @@ import (
77
"strings"
88
"testing"
99

10+
"github.com/BurntSushi/toml"
1011
"github.com/mark3labs/mcp-go/mcp"
12+
"github.com/stretchr/testify/suite"
1113
v1 "k8s.io/api/core/v1"
1214
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1315

1416
"github.com/containers/kubernetes-mcp-server/internal/test"
15-
"github.com/containers/kubernetes-mcp-server/pkg/config"
1617
)
1718

18-
func TestPodsExec(t *testing.T) {
19-
testCase(t, func(c *mcpContext) {
20-
mockServer := test.NewMockServer()
21-
defer mockServer.Close()
22-
c.withKubeConfig(mockServer.Config())
23-
mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
24-
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" {
25-
return
26-
}
27-
var stdin, stdout bytes.Buffer
28-
ctx, err := test.CreateHTTPStreams(w, req, &test.StreamOptions{
29-
Stdin: &stdin,
30-
Stdout: &stdout,
31-
})
32-
if err != nil {
33-
w.WriteHeader(http.StatusInternalServerError)
34-
_, _ = w.Write([]byte(err.Error()))
35-
return
36-
}
37-
defer func(conn io.Closer) { _ = conn.Close() }(ctx.Closer)
38-
_, _ = io.WriteString(ctx.StdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n")
39-
_, _ = io.WriteString(ctx.StdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n")
40-
}))
41-
mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
42-
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" {
43-
return
44-
}
45-
test.WriteObject(w, &v1.Pod{
46-
ObjectMeta: metav1.ObjectMeta{
47-
Namespace: "default",
48-
Name: "pod-to-exec",
49-
},
50-
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container-to-exec"}}},
51-
})
52-
}))
53-
podsExecNilNamespace, err := c.callTool("pods_exec", map[string]interface{}{
19+
type PodsExecSuite struct {
20+
BaseMcpSuite
21+
mockServer *test.MockServer
22+
}
23+
24+
func (s *PodsExecSuite) SetupTest() {
25+
s.BaseMcpSuite.SetupTest()
26+
s.mockServer = test.NewMockServer()
27+
s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T())
28+
}
29+
30+
func (s *PodsExecSuite) TearDownTest() {
31+
s.BaseMcpSuite.TearDownTest()
32+
if s.mockServer != nil {
33+
s.mockServer.Close()
34+
}
35+
}
36+
37+
func (s *PodsExecSuite) TestPodsExec() {
38+
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
39+
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec/exec" {
40+
return
41+
}
42+
var stdin, stdout bytes.Buffer
43+
ctx, err := test.CreateHTTPStreams(w, req, &test.StreamOptions{
44+
Stdin: &stdin,
45+
Stdout: &stdout,
46+
})
47+
if err != nil {
48+
w.WriteHeader(http.StatusInternalServerError)
49+
_, _ = w.Write([]byte(err.Error()))
50+
return
51+
}
52+
defer func(conn io.Closer) { _ = conn.Close() }(ctx.Closer)
53+
_, _ = io.WriteString(ctx.StdoutStream, "command:"+strings.Join(req.URL.Query()["command"], " ")+"\n")
54+
_, _ = io.WriteString(ctx.StdoutStream, "container:"+strings.Join(req.URL.Query()["container"], " ")+"\n")
55+
}))
56+
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
57+
if req.URL.Path != "/api/v1/namespaces/default/pods/pod-to-exec" {
58+
return
59+
}
60+
test.WriteObject(w, &v1.Pod{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Namespace: "default",
63+
Name: "pod-to-exec",
64+
},
65+
Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container-to-exec"}}},
66+
})
67+
}))
68+
s.InitMcpClient()
69+
70+
s.Run("pods_exec(name=pod-to-exec, command=[ls -l])", func() {
71+
result, err := s.CallTool("pods_exec", map[string]interface{}{
5472
"name": "pod-to-exec",
5573
"command": []interface{}{"ls", "-l"},
5674
})
57-
t.Run("pods_exec with name and nil namespace returns command output", func(t *testing.T) {
58-
if err != nil {
59-
t.Fatalf("call tool failed %v", err)
60-
}
61-
if podsExecNilNamespace.IsError {
62-
t.Fatalf("call tool failed: %v", podsExecNilNamespace.Content)
63-
}
64-
if !strings.Contains(podsExecNilNamespace.Content[0].(mcp.TextContent).Text, "command:ls -l\n") {
65-
t.Errorf("unexpected result %v", podsExecNilNamespace.Content[0].(mcp.TextContent).Text)
66-
}
75+
s.Require().NotNil(result)
76+
s.Run("returns command output", func() {
77+
s.NoError(err, "call tool failed %v", err)
78+
s.Falsef(result.IsError, "call tool failed: %v", result.Content)
79+
s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text)
6780
})
68-
podsExecInNamespace, err := c.callTool("pods_exec", map[string]interface{}{
81+
})
82+
s.Run("pods_exec(name=pod-to-exec, namespace=default, command=[ls -l])", func() {
83+
result, err := s.CallTool("pods_exec", map[string]interface{}{
6984
"namespace": "default",
7085
"name": "pod-to-exec",
7186
"command": []interface{}{"ls", "-l"},
7287
})
73-
t.Run("pods_exec with name and namespace returns command output", func(t *testing.T) {
74-
if err != nil {
75-
t.Fatalf("call tool failed %v", err)
76-
}
77-
if podsExecInNamespace.IsError {
78-
t.Fatalf("call tool failed: %v", podsExecInNamespace.Content)
79-
}
80-
if !strings.Contains(podsExecInNamespace.Content[0].(mcp.TextContent).Text, "command:ls -l\n") {
81-
t.Errorf("unexpected result %v", podsExecInNamespace.Content[0].(mcp.TextContent).Text)
82-
}
88+
s.Require().NotNil(result)
89+
s.Run("returns command output", func() {
90+
s.NoError(err, "call tool failed %v", err)
91+
s.Falsef(result.IsError, "call tool failed: %v", result.Content)
92+
s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text)
8393
})
84-
podsExecInNamespaceAndContainer, err := c.callTool("pods_exec", map[string]interface{}{
94+
})
95+
s.Run("pods_exec(name=pod-to-exec, namespace=default, command=[ls -l], container=a-specific-container)", func() {
96+
result, err := s.CallTool("pods_exec", map[string]interface{}{
8597
"namespace": "default",
8698
"name": "pod-to-exec",
8799
"command": []interface{}{"ls", "-l"},
88100
"container": "a-specific-container",
89101
})
90-
t.Run("pods_exec with name, namespace, and container returns command output", func(t *testing.T) {
91-
if err != nil {
92-
t.Fatalf("call tool failed %v", err)
93-
}
94-
if podsExecInNamespaceAndContainer.IsError {
95-
t.Fatalf("call tool failed")
96-
}
97-
if !strings.Contains(podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text, "command:ls -l\n") {
98-
t.Errorf("unexpected result %v", podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text)
99-
}
100-
if !strings.Contains(podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text, "container:a-specific-container\n") {
101-
t.Errorf("expected container name not found %v", podsExecInNamespaceAndContainer.Content[0].(mcp.TextContent).Text)
102-
}
102+
s.Require().NotNil(result)
103+
s.Run("returns command output", func() {
104+
s.NoError(err, "call tool failed %v", err)
105+
s.Falsef(result.IsError, "call tool failed: %v", result.Content)
106+
s.Contains(result.Content[0].(mcp.TextContent).Text, "command:ls -l\n", "unexpected result %v", result.Content[0].(mcp.TextContent).Text)
103107
})
104108
})
105109
}
106110

107-
func TestPodsExecDenied(t *testing.T) {
108-
deniedResourcesServer := test.Must(config.ReadToml([]byte(`
111+
func (s *PodsExecSuite) TestPodsExecDenied() {
112+
s.Require().NoError(toml.Unmarshal([]byte(`
109113
denied_resources = [ { version = "v1", kind = "Pod" } ]
110-
`)))
111-
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
112-
c.withEnvTest()
113-
podsRun, _ := c.callTool("pods_exec", map[string]interface{}{
114+
`), s.Cfg), "Expected to parse denied resources config")
115+
s.InitMcpClient()
116+
s.Run("pods_exec (denied)", func() {
117+
toolResult, err := s.CallTool("pods_exec", map[string]interface{}{
114118
"namespace": "default",
115119
"name": "pod-to-exec",
116120
"command": []interface{}{"ls", "-l"},
117121
"container": "a-specific-container",
118122
})
119-
t.Run("pods_exec has error", func(t *testing.T) {
120-
if !podsRun.IsError {
121-
t.Fatalf("call tool should fail")
122-
}
123+
s.Require().NotNil(toolResult, "toolResult should not be nil")
124+
s.Run("has error", func() {
125+
s.Truef(toolResult.IsError, "call tool should fail")
126+
s.Nilf(err, "call tool should not return error object")
123127
})
124-
t.Run("pods_exec describes denial", func(t *testing.T) {
128+
s.Run("describes denial", func() {
125129
expectedMessage := "failed to exec in pod pod-to-exec in namespace default: resource not allowed: /v1, Kind=Pod"
126-
if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage {
127-
t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text)
128-
}
130+
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
131+
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
129132
})
130133
})
131134
}
135+
136+
func TestPodsExec(t *testing.T) {
137+
suite.Run(t, new(PodsExecSuite))
138+
}

0 commit comments

Comments
 (0)