Skip to content

Commit e16114d

Browse files
authored
test(mcp): refactor core toolset tests (namespaces) (#330)
Signed-off-by: Marc Nuri <[email protected]>
1 parent 2bf6c54 commit e16114d

File tree

3 files changed

+63
-68
lines changed

3 files changed

+63
-68
lines changed

pkg/mcp/common_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ type BaseMcpSuite struct {
429429

430430
func (s *BaseMcpSuite) SetupTest() {
431431
s.Cfg = config.Default()
432+
s.Cfg.ListOutput = "yaml"
432433
s.Cfg.KubeConfig = filepath.Join(s.T().TempDir(), "config")
433434
s.Require().NoError(os.WriteFile(s.Cfg.KubeConfig, envTest.KubeConfig, 0600), "Expected to write kubeconfig")
434435
}

pkg/mcp/events_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ func (s *EventsSuite) TestEventsListDenied() {
107107
`), s.Cfg), "Expected to parse denied resources config")
108108
s.InitMcpClient()
109109
s.Run("events_list (denied)", func() {
110-
eventList, err := s.CallTool("events_list", map[string]interface{}{})
111-
s.Run("events_list has error", func() {
112-
s.Truef(eventList.IsError, "call tool should fail")
110+
toolResult, err := s.CallTool("events_list", map[string]interface{}{})
111+
s.Run("has error", func() {
112+
s.Truef(toolResult.IsError, "call tool should fail")
113113
s.Nilf(err, "call tool should not return error object")
114114
})
115-
s.Run("events_list describes denial", func() {
115+
s.Run("describes denial", func() {
116116
expectedMessage := "failed to list events in all namespaces: resource not allowed: /v1, Kind=Event"
117-
s.Equalf(expectedMessage, eventList.Content[0].(mcp.TextContent).Text,
118-
"expected descriptive error '%s', got %v", expectedMessage, eventList.Content[0].(mcp.TextContent).Text)
117+
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
118+
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
119119
})
120120
})
121121
}

pkg/mcp/namespaces_test.go

Lines changed: 56 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"slices"
66
"testing"
77

8+
"github.com/BurntSushi/toml"
89
"github.com/mark3labs/mcp-go/mcp"
10+
"github.com/stretchr/testify/suite"
911
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1012
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1113
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -14,108 +16,100 @@ import (
1416

1517
"github.com/containers/kubernetes-mcp-server/internal/test"
1618
"github.com/containers/kubernetes-mcp-server/pkg/config"
17-
"github.com/containers/kubernetes-mcp-server/pkg/output"
1819
)
1920

20-
func TestNamespacesList(t *testing.T) {
21-
testCase(t, func(c *mcpContext) {
22-
c.withEnvTest()
23-
toolResult, err := c.callTool("namespaces_list", map[string]interface{}{})
24-
t.Run("namespaces_list returns namespace list", func(t *testing.T) {
25-
if err != nil {
26-
t.Fatalf("call tool failed %v", err)
27-
}
28-
if toolResult.IsError {
29-
t.Fatalf("call tool failed")
30-
}
21+
type NamespacesSuite struct {
22+
BaseMcpSuite
23+
}
24+
25+
func (s *NamespacesSuite) TestNamespacesList() {
26+
s.InitMcpClient()
27+
s.Run("namespaces_list", func() {
28+
toolResult, err := s.CallTool("namespaces_list", map[string]interface{}{})
29+
s.Run("no error", func() {
30+
s.Nilf(err, "call tool failed %v", err)
31+
s.Falsef(toolResult.IsError, "call tool failed")
3132
})
33+
s.Require().NotNil(toolResult, "Expected tool result from call")
3234
var decoded []unstructured.Unstructured
3335
err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decoded)
34-
t.Run("namespaces_list has yaml content", func(t *testing.T) {
35-
if err != nil {
36-
t.Fatalf("invalid tool result content %v", err)
37-
}
36+
s.Run("has yaml content", func() {
37+
s.Nilf(err, "invalid tool result content %v", err)
3838
})
39-
t.Run("namespaces_list returns at least 3 items", func(t *testing.T) {
40-
if len(decoded) < 3 {
41-
t.Errorf("invalid namespace count, expected at least 3, got %v", len(decoded))
42-
}
39+
s.Run("returns at least 3 items", func() {
40+
s.Truef(len(decoded) >= 3, "expected at least 3 items, got %v", len(decoded))
4341
for _, expectedNamespace := range []string{"default", "ns-1", "ns-2"} {
44-
idx := slices.IndexFunc(decoded, func(ns unstructured.Unstructured) bool {
42+
s.Truef(slices.ContainsFunc(decoded, func(ns unstructured.Unstructured) bool {
4543
return ns.GetName() == expectedNamespace
46-
})
47-
if idx == -1 {
48-
t.Errorf("namespace %s not found in the list", expectedNamespace)
49-
}
44+
}), "namespace %s not found in the list", expectedNamespace)
5045
}
5146
})
5247
})
5348
}
5449

55-
func TestNamespacesListDenied(t *testing.T) {
56-
deniedResourcesServer := test.Must(config.ReadToml([]byte(`
50+
func (s *NamespacesSuite) TestNamespacesListDenied() {
51+
s.Require().NoError(toml.Unmarshal([]byte(`
5752
denied_resources = [ { version = "v1", kind = "Namespace" } ]
58-
`)))
59-
testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) {
60-
c.withEnvTest()
61-
namespacesList, _ := c.callTool("namespaces_list", map[string]interface{}{})
62-
t.Run("namespaces_list has error", func(t *testing.T) {
63-
if !namespacesList.IsError {
64-
t.Fatalf("call tool should fail")
65-
}
53+
`), s.Cfg), "Expected to parse denied resources config")
54+
s.InitMcpClient()
55+
s.Run("namespaces_list (denied)", func() {
56+
toolResult, err := s.CallTool("namespaces_list", map[string]interface{}{})
57+
s.Run("has error", func() {
58+
s.Truef(toolResult.IsError, "call tool should fail")
59+
s.Nilf(err, "call tool should not return error object")
6660
})
67-
t.Run("namespaces_list describes denial", func(t *testing.T) {
61+
s.Run("describes denial", func() {
6862
expectedMessage := "failed to list namespaces: resource not allowed: /v1, Kind=Namespace"
69-
if namespacesList.Content[0].(mcp.TextContent).Text != expectedMessage {
70-
t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, namespacesList.Content[0].(mcp.TextContent).Text)
71-
}
63+
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
64+
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
7265
})
7366
})
7467
}
7568

76-
func TestNamespacesListAsTable(t *testing.T) {
77-
testCaseWithContext(t, &mcpContext{listOutput: output.Table}, func(c *mcpContext) {
78-
c.withEnvTest()
79-
toolResult, err := c.callTool("namespaces_list", map[string]interface{}{})
80-
t.Run("namespaces_list returns namespace list", func(t *testing.T) {
81-
if err != nil {
82-
t.Fatalf("call tool failed %v", err)
83-
}
84-
if toolResult.IsError {
85-
t.Fatalf("call tool failed")
86-
}
69+
func (s *NamespacesSuite) TestNamespacesListAsTable() {
70+
s.Cfg.ListOutput = "table"
71+
s.InitMcpClient()
72+
s.Run("namespaces_list (list_output=table)", func() {
73+
toolResult, err := s.CallTool("namespaces_list", map[string]interface{}{})
74+
s.Run("no error", func() {
75+
s.Nilf(err, "call tool failed %v", err)
76+
s.Falsef(toolResult.IsError, "call tool failed")
8777
})
78+
s.Require().NotNil(toolResult, "Expected tool result from call")
8879
out := toolResult.Content[0].(mcp.TextContent).Text
89-
t.Run("namespaces_list returns column headers", func(t *testing.T) {
80+
s.Run("returns column headers", func() {
9081
expectedHeaders := "APIVERSION\\s+KIND\\s+NAME\\s+STATUS\\s+AGE\\s+LABELS"
91-
if m, e := regexp.MatchString(expectedHeaders, out); !m || e != nil {
92-
t.Fatalf("Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
93-
}
82+
m, e := regexp.MatchString(expectedHeaders, out)
83+
s.Truef(m, "Expected headers '%s' not found in output:\n%s", expectedHeaders, out)
84+
s.NoErrorf(e, "Error matching headers regex: %v", e)
9485
})
95-
t.Run("namespaces_list returns formatted row for ns-1", func(t *testing.T) {
86+
s.Run("returns formatted row for ns-1", func() {
9687
expectedRow := "(?<apiVersion>v1)\\s+" +
9788
"(?<kind>Namespace)\\s+" +
9889
"(?<name>ns-1)\\s+" +
9990
"(?<status>Active)\\s+" +
10091
"(?<age>(\\d+m)?(\\d+s)?)\\s+" +
10192
"(?<labels>kubernetes.io/metadata.name=ns-1)"
102-
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
103-
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
104-
}
93+
m, e := regexp.MatchString(expectedRow, out)
94+
s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, out)
95+
s.NoErrorf(e, "Error matching ns-1 regex: %v", e)
10596
})
106-
t.Run("namespaces_list returns formatted row for ns-2", func(t *testing.T) {
97+
s.Run("returns formatted row for ns-2", func() {
10798
expectedRow := "(?<apiVersion>v1)\\s+" +
10899
"(?<kind>Namespace)\\s+" +
109100
"(?<name>ns-2)\\s+" +
110101
"(?<status>Active)\\s+" +
111102
"(?<age>(\\d+m)?(\\d+s)?)\\s+" +
112103
"(?<labels>kubernetes.io/metadata.name=ns-2)"
113-
if m, e := regexp.MatchString(expectedRow, out); !m || e != nil {
114-
t.Fatalf("Expected row '%s' not found in output:\n%s", expectedRow, out)
115-
}
104+
m, e := regexp.MatchString(expectedRow, out)
105+
s.Truef(m, "Expected row '%s' not found in output:\n%s", expectedRow, out)
106+
s.NoErrorf(e, "Error matching ns-2 regex: %v", e)
116107
})
117108
})
109+
}
118110

111+
func TestNamespaces(t *testing.T) {
112+
suite.Run(t, new(NamespacesSuite))
119113
}
120114

121115
func TestProjectsListInOpenShift(t *testing.T) {

0 commit comments

Comments
 (0)