Skip to content

Commit ce6ec78

Browse files
authored
test(pods): update pods tests to use testify and improve readability (#426)
Signed-off-by: Marc Nuri <[email protected]>
1 parent 09c8b23 commit ce6ec78

File tree

2 files changed

+623
-833
lines changed

2 files changed

+623
-833
lines changed

pkg/mcp/pods_run_test.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package mcp
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/containers/kubernetes-mcp-server/internal/test"
8+
"github.com/containers/kubernetes-mcp-server/pkg/config"
9+
"github.com/mark3labs/mcp-go/mcp"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"sigs.k8s.io/yaml"
12+
)
13+
14+
func TestPodsRun(t *testing.T) {
15+
testCase(t, func(c *mcpContext) {
16+
c.withEnvTest()
17+
t.Run("pods_run with nil image returns error", func(t *testing.T) {
18+
toolResult, _ := c.callTool("pods_run", map[string]interface{}{})
19+
if toolResult.IsError != true {
20+
t.Errorf("call tool should fail")
21+
return
22+
}
23+
if toolResult.Content[0].(mcp.TextContent).Text != "failed to run pod, missing argument image" {
24+
t.Errorf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
25+
return
26+
}
27+
})
28+
podsRunNilNamespace, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx"})
29+
t.Run("pods_run with image and nil namespace runs pod", func(t *testing.T) {
30+
if err != nil {
31+
t.Errorf("call tool failed %v", err)
32+
return
33+
}
34+
if podsRunNilNamespace.IsError {
35+
t.Errorf("call tool failed")
36+
return
37+
}
38+
})
39+
var decodedNilNamespace []unstructured.Unstructured
40+
err = yaml.Unmarshal([]byte(podsRunNilNamespace.Content[0].(mcp.TextContent).Text), &decodedNilNamespace)
41+
t.Run("pods_run with image and nil namespace has yaml content", func(t *testing.T) {
42+
if err != nil {
43+
t.Errorf("invalid tool result content %v", err)
44+
return
45+
}
46+
})
47+
t.Run("pods_run with image and nil namespace returns 1 item (Pod)", func(t *testing.T) {
48+
if len(decodedNilNamespace) != 1 {
49+
t.Errorf("invalid pods count, expected 1, got %v", len(decodedNilNamespace))
50+
return
51+
}
52+
if decodedNilNamespace[0].GetKind() != "Pod" {
53+
t.Errorf("invalid pod kind, expected Pod, got %v", decodedNilNamespace[0].GetKind())
54+
return
55+
}
56+
})
57+
t.Run("pods_run with image and nil namespace returns pod in default", func(t *testing.T) {
58+
if decodedNilNamespace[0].GetNamespace() != "default" {
59+
t.Errorf("invalid pod namespace, expected default, got %v", decodedNilNamespace[0].GetNamespace())
60+
return
61+
}
62+
})
63+
t.Run("pods_run with image and nil namespace returns pod with random name", func(t *testing.T) {
64+
if !strings.HasPrefix(decodedNilNamespace[0].GetName(), "kubernetes-mcp-server-run-") {
65+
t.Errorf("invalid pod name, expected random, got %v", decodedNilNamespace[0].GetName())
66+
return
67+
}
68+
})
69+
t.Run("pods_run with image and nil namespace returns pod with labels", func(t *testing.T) {
70+
labels := decodedNilNamespace[0].Object["metadata"].(map[string]interface{})["labels"].(map[string]interface{})
71+
if labels["app.kubernetes.io/name"] == "" {
72+
t.Errorf("invalid labels, expected app.kubernetes.io/name, got %v", labels)
73+
return
74+
}
75+
if labels["app.kubernetes.io/component"] == "" {
76+
t.Errorf("invalid labels, expected app.kubernetes.io/component, got %v", labels)
77+
return
78+
}
79+
if labels["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" {
80+
t.Errorf("invalid labels, expected app.kubernetes.io/managed-by, got %v", labels)
81+
return
82+
}
83+
if labels["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" {
84+
t.Errorf("invalid labels, expected app.kubernetes.io/part-of, got %v", labels)
85+
return
86+
}
87+
})
88+
t.Run("pods_run with image and nil namespace returns pod with nginx container", func(t *testing.T) {
89+
containers := decodedNilNamespace[0].Object["spec"].(map[string]interface{})["containers"].([]interface{})
90+
if containers[0].(map[string]interface{})["image"] != "nginx" {
91+
t.Errorf("invalid container name, expected nginx, got %v", containers[0].(map[string]interface{})["image"])
92+
return
93+
}
94+
})
95+
96+
podsRunNamespaceAndPort, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80})
97+
t.Run("pods_run with image, namespace, and port runs pod", func(t *testing.T) {
98+
if err != nil {
99+
t.Errorf("call tool failed %v", err)
100+
return
101+
}
102+
if podsRunNamespaceAndPort.IsError {
103+
t.Errorf("call tool failed")
104+
return
105+
}
106+
})
107+
var decodedNamespaceAndPort []unstructured.Unstructured
108+
err = yaml.Unmarshal([]byte(podsRunNamespaceAndPort.Content[0].(mcp.TextContent).Text), &decodedNamespaceAndPort)
109+
t.Run("pods_run with image, namespace, and port has yaml content", func(t *testing.T) {
110+
if err != nil {
111+
t.Errorf("invalid tool result content %v", err)
112+
return
113+
}
114+
})
115+
t.Run("pods_run with image, namespace, and port returns 2 items (Pod + Service)", func(t *testing.T) {
116+
if len(decodedNamespaceAndPort) != 2 {
117+
t.Errorf("invalid pods count, expected 2, got %v", len(decodedNamespaceAndPort))
118+
return
119+
}
120+
if decodedNamespaceAndPort[0].GetKind() != "Pod" {
121+
t.Errorf("invalid pod kind, expected Pod, got %v", decodedNamespaceAndPort[0].GetKind())
122+
return
123+
}
124+
if decodedNamespaceAndPort[1].GetKind() != "Service" {
125+
t.Errorf("invalid service kind, expected Service, got %v", decodedNamespaceAndPort[1].GetKind())
126+
return
127+
}
128+
})
129+
t.Run("pods_run with image, namespace, and port returns pod with port", func(t *testing.T) {
130+
containers := decodedNamespaceAndPort[0].Object["spec"].(map[string]interface{})["containers"].([]interface{})
131+
ports := containers[0].(map[string]interface{})["ports"].([]interface{})
132+
if ports[0].(map[string]interface{})["containerPort"] != int64(80) {
133+
t.Errorf("invalid container port, expected 80, got %v", ports[0].(map[string]interface{})["containerPort"])
134+
return
135+
}
136+
})
137+
t.Run("pods_run with image, namespace, and port returns service with port and selector", func(t *testing.T) {
138+
ports := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["ports"].([]interface{})
139+
if ports[0].(map[string]interface{})["port"] != int64(80) {
140+
t.Errorf("invalid service port, expected 80, got %v", ports[0].(map[string]interface{})["port"])
141+
return
142+
}
143+
if ports[0].(map[string]interface{})["targetPort"] != int64(80) {
144+
t.Errorf("invalid service target port, expected 80, got %v", ports[0].(map[string]interface{})["targetPort"])
145+
return
146+
}
147+
selector := decodedNamespaceAndPort[1].Object["spec"].(map[string]interface{})["selector"].(map[string]interface{})
148+
if selector["app.kubernetes.io/name"] == "" {
149+
t.Errorf("invalid service selector, expected app.kubernetes.io/name, got %v", selector)
150+
return
151+
}
152+
if selector["app.kubernetes.io/managed-by"] != "kubernetes-mcp-server" {
153+
t.Errorf("invalid service selector, expected app.kubernetes.io/managed-by, got %v", selector)
154+
return
155+
}
156+
if selector["app.kubernetes.io/part-of"] != "kubernetes-mcp-server-run-sandbox" {
157+
t.Errorf("invalid service selector, expected app.kubernetes.io/part-of, got %v", selector)
158+
return
159+
}
160+
})
161+
})
162+
}
163+
164+
func TestPodsRunDenied(t *testing.T) {
165+
deniedResourcesServer := test.Must(config.ReadToml([]byte(`
166+
denied_resources = [ { version = "v1", kind = "Pod" } ]
167+
`)))
168+
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
169+
c.withEnvTest()
170+
podsRun, _ := c.callTool("pods_run", map[string]interface{}{"image": "nginx"})
171+
t.Run("pods_run has error", func(t *testing.T) {
172+
if !podsRun.IsError {
173+
t.Fatalf("call tool should fail")
174+
}
175+
})
176+
t.Run("pods_run describes denial", func(t *testing.T) {
177+
expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod"
178+
if podsRun.Content[0].(mcp.TextContent).Text != expectedMessage {
179+
t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text)
180+
}
181+
})
182+
})
183+
}
184+
185+
func TestPodsRunInOpenShift(t *testing.T) {
186+
testCaseWithContext(t, &mcpContext{before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
187+
t.Run("pods_run with image, namespace, and port returns route with port", func(t *testing.T) {
188+
podsRunInOpenShift, err := c.callTool("pods_run", map[string]interface{}{"image": "nginx", "port": 80})
189+
if err != nil {
190+
t.Errorf("call tool failed %v", err)
191+
return
192+
}
193+
if podsRunInOpenShift.IsError {
194+
t.Errorf("call tool failed")
195+
return
196+
}
197+
var decodedPodServiceRoute []unstructured.Unstructured
198+
err = yaml.Unmarshal([]byte(podsRunInOpenShift.Content[0].(mcp.TextContent).Text), &decodedPodServiceRoute)
199+
if err != nil {
200+
t.Errorf("invalid tool result content %v", err)
201+
return
202+
}
203+
if len(decodedPodServiceRoute) != 3 {
204+
t.Errorf("invalid pods count, expected 3, got %v", len(decodedPodServiceRoute))
205+
return
206+
}
207+
if decodedPodServiceRoute[2].GetKind() != "Route" {
208+
t.Errorf("invalid route kind, expected Route, got %v", decodedPodServiceRoute[2].GetKind())
209+
return
210+
}
211+
targetPort := decodedPodServiceRoute[2].Object["spec"].(map[string]interface{})["port"].(map[string]interface{})["targetPort"].(int64)
212+
if targetPort != 80 {
213+
t.Errorf("invalid route target port, expected 80, got %v", targetPort)
214+
return
215+
}
216+
})
217+
})
218+
}

0 commit comments

Comments
 (0)