@@ -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