11package mcp
22
33import (
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+
158228func TestResourcesGet (t * testing.T ) {
159229 testCase (t , func (c * mcpContext ) {
160230 c .withEnvTest ()
0 commit comments