Skip to content

Commit 54d3726

Browse files
authored
test(output): additional tests for --list-output=table
1 parent 7e10e82 commit 54d3726

File tree

3 files changed

+88
-18
lines changed

3 files changed

+88
-18
lines changed

pkg/mcp/namespaces_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestNamespacesListAsTable(t *testing.T) {
6464
t.Run("namespaces_list returns column headers", func(t *testing.T) {
6565
expectedHeaders := "APIVERSION\\s+KIND\\s+NAME\\s+STATUS\\s+AGE\\s+LABELS"
6666
if m, e := regexp.MatchString(expectedHeaders, out); !m || e != nil {
67-
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
67+
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
6868
}
6969
})
7070
t.Run("namespaces_list returns formatted row for ns-1", func(t *testing.T) {
@@ -75,7 +75,7 @@ func TestNamespacesListAsTable(t *testing.T) {
7575
"(?<age>\\d+(s|m))\\s+" +
7676
"(?<labels>kubernetes.io/metadata.name=ns-1)"
7777
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
78-
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, out)
78+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
7979
}
8080
})
8181
t.Run("namespaces_list returns formatted row for ns-2", func(t *testing.T) {
@@ -86,7 +86,7 @@ func TestNamespacesListAsTable(t *testing.T) {
8686
"(?<age>\\d+(s|m))\\s+" +
8787
"(?<labels>kubernetes.io/metadata.name=ns-2)"
8888
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
89-
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, out)
89+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
9090
}
9191
})
9292
})

pkg/mcp/pods_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func TestPodsListAsTable(t *testing.T) {
198198
t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) {
199199
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+READY\\s+STATUS\\s+RESTARTS\\s+AGE\\s+IP\\s+NODE\\s+NOMINATED NODE\\s+READINESS GATES\\s+LABELS"
200200
if m, e := regexp.MatchString(expectedHeaders, outPodsList); !m || e != nil {
201-
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList)
201+
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList)
202202
}
203203
})
204204
t.Run("pods_list_in_namespace returns formatted row for a-pod-in-ns-1", func(t *testing.T) {
@@ -216,7 +216,7 @@ func TestPodsListAsTable(t *testing.T) {
216216
"(?<readiness_gates><none>)\\s+" +
217217
"(?<labels><none>)"
218218
if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil {
219-
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
219+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
220220
}
221221
})
222222
t.Run("pods_list_in_namespace returns formatted row for a-pod-in-default", func(t *testing.T) {
@@ -234,7 +234,7 @@ func TestPodsListAsTable(t *testing.T) {
234234
"(?<readiness_gates><none>)\\s+" +
235235
"(?<labels>app=nginx)"
236236
if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil {
237-
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
237+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
238238
}
239239
})
240240
podsListInNamespace, err := c.callTool("pods_list_in_namespace", map[string]interface{}{
@@ -259,7 +259,7 @@ func TestPodsListAsTable(t *testing.T) {
259259
t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) {
260260
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+READY\\s+STATUS\\s+RESTARTS\\s+AGE\\s+IP\\s+NODE\\s+NOMINATED NODE\\s+READINESS GATES\\s+LABELS"
261261
if m, e := regexp.MatchString(expectedHeaders, outPodsListInNamespace); !m || e != nil {
262-
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace)
262+
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace)
263263
}
264264
})
265265
t.Run("pods_list_in_namespace returns formatted row", func(t *testing.T) {
@@ -277,7 +277,7 @@ func TestPodsListAsTable(t *testing.T) {
277277
"(?<readiness_gates><none>)\\s+" +
278278
"(?<labels><none>)"
279279
if m, e := regexp.MatchString(expectedRow, outPodsListInNamespace); !m || e != nil {
280-
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace)
280+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace)
281281
}
282282
})
283283
})

pkg/mcp/resources_test.go

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package mcp
22

33
import (
4+
"github.com/manusa/kubernetes-mcp-server/pkg/output"
5+
corev1 "k8s.io/api/core/v1"
6+
"regexp"
47
"strings"
58
"testing"
69

@@ -19,44 +22,36 @@ func TestResourcesList(t *testing.T) {
1922
toolResult, _ := c.callTool("resources_list", map[string]interface{}{})
2023
if !toolResult.IsError {
2124
t.Fatalf("call tool should fail")
22-
return
2325
}
2426
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument apiVersion" {
2527
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
26-
return
2728
}
2829
})
2930
t.Run("resources_list with missing kind returns error", func(t *testing.T) {
3031
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1"})
3132
if !toolResult.IsError {
3233
t.Fatalf("call tool should fail")
33-
return
3434
}
3535
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument kind" {
3636
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
37-
return
3837
}
3938
})
4039
t.Run("resources_list with invalid apiVersion returns error", func(t *testing.T) {
4140
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod"})
4241
if !toolResult.IsError {
4342
t.Fatalf("call tool should fail")
44-
return
4543
}
4644
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, invalid argument apiVersion" {
4745
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
48-
return
4946
}
5047
})
5148
t.Run("resources_list with nonexistent apiVersion returns error", func(t *testing.T) {
5249
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom"})
5350
if !toolResult.IsError {
5451
t.Fatalf("call tool should fail")
55-
return
5652
}
5753
if toolResult.Content[0].(mcp.TextContent).Text != `failed to list resources: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` {
5854
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
59-
return
6055
}
6156
})
6257
namespaces, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"})
@@ -75,13 +70,11 @@ func TestResourcesList(t *testing.T) {
7570
t.Run("resources_list has yaml content", func(t *testing.T) {
7671
if err != nil {
7772
t.Fatalf("invalid tool result content %v", err)
78-
return
7973
}
8074
})
8175
t.Run("resources_list returns more than 2 items", func(t *testing.T) {
8276
if len(decodedNamespaces) < 3 {
8377
t.Fatalf("invalid namespace count, expected >2, got %v", len(decodedNamespaces))
84-
return
8578
}
8679
})
8780

@@ -155,6 +148,83 @@ func TestResourcesList(t *testing.T) {
155148
})
156149
}
157150

