From e12e497588f50b1d8c91970b28cb1db72841255e Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 23 Oct 2025 17:14:36 +0200 Subject: [PATCH] test(pods): update PodsTop tests to use testify and improve readability Signed-off-by: Marc Nuri --- pkg/mcp/pods_top_test.go | 427 +++++++++++++++++++-------------------- 1 file changed, 213 insertions(+), 214 deletions(-) diff --git a/pkg/mcp/pods_top_test.go b/pkg/mcp/pods_top_test.go index 9fd218bb..92f6505a 100644 --- a/pkg/mcp/pods_top_test.go +++ b/pkg/mcp/pods_top_test.go @@ -5,247 +5,246 @@ import ( "regexp" "testing" + "github.com/BurntSushi/toml" "github.com/containers/kubernetes-mcp-server/internal/test" "github.com/mark3labs/mcp-go/mcp" - - "github.com/containers/kubernetes-mcp-server/pkg/config" + "github.com/stretchr/testify/suite" ) -func TestPodsTopMetricsUnavailable(t *testing.T) { - testCase(t, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - })) - podsTopMetricsApiUnavailable, err := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_top with metrics API not available", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - if !podsTopMetricsApiUnavailable.IsError { - t.Errorf("call tool should have returned an error") - } - if podsTopMetricsApiUnavailable.Content[0].(mcp.TextContent).Text != "failed to get pods top: metrics API is not available" { - t.Errorf("call tool returned unexpected content: %s", podsTopMetricsApiUnavailable.Content[0].(mcp.TextContent).Text) - } - }) +type PodsTopSuite struct { + BaseMcpSuite + mockServer *test.MockServer +} + +func (s *PodsTopSuite) SetupTest() { + s.BaseMcpSuite.SetupTest() + s.mockServer = test.NewMockServer() + s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T()) +} + +func (s *PodsTopSuite) TearDownTest() { + s.BaseMcpSuite.TearDownTest() + if s.mockServer != nil { + s.mockServer.Close() + } +} + +func (s *PodsTopSuite) TestPodsTopMetricsUnavailable() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":[],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + })) + s.InitMcpClient() + + s.Run("pods_top with metrics API not available", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.NoError(err, "call tool failed %v", err) + s.Require().NoError(err) + s.True(result.IsError, "call tool should have returned an error") + s.Equalf("failed to get pods top: metrics API is not available", result.Content[0].(mcp.TextContent).Text, + "call tool returned unexpected content: %s", result.Content[0].(mcp.TextContent).Text) }) } -func TestPodsTopMetricsAvailable(t *testing.T) { - testCase(t, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - println("Request received:", req.Method, req.URL.Path) // TODO: REMOVE LINE - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Resources) - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { - _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) - return - } - // Pod Metrics from all namespaces - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/pods" { - if req.URL.Query().Get("labelSelector") == "app=pod-ns-5-42" { - _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-ns-5-42","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"42m","memory":"42Mi","swap":"42Mi"}}]}` + - `]}`)) - } else { - _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"100m","memory":"200Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"200m","memory":"300Mi","swap":"37Mi"}}]},` + - `{"metadata":{"name":"pod-2","namespace":"ns-1"},"containers":[{"name":"container-1-ns-1","usage":{"cpu":"300m","memory":"400Mi","swap":"42Mi"}}]}` + - `]}`)) - - } - return - } - // Pod Metrics from configured namespace - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" { +func (s *PodsTopSuite) TestPodsTopMetricsAvailable() { + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Resources) + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { + _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) + return + } + // Pod Metrics from all namespaces + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/pods" { + if req.URL.Query().Get("labelSelector") == "app=pod-ns-5-42" { _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"30m","memory":"40Mi","swap":"37Mi"}}]}` + + `{"metadata":{"name":"pod-ns-5-42","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"42m","memory":"42Mi","swap":"42Mi"}}]}` + `]}`)) - return - } - // Pod Metrics from ns-5 namespace - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods" { + } else { _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + - `{"metadata":{"name":"pod-ns-5-1","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"42Mi"}}]}` + + `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"100m","memory":"200Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"200m","memory":"300Mi","swap":"37Mi"}}]},` + + `{"metadata":{"name":"pod-2","namespace":"ns-1"},"containers":[{"name":"container-1-ns-1","usage":{"cpu":"300m","memory":"400Mi","swap":"42Mi"}}]}` + `]}`)) - return - } - // Pod Metrics from ns-5 namespace with pod-ns-5-5 pod name - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods/pod-ns-5-5" { - _, _ = w.Write([]byte(`{"kind":"PodMetrics","apiVersion":"metrics.k8s.io/v1beta1",` + - `"metadata":{"name":"pod-ns-5-5","namespace":"ns-5"},` + - `"containers":[{"name":"container-1","usage":{"cpu":"13m","memory":"37Mi","swap":"42Mi"}}]` + - `}`)) - } - })) - podsTopDefaults, err := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_top defaults returns pod metrics from all namespaces", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopDefaults.Content[0].(mcp.TextContent).Text - if podsTopDefaults.IsError { - t.Fatalf("call tool failed %s", textContent) - } - expectedHeaders := regexp.MustCompile(`(?m)^\s*NAMESPACE\s+POD\s+NAME\s+CPU\(cores\)\s+MEMORY\(bytes\)\s+SWAP\(bytes\)\s*$`) - if !expectedHeaders.MatchString(textContent) { - t.Errorf("Expected headers '%s' not found in output:\n%s", expectedHeaders.String(), textContent) - } - expectedRows := []string{ - "default\\s+pod-1\\s+container-1\\s+100m\\s+200Mi\\s+13Mi", - "default\\s+pod-1\\s+container-2\\s+200m\\s+300Mi\\s+37Mi", - "ns-1\\s+pod-2\\s+container-1-ns-1\\s+300m\\s+400Mi\\s+42Mi", - } - for _, row := range expectedRows { - if !regexp.MustCompile(row).MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent) - } - } - expectedTotal := regexp.MustCompile(`(?m)^\s+600m\s+900Mi\s+92Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + } - }) - podsTopConfiguredNamespace, err := c.callTool("pods_top", map[string]interface{}{ + return + } + // Pod Metrics from configured namespace + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" { + _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + + `{"metadata":{"name":"pod-1","namespace":"default"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"13Mi"}},{"name":"container-2","usage":{"cpu":"30m","memory":"40Mi","swap":"37Mi"}}]}` + + `]}`)) + return + } + // Pod Metrics from ns-5 namespace + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods" { + _, _ = w.Write([]byte(`{"kind":"PodMetricsList","apiVersion":"metrics.k8s.io/v1beta1","items":[` + + `{"metadata":{"name":"pod-ns-5-1","namespace":"ns-5"},"containers":[{"name":"container-1","usage":{"cpu":"10m","memory":"20Mi","swap":"42Mi"}}]}` + + `]}`)) + return + } + // Pod Metrics from ns-5 namespace with pod-ns-5-5 pod name + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1/namespaces/ns-5/pods/pod-ns-5-5" { + _, _ = w.Write([]byte(`{"kind":"PodMetrics","apiVersion":"metrics.k8s.io/v1beta1",` + + `"metadata":{"name":"pod-ns-5-5","namespace":"ns-5"},` + + `"containers":[{"name":"container-1","usage":{"cpu":"13m","memory":"37Mi","swap":"42Mi"}}]` + + `}`)) + } + })) + s.InitMcpClient() + + s.Run("pods_top(defaults) returns pod metrics from all namespaces", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedHeaders := regexp.MustCompile(`(?m)^\s*NAMESPACE\s+POD\s+NAME\s+CPU\(cores\)\s+MEMORY\(bytes\)\s+SWAP\(bytes\)\s*$`) + s.Regexpf(expectedHeaders, textContent, "expected headers '%s' not found in output:\n%s", expectedHeaders.String(), textContent) + expectedRows := []string{ + "default\\s+pod-1\\s+container-1\\s+100m\\s+200Mi\\s+13Mi", + "default\\s+pod-1\\s+container-2\\s+200m\\s+300Mi\\s+37Mi", + "ns-1\\s+pod-2\\s+container-1-ns-1\\s+300m\\s+400Mi\\s+42Mi", + } + + for _, row := range expectedRows { + s.Regexpf(row, textContent, "expected row '%s' not found in output:\n%s", row, textContent) + } + + expectedTotal := regexp.MustCompile(`(?m)^\s+600m\s+900Mi\s+92Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(allNamespaces=false) returns pod metrics from configured namespace", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "all_namespaces": false, }) - t.Run("pods_top[allNamespaces=false] returns pod metrics from configured namespace", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopConfiguredNamespace.Content[0].(mcp.TextContent).Text - expectedRows := []string{ - "default\\s+pod-1\\s+container-1\\s+10m\\s+20Mi\\s+13Mi", - "default\\s+pod-1\\s+container-2\\s+30m\\s+40Mi\\s+37Mi", - } - for _, row := range expectedRows { - if !regexp.MustCompile(row).MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", row, textContent) - } - } - expectedTotal := regexp.MustCompile(`(?m)^\s+40m\s+60Mi\s+50Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespace, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRows := []string{ + "default\\s+pod-1\\s+container-1\\s+10m\\s+20Mi\\s+13Mi", + "default\\s+pod-1\\s+container-2\\s+30m\\s+40Mi\\s+37Mi", + } + for _, row := range expectedRows { + s.Regexpf(row, textContent, "expected row '%s' not found in output:\n%s", row, textContent) + } + + expectedTotal := regexp.MustCompile(`(?m)^\s+40m\s+60Mi\s+50Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(namespace=ns-5) returns pod metrics from provided namespace", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "namespace": "ns-5", }) - t.Run("pods_top[namespace=ns-5] returns pod metrics from provided namespace", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespace.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-1\s+container-1\s+10m\s+20Mi\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+10m\s+20Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespaceName, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-1\s+container-1\s+10m\s+20Mi\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+10m\s+20Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top(namespace=ns-5,name=pod-ns-5-5) returns pod metrics from provided namespace and name", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "namespace": "ns-5", "name": "pod-ns-5-5", }) - t.Run("pods_top[namespace=ns-5,name=pod-ns-5-5] returns pod metrics from provided namespace and name", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespaceName.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-5\s+container-1\s+13m\s+37Mi\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+13m\s+37Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) - podsTopNamespaceLabelSelector, err := c.callTool("pods_top", map[string]interface{}{ + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-5\s+container-1\s+13m\s+37Mi\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+13m\s+37Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) + }) + + s.Run("pods_top[label_selector=app=pod-ns-5-42] returns pod metrics from pods matching selector", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{ "label_selector": "app=pod-ns-5-42", }) - t.Run("pods_top[label_selector=app=pod-ns-5-42] returns pod metrics from pods matching selector", func(t *testing.T) { - if err != nil { - t.Fatalf("call tool failed %v", err) - } - textContent := podsTopNamespaceLabelSelector.Content[0].(mcp.TextContent).Text - expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-42\s+container-1\s+42m\s+42Mi`) - if !expectedRow.MatchString(textContent) { - t.Errorf("Expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) - } - expectedTotal := regexp.MustCompile(`(?m)^\s+42m\s+42Mi\s+42Mi\s*$`) - if !expectedTotal.MatchString(textContent) { - t.Errorf("Expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) - } - }) + s.Require().NotNil(result) + s.NoErrorf(err, "call tool failed %v", err) + textContent := result.Content[0].(mcp.TextContent).Text + s.Falsef(result.IsError, "call tool failed %v", textContent) + + expectedRow := regexp.MustCompile(`ns-5\s+pod-ns-5-42\s+container-1\s+42m\s+42Mi`) + s.Regexpf(expectedRow, textContent, "expected row '%s' not found in output:\n%s", expectedRow.String(), textContent) + + expectedTotal := regexp.MustCompile(`(?m)^\s+42m\s+42Mi\s+42Mi\s*$`) + s.Regexpf(expectedTotal, textContent, "expected total row '%s' not found in output:\n%s", expectedTotal.String(), textContent) }) } -func TestPodsTopDenied(t *testing.T) { - deniedResourcesServer := test.Must(config.ReadToml([]byte(` +func (s *PodsTopSuite) TestPodsTopDenied() { + s.Require().NoError(toml.Unmarshal([]byte(` denied_resources = [ { group = "metrics.k8s.io", version = "v1beta1" } ] - `))) - testCaseWithContext(t, &mcpContext{staticConfig: deniedResourcesServer}, func(c *mcpContext) { - mockServer := test.NewMockServer() - defer mockServer.Close() - c.withKubeConfig(mockServer.Config()) - mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json") - // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) - if req.URL.Path == "/api" { - _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Groups) - if req.URL.Path == "/apis" { - _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) - return - } - // Request Performed by DiscoveryClient to Kube API (Get API Resources) - if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { - _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) - return - } - })) - podsTop, _ := c.callTool("pods_top", map[string]interface{}{}) - t.Run("pods_run has error", func(t *testing.T) { - if !podsTop.IsError { - t.Fatalf("call tool should fail") - } + `), s.Cfg), "Expected to parse denied resources config") + s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-) + if req.URL.Path == "/api" { + _, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["metrics.k8s.io/v1beta1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Groups) + if req.URL.Path == "/apis" { + _, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`)) + return + } + // Request Performed by DiscoveryClient to Kube API (Get API Resources) + if req.URL.Path == "/apis/metrics.k8s.io/v1beta1" { + _, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","groupVersion":"metrics.k8s.io/v1beta1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"PodMetrics","verbs":["get","list"]}]}`)) + return + } + })) + s.InitMcpClient() + + s.Run("pods_top (denied)", func() { + result, err := s.CallTool("pods_top", map[string]interface{}{}) + s.Require().NotNil(result, "toolResult should not be nil") + s.Run("has error", func() { + s.Truef(result.IsError, "call tool should fail") + s.Nilf(err, "call tool should not return error object") }) - t.Run("pods_run describes denial", func(t *testing.T) { + s.Run("describes denial", func() { expectedMessage := "failed to get pods top: resource not allowed: metrics.k8s.io/v1beta1, Kind=PodMetrics" - if podsTop.Content[0].(mcp.TextContent).Text != expectedMessage { - t.Fatalf("expected descriptive error '%s', got %v", expectedMessage, podsTop.Content[0].(mcp.TextContent).Text) - } + s.Equalf(expectedMessage, result.Content[0].(mcp.TextContent).Text, + "expected descriptive error '%s', got %v", expectedMessage, result.Content[0].(mcp.TextContent).Text) }) }) } + +func TestPodsTop(t *testing.T) { + suite.Run(t, new(PodsTopSuite)) +}