test(profiles): bootstrap initial testing support for profiles

This commit is contained in:
Marc Nuri
2025-05-16 11:30:04 +02:00
parent f23c153eb1
commit 1f00601f43
3 changed files with 109 additions and 82 deletions

View File

@@ -92,6 +92,9 @@ func TestMain(m *testing.M) {
}
type mcpContext struct {
profile Profile
before *func(*mcpContext)
after *func(*mcpContext)
ctx context.Context
tempDir string
cancel context.CancelFunc
@@ -102,10 +105,13 @@ type mcpContext struct {
func (c *mcpContext) beforeEach(t *testing.T) {
var err error
c.ctx, c.cancel = context.WithCancel(context.Background())
c.ctx, c.cancel = context.WithCancel(t.Context())
c.tempDir = t.TempDir()
c.withKubeConfig(nil)
if c.mcpServer, err = NewSever(Configuration{Profile: &FullProfile{}}); err != nil {
if c.before != nil {
(*c.before)(c)
}
if c.mcpServer, err = NewSever(Configuration{Profile: c.profile}); err != nil {
t.Fatal(err)
return
}
@@ -129,6 +135,9 @@ func (c *mcpContext) beforeEach(t *testing.T) {
}
func (c *mcpContext) afterEach() {
if c.after != nil {
(*c.after)(c)
}
c.cancel()
c.mcpServer.Close()
_ = c.mcpClient.Close()
@@ -136,7 +145,10 @@ func (c *mcpContext) afterEach() {
}
func testCase(t *testing.T, test func(c *mcpContext)) {
mcpCtx := &mcpContext{}
testCaseWithContext(t, &mcpContext{profile: &FullProfile{}}, test)
}
func testCaseWithContext(t *testing.T, mcpCtx *mcpContext, test func(c *mcpContext)) {
mcpCtx.beforeEach(t)
defer mcpCtx.afterEach()
test(mcpCtx)

View File

@@ -6,8 +6,6 @@ import (
"os"
"path/filepath"
"runtime"
"slices"
"strings"
"testing"
"time"
)
@@ -49,80 +47,3 @@ func TestWatchKubeConfig(t *testing.T) {
})
})
}
func TestTools(t *testing.T) {
expectedNames := []string{
"configuration_view",
"events_list",
"helm_install",
"helm_list",
"helm_uninstall",
"namespaces_list",
"pods_list",
"pods_list_in_namespace",
"pods_get",
"pods_delete",
"pods_log",
"pods_run",
"pods_exec",
"resources_list",
"resources_get",
"resources_create_or_update",
"resources_delete",
}
testCase(t, func(c *mcpContext) {
tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
t.Run("ListTools returns tools", func(t *testing.T) {
if err != nil {
t.Fatalf("call ListTools failed %v", err)
return
}
})
nameSet := make(map[string]bool)
for _, tool := range tools.Tools {
nameSet[tool.Name] = true
}
for _, name := range expectedNames {
t.Run("ListTools has "+name+" tool", func(t *testing.T) {
if nameSet[name] != true {
t.Fatalf("tool %s not found", name)
return
}
})
}
})
}
func TestToolsInOpenShift(t *testing.T) {
testCase(t, func(c *mcpContext) {
defer c.inOpenShift()() // n.b. two sets of parentheses to invoke the first function
c.mcpServer.server.AddTools(c.mcpServer.initNamespaces()...)
c.mcpServer.server.AddTools(c.mcpServer.initResources()...)
tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
t.Run("ListTools returns tools", func(t *testing.T) {
if err != nil {
t.Fatalf("call ListTools failed %v", err)
}
})
t.Run("ListTools contains projects_list tool", func(t *testing.T) {
idx := slices.IndexFunc(tools.Tools, func(tool mcp.Tool) bool {
return tool.Name == "projects_list"
})
if idx == -1 {
t.Fatalf("tool projects_list not found")
}
})
t.Run("ListTools has resources_list tool with OpenShift hint", func(t *testing.T) {
idx := slices.IndexFunc(tools.Tools, func(tool mcp.Tool) bool {
return tool.Name == "resources_list"
})
if idx == -1 {
t.Fatalf("tool resources_list not found")
}
if !strings.Contains(tools.Tools[idx].Description, ", route.openshift.io/v1 Route") {
t.Fatalf("tool resources_list does not have OpenShift hint, got %s", tools.Tools[9].Description)
}
})
})
}

94
pkg/mcp/profiles_test.go Normal file
View File

@@ -0,0 +1,94 @@
package mcp
import (
"github.com/mark3labs/mcp-go/mcp"
"slices"
"strings"
"testing"
)
func TestFullProfileTools(t *testing.T) {
expectedNames := []string{
"configuration_view",
"events_list",
"helm_install",
"helm_list",
"helm_uninstall",
"namespaces_list",
"pods_list",
"pods_list_in_namespace",
"pods_get",
"pods_delete",
"pods_log",
"pods_run",
"pods_exec",
"resources_list",
"resources_get",
"resources_create_or_update",
"resources_delete",
}
mcpCtx := &mcpContext{profile: &FullProfile{}}
testCaseWithContext(t, mcpCtx, func(c *mcpContext) {
tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
t.Run("ListTools returns tools", func(t *testing.T) {
if err != nil {
t.Fatalf("call ListTools failed %v", err)
return
}
})
nameSet := make(map[string]bool)
for _, tool := range tools.Tools {
nameSet[tool.Name] = true
}
for _, name := range expectedNames {
t.Run("ListTools has "+name+" tool", func(t *testing.T) {
if nameSet[name] != true {
t.Fatalf("tool %s not found", name)
return
}
})
}
})
}
func TestFullProfileToolsInOpenShift(t *testing.T) {
var after func(c *mcpContext)
before := func(c *mcpContext) {
cleanCrds := c.inOpenShift()
after = func(_ *mcpContext) {
cleanCrds()
}
}
mcpCtx := &mcpContext{
profile: &FullProfile{},
before: &before,
after: &after,
}
testCaseWithContext(t, mcpCtx, func(c *mcpContext) {
tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
t.Run("ListTools returns tools", func(t *testing.T) {
if err != nil {
t.Fatalf("call ListTools failed %v", err)
}
})
t.Run("ListTools contains projects_list tool", func(t *testing.T) {
idx := slices.IndexFunc(tools.Tools, func(tool mcp.Tool) bool {
return tool.Name == "projects_list"
})
if idx == -1 {
t.Fatalf("tool projects_list not found")
}
})
t.Run("ListTools has resources_list tool with OpenShift hint", func(t *testing.T) {
idx := slices.IndexFunc(tools.Tools, func(tool mcp.Tool) bool {
return tool.Name == "resources_list"
})
if idx == -1 {
t.Fatalf("tool resources_list not found")
}
if !strings.Contains(tools.Tools[idx].Description, ", route.openshift.io/v1 Route") {
t.Fatalf("tool resources_list does not have OpenShift hint, got %s", tools.Tools[9].Description)
}
})
})
}