mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
test(output): additional tests for --list-output=table
This commit is contained in:
@@ -64,7 +64,7 @@ func TestNamespacesListAsTable(t *testing.T) {
|
||||
t.Run("namespaces_list returns column headers", func(t *testing.T) {
|
||||
expectedHeaders := "APIVERSION\\s+KIND\\s+NAME\\s+STATUS\\s+AGE\\s+LABELS"
|
||||
if m, e := regexp.MatchString(expectedHeaders, out); !m || e != nil {
|
||||
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
|
||||
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
|
||||
}
|
||||
})
|
||||
t.Run("namespaces_list returns formatted row for ns-1", func(t *testing.T) {
|
||||
@@ -75,7 +75,7 @@ func TestNamespacesListAsTable(t *testing.T) {
|
||||
"(?<age>\\d+(s|m))\\s+" +
|
||||
"(?<labels>kubernetes.io/metadata.name=ns-1)"
|
||||
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
|
||||
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, out)
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
|
||||
}
|
||||
})
|
||||
t.Run("namespaces_list returns formatted row for ns-2", func(t *testing.T) {
|
||||
@@ -86,7 +86,7 @@ func TestNamespacesListAsTable(t *testing.T) {
|
||||
"(?<age>\\d+(s|m))\\s+" +
|
||||
"(?<labels>kubernetes.io/metadata.name=ns-2)"
|
||||
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
|
||||
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, out)
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -198,7 +198,7 @@ func TestPodsListAsTable(t *testing.T) {
|
||||
t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) {
|
||||
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"
|
||||
if m, e := regexp.MatchString(expectedHeaders, outPodsList); !m || e != nil {
|
||||
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList)
|
||||
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsList)
|
||||
}
|
||||
})
|
||||
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) {
|
||||
"(?<readiness_gates><none>)\\s+" +
|
||||
"(?<labels><none>)"
|
||||
if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil {
|
||||
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
|
||||
}
|
||||
})
|
||||
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) {
|
||||
"(?<readiness_gates><none>)\\s+" +
|
||||
"(?<labels>app=nginx)"
|
||||
if m, e := regexp.MatchString(expectedRow, outPodsList); !m || e != nil {
|
||||
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsList)
|
||||
}
|
||||
})
|
||||
podsListInNamespace, err := c.callTool("pods_list_in_namespace", map[string]interface{}{
|
||||
@@ -259,7 +259,7 @@ func TestPodsListAsTable(t *testing.T) {
|
||||
t.Run("pods_list_in_namespace returns column headers", func(t *testing.T) {
|
||||
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"
|
||||
if m, e := regexp.MatchString(expectedHeaders, outPodsListInNamespace); !m || e != nil {
|
||||
t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace)
|
||||
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outPodsListInNamespace)
|
||||
}
|
||||
})
|
||||
t.Run("pods_list_in_namespace returns formatted row", func(t *testing.T) {
|
||||
@@ -277,7 +277,7 @@ func TestPodsListAsTable(t *testing.T) {
|
||||
"(?<readiness_gates><none>)\\s+" +
|
||||
"(?<labels><none>)"
|
||||
if m, e := regexp.MatchString(expectedRow, outPodsListInNamespace); !m || e != nil {
|
||||
t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace)
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outPodsListInNamespace)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -19,44 +22,36 @@ func TestResourcesList(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_list", map[string]interface{}{})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument apiVersion" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_list with missing kind returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, missing argument kind" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_list with invalid apiVersion returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(mcp.TextContent).Text != "failed to list resources, invalid argument apiVersion" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_list with nonexistent apiVersion returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_list", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(mcp.TextContent).Text != `failed to list resources: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
return
|
||||
}
|
||||
})
|
||||
namespaces, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"})
|
||||
@@ -75,13 +70,11 @@ func TestResourcesList(t *testing.T) {
|
||||
t.Run("resources_list has yaml content", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("invalid tool result content %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_list returns more than 2 items", func(t *testing.T) {
|
||||
if len(decodedNamespaces) < 3 {
|
||||
t.Fatalf("invalid namespace count, expected >2, got %v", len(decodedNamespaces))
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
@@ -155,6 +148,83 @@ func TestResourcesList(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestResourcesListAsTable(t *testing.T) {
|
||||
testCaseWithContext(t, &mcpContext{listOutput: output.Table, before: inOpenShift, after: inOpenShiftClear}, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
kc := c.newKubernetesClient()
|
||||
_, _ = kc.CoreV1().ConfigMaps("default").Create(t.Context(), &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "a-configmap-to-list-as-table", Labels: map[string]string{"resource": "config-map"}},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}, metav1.CreateOptions{})
|
||||
configMapList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "v1", "kind": "ConfigMap"})
|
||||
t.Run("resources_list returns ConfigMap list", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
}
|
||||
if configMapList.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
}
|
||||
})
|
||||
outConfigMapList := configMapList.Content[0].(mcp.TextContent).Text
|
||||
t.Run("resources_list returns column headers for ConfigMap list", func(t *testing.T) {
|
||||
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+DATA\\s+AGE\\s+LABELS"
|
||||
if m, e := regexp.MatchString(expectedHeaders, outConfigMapList); !m || e != nil {
|
||||
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outConfigMapList)
|
||||
}
|
||||
})
|
||||
t.Run("resources_list returns formatted row for a-configmap-to-list-as-table", func(t *testing.T) {
|
||||
expectedRow := "(?<namespace>default)\\s+" +
|
||||
"(?<apiVersion>v1)\\s+" +
|
||||
"(?<kind>ConfigMap)\\s+" +
|
||||
"(?<name>a-configmap-to-list-as-table)\\s+" +
|
||||
"(?<data>1)\\s+" +
|
||||
"(?<age>\\d+(s|m))\\s+" +
|
||||
"(?<labels>resource=config-map)"
|
||||
if m, e := regexp.MatchString(expectedRow, outConfigMapList); !m || e != nil {
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outConfigMapList)
|
||||
}
|
||||
})
|
||||
// Custom Resource List
|
||||
_, _ = dynamic.NewForConfigOrDie(envTestRestConfig).
|
||||
Resource(schema.GroupVersionResource{Group: "route.openshift.io", Version: "v1", Resource: "routes"}).
|
||||
Namespace("default").
|
||||
Create(c.ctx, &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "route.openshift.io/v1",
|
||||
"kind": "Route",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "an-openshift-route-to-list-as-table",
|
||||
},
|
||||
}}, metav1.CreateOptions{})
|
||||
routeList, err := c.callTool("resources_list", map[string]interface{}{"apiVersion": "route.openshift.io/v1", "kind": "Route"})
|
||||
t.Run("resources_list returns Route list", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
}
|
||||
if routeList.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
}
|
||||
})
|
||||
outRouteList := routeList.Content[0].(mcp.TextContent).Text
|
||||
t.Run("resources_list returns column headers for Route list", func(t *testing.T) {
|
||||
expectedHeaders := "NAMESPACE\\s+APIVERSION\\s+KIND\\s+NAME\\s+AGE\\s+LABELS"
|
||||
if m, e := regexp.MatchString(expectedHeaders, outRouteList); !m || e != nil {
|
||||
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, outRouteList)
|
||||
}
|
||||
})
|
||||
t.Run("resources_list returns formatted row for an-openshift-route-to-list-as-table", func(t *testing.T) {
|
||||
expectedRow := "(?<namespace>default)\\s+" +
|
||||
"(?<apiVersion>route.openshift.io/v1)\\s+" +
|
||||
"(?<kind>Route)\\s+" +
|
||||
"(?<name>an-openshift-route-to-list-as-table)\\s+" +
|
||||
"(?<age>\\d+(s|m))\\s+" +
|
||||
"(?<labels><none>)"
|
||||
if m, e := regexp.MatchString(expectedRow, outRouteList); !m || e != nil {
|
||||
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, outRouteList)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestResourcesGet(t *testing.T) {
|
||||
testCase(t, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
|
||||
Reference in New Issue
Block a user