151+
func TestResourcesListAsTable(t *testing.T) {
152+
testCaseWithContext(t, &mcpContext{listOutput: output.Table, before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
153+
c.withEnvTest()
154+
kc := c.newKubernetesClient()
155+
_, _ = kc.CoreV1().ConfigMaps("default").Create(t.Context(), &corev1.ConfigMap{
156+
ObjectMeta: metav1.ObjectMeta{Name: "a-configmap-to-list-as-table", Labels: map[string]string{"resource": "config-map"}},
157+
Data: map[string]string{"key": "value"},
158+
}, metav1.CreateOptions{})
159+
configMapList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap"})
160+
t.Run("resources_list returns ConfigMap list", func(t *testing.T) {
161+
if err != nil {
162+
t.Fatalf("call tool failed %v", err)
163+
}
164+
if configMapList.IsError {
165+
t.Fatalf("call tool failed")
166+
}
167+
})
168+
outConfigMapList := configMapList.Content[0].(mcp.TextContent).Text
169+
t.Run("resources_list returns column headers for ConfigMap list", func(t *testing.T) {
170+
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+DATA\\s+AGE\\s+LABELS"
171+
if m, e := regexp.MatchString(expectedHeaders, outConfigMapList); !m || e != nil {
172+
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outConfigMapList)
173+
}
174+
})
175+
t.Run("resources_list returns formatted row for a-configmap-to-list-as-table", func(t *testing.T) {
176+
expectedRow := "(?<namespace>default)\\s+" +
177+
"(?<apiVersion>v1)\\s+" +
178+
"(?<kind>ConfigMap)\\s+" +
179+
"(?<name>a-configmap-to-list-as-table)\\s+" +
180+
"(?<data>1)\\s+" +
181+
"(?<age>\\d+(s|m))\\s+" +
182+
"(?<labels>resource=config-map)"
183+
if m, e := regexp.MatchString(expectedRow, outConfigMapList); !m || e != nil {
184+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outConfigMapList)
185+
}
186+
})
187+
// Custom Resource List
188+
_, _ = dynamic.NewForConfigOrDie(envTestRestConfig).
189+
Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}).
190+
Namespace("default").
191+
Create(c.ctx, &unstructured.Unstructured{Object: map[string]interface{}{
192+
"apiVersion": "route.openshift.io/v1",
193+
"kind": "Route",
194+
"metadata": map[string]interface{}{
195+
"name": "an-openshift-route-to-list-as-table",
196+
},
197+
}}, metav1.CreateOptions{})
198+
routeList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "route.openshift.io/v1", "kind": "Route"})
199+
t.Run("resources_list returns Route list", func(t *testing.T) {
200+
if err != nil {
201+
t.Fatalf("call tool failed %v", err)
202+
}
203+
if routeList.IsError {
204+
t.Fatalf("call tool failed")
205+
}
206+
})
207+
outRouteList := routeList.Content[0].(mcp.TextContent).Text
208+
t.Run("resources_list returns column headers for Route list", func(t *testing.T) {
209+
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+AGE\\s+LABELS"
210+
if m, e := regexp.MatchString(expectedHeaders, outRouteList); !m || e != nil {
211+
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outRouteList)
212+
}
213+
})
214+
t.Run("resources_list returns formatted row for an-openshift-route-to-list-as-table", func(t *testing.T) {
215+
expectedRow := "(?<namespace>default)\\s+" +
216+
"(?<apiVersion>route.openshift.io/v1)\\s+" +
217+
"(?<kind>Route)\\s+" +
218+
"(?<name>an-openshift-route-to-list-as-table)\\s+" +
219+
"(?<age>\\d+(s|m))\\s+" +
220+
"(?<labels><none>)"
221+
if m, e := regexp.MatchString(expectedRow, outRouteList); !m || e != nil {
222+
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outRouteList)
223+
}
224+
})
225+
})
226+
}
227+
158228
func TestResourcesGet(t *testing.T) {
159229
testCase(t, func(c *mcpContext) {
160230
c.withEnvTest()

0 commit comments

Comments
 (0)