Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions internal/test/mock_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,33 @@ WaitForStreams:
return ctx, nil
}

type DiscoveryClientHandler struct{}

var _ http.Handler = (*DiscoveryClientHandler)(nil)

func (h *DiscoveryClientHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)
if req.URL.Path == "/api" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`))
return
}
// Request Performed by DiscoveryClient to Kube API (Get API Groups)
if req.URL.Path == "/apis" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`))
return
}
// Request Performed by DiscoveryClient to Kube API (Get API Resources)
if req.URL.Path == "/api/v1" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","resources":[
{"name":"pods","singularName":"","namespaced":true,"kind":"Pod","verbs":["get","list","watch","create","update","patch","delete"]}
]}`))
return
}
}

type InOpenShiftHandler struct {
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/mcp/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ func (s *EventsSuite) TestEventsListDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to list events in all namespaces: resource not allowed: /v1, Kind=Event"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to list events in all namespaces:(.+:)? resource not allowed: /v1, Kind=Event"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
10 changes: 6 additions & 4 deletions pkg/mcp/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ func (s *HelmSuite) TestHelmInstallDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
s.Truef(strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "failed to install helm chart"), "expected descriptive error, got %v", toolResult.Content[0].(mcp.TextContent).Text)
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
s.Truef(strings.HasPrefix(msg, "failed to install helm chart"), "expected descriptive error, got %v", toolResult.Content[0].(mcp.TextContent).Text)
expectedMessage := ": resource not allowed: /v1, Kind=Secret"
s.Truef(strings.HasSuffix(toolResult.Content[0].(mcp.TextContent).Text, expectedMessage), "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
s.Truef(strings.HasSuffix(msg, expectedMessage), "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
}
Expand Down Expand Up @@ -260,8 +262,8 @@ func (s *HelmSuite) TestHelmUninstallDenied() {
})
s.Run("describes denial", func() {
s.T().Skipf("Helm won't report what underlying resource caused the failure, so we can't assert on it")
expectedMessage := "failed to uninstall release: resource not allowed: /v1, Kind=Secret"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
expectedMessage := "failed to uninstall release:(.+:)? resource not allowed: /v1, Kind=Secret"
s.Regexpf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text, "expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
}
Expand Down
22 changes: 3 additions & 19 deletions pkg/mcp/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,9 @@ func (s *McpHeadersSuite) SetupTest() {
s.pathHeaders = make(map[string]http.Header)
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
s.pathHeaders[req.URL.Path] = req.Header.Clone()
// Request Performed by DiscoveryClient to Kube API (Get API Groups legacy -core-)
if req.URL.Path == "/api" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0"}]}`))
return
}
// Request Performed by DiscoveryClient to Kube API (Get API Groups)
if req.URL.Path == "/apis" {
w.Header().Set("Content-Type", "application/json")
//w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[{"name":"apps","versions":[{"groupVersion":"apps/v1","version":"v1"}],"preferredVersion":{"groupVersion":"apps/v1","version":"v1"}}]}`))
_, _ = w.Write([]byte(`{"kind":"APIGroupList","apiVersion":"v1","groups":[]}`))
return
}
// Request Performed by DiscoveryClient to Kube API (Get API Resources)
if req.URL.Path == "/api/v1" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"kind":"APIResourceList","apiVersion":"v1","resources":[{"name":"pods","singularName":"","namespaced":true,"kind":"Pod","verbs":["get","list","watch","create","update","patch","delete"]}]}`))
return
}
}))
s.mockServer.Handle(&test.DiscoveryClientHandler{})
s.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Request Performed by DynamicClient
if req.URL.Path == "/api/v1/namespaces/default/pods" {
w.Header().Set("Content-Type", "application/json")
Expand Down
12 changes: 8 additions & 4 deletions pkg/mcp/namespaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ func (s *NamespacesSuite) TestNamespacesListDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to list namespaces: resource not allowed: /v1, Kind=Namespace"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to list namespaces:(.+:)? resource not allowed: /v1, Kind=Namespace"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -159,8 +161,10 @@ func (s *NamespacesSuite) TestProjectsListInOpenShiftDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to list projects: resource not allowed: project.openshift.io/v1, Kind=Project"
s.Equalf(expectedMessage, projectsList.Content[0].(mcp.TextContent).Text,
msg := projectsList.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to list projects:(.+:)? resource not allowed: project.openshift.io/v1, Kind=Project"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, projectsList.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
12 changes: 8 additions & 4 deletions pkg/mcp/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (s *NodesSuite) TestNodesLog() {
})
s.Run("describes missing name", func() {
expectedMessage := "failed to get node log, missing argument query"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
s.Regexpf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -215,8 +215,10 @@ func (s *NodesSuite) TestNodesLogDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get node log for does-not-matter: resource not allowed: /v1, Kind=Node"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
s.Equalf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -324,8 +326,10 @@ func (s *NodesSuite) TestNodesStatsSummaryDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to get node stats summary for does-not-matter: resource not allowed: /v1, Kind=Node"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get node stats summary for does-not-matter:(.+:)? resource not allowed: /v1, Kind=Node"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
6 changes: 4 additions & 2 deletions pkg/mcp/nodes_top_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,10 @@ func (s *NodesTopSuite) TestNodesTopDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to get nodes top: resource not allowed: metrics.k8s.io/v1beta1, Kind=NodeMetrics"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get nodes top:(.+:)? resource not allowed: metrics.k8s.io/v1beta1, Kind=NodeMetrics"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
6 changes: 4 additions & 2 deletions pkg/mcp/pods_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ func (s *PodsExecSuite) TestPodsExecDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to exec in pod pod-to-exec in namespace default: resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, toolResult.Content[0].(mcp.TextContent).Text,
msg := toolResult.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to exec in pod pod-to-exec in namespace default:(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, toolResult.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
6 changes: 4 additions & 2 deletions pkg/mcp/pods_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ func (s *PodsRunSuite) TestPodsRunDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to run pod in namespace : resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsRun.Content[0].(mcp.TextContent).Text,
msg := podsRun.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to run pod in namespace :(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsRun.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
30 changes: 20 additions & 10 deletions pkg/mcp/pods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ func (s *PodsSuite) TestPodsListDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to list pods in all namespaces: resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsList.Content[0].(mcp.TextContent).Text,
msg := podsList.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to list pods in all namespaces:(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsList.Content[0].(mcp.TextContent).Text)
})
})
Expand All @@ -174,8 +176,10 @@ func (s *PodsSuite) TestPodsListDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to list pods in namespace ns-1: resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text,
msg := podsListInNamespace.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to list pods in namespace ns-1:(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsListInNamespace.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -346,8 +350,10 @@ func (s *PodsSuite) TestPodsGetDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to get pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsGet.Content[0].(mcp.TextContent).Text,
msg := podsGet.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get pod a-pod-in-default in namespace :(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsGet.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -447,8 +453,10 @@ func (s *PodsSuite) TestPodsDeleteDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to delete pod a-pod-in-default in namespace : resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text,
msg := podsDelete.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to delete pod a-pod-in-default in namespace :(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsDelete.Content[0].(mcp.TextContent).Text)
})
})
Expand Down Expand Up @@ -599,8 +607,10 @@ func (s *PodsSuite) TestPodsLogDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to get pod a-pod-in-default log in namespace : resource not allowed: /v1, Kind=Pod"
s.Equalf(expectedMessage, podsLog.Content[0].(mcp.TextContent).Text,
msg := podsLog.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get pod a-pod-in-default log in namespace :(.+:)? resource not allowed: /v1, Kind=Pod"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, podsLog.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
20 changes: 5 additions & 15 deletions pkg/mcp/pods_top_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,7 @@ func (s *PodsTopSuite) TearDownTest() {
}

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.mockServer.Handle(&test.DiscoveryClientHandler{})
s.InitMcpClient()

s.Run("pods_top with metrics API not available", func() {
Expand Down Expand Up @@ -238,8 +226,10 @@ func (s *PodsTopSuite) TestPodsTopDenied() {
s.Nilf(err, "call tool should not return error object")
})
s.Run("describes denial", func() {
expectedMessage := "failed to get pods top: resource not allowed: metrics.k8s.io/v1beta1, Kind=PodMetrics"
s.Equalf(expectedMessage, result.Content[0].(mcp.TextContent).Text,
msg := result.Content[0].(mcp.TextContent).Text
s.Contains(msg, "resource not allowed:")
expectedMessage := "failed to get pods top:(.+:)? resource not allowed: metrics.k8s.io/v1beta1, Kind=PodMetrics"
s.Regexpf(expectedMessage, msg,
"expected descriptive error '%s', got %v", expectedMessage, result.Content[0].(mcp.TextContent).Text)
})
})
Expand Down
Loading