Skip to content
Open
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
35 changes: 15 additions & 20 deletions pkg/toolsets/netedge/coredns.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import (
"fmt"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
"github.com/google/jsonschema-go/jsonschema"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func initCoreDNS() []api.ServerTool {
Expand All @@ -34,30 +32,27 @@ func initCoreDNS() []api.ServerTool {
}
}

// newClientFunc is the function used to create a controller-runtime client.
// It is a variable to allow overriding in tests.
var newClientFunc = func(config *rest.Config, options client.Options) (client.Client, error) {
return client.New(config, options)
}

func getCoreDNSConfig(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
cfg := params.RESTConfig()
if cfg == nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get REST config")), nil
gvr := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "configmaps",
}

cl, err := newClientFunc(cfg, client.Options{Scheme: kubernetes.Scheme})
cm, err := params.DynamicClient().Resource(gvr).Namespace("openshift-dns").Get(params.Context, "dns-default", metav1.GetOptions{})
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to create controller-runtime client: %w", err)), nil
return api.NewToolCallResult("", fmt.Errorf("failed to get dns-default ConfigMap: %w", err)), nil
}

cm := &corev1.ConfigMap{}
err = cl.Get(params.Context, types.NamespacedName{Name: "dns-default", Namespace: "openshift-dns"}, cm)
data, found, err := unstructured.NestedStringMap(cm.Object, "data")
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get dns-default ConfigMap: %w", err)), nil
return api.NewToolCallResult("", fmt.Errorf("failed to parse ConfigMap data: %w", err)), nil
}
if !found {
return api.NewToolCallResult("", fmt.Errorf("ConfigMap has no data")), nil
}

corefile, ok := cm.Data["Corefile"]
corefile, ok := data["Corefile"]
if !ok {
return api.NewToolCallResult("", fmt.Errorf("corefile not found in dns-default ConfigMap")), nil
}
Expand Down
61 changes: 20 additions & 41 deletions pkg/toolsets/netedge/coredns_test.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,15 @@
package netedge

import (
"context"
"testing"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"k8s.io/client-go/dynamic/fake"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
)

// mockKubernetesClient implements api.KubernetesClient for testing
type mockKubernetesClient struct {
api.KubernetesClient
restConfig *rest.Config
}

func (m *mockKubernetesClient) RESTConfig() *rest.Config {
return m.restConfig
}
func (s *NetEdgeTestSuite) TestGetCoreDNSConfig() {

func TestGetCoreDNSConfig(t *testing.T) {
tests := []struct {
name string
configMap *corev1.ConfigMap
Expand Down Expand Up @@ -72,39 +56,34 @@ func TestGetCoreDNSConfig(t *testing.T) {
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s.Run(tt.name, func() {
// Setup mock client
objs := []runtime.Object{}
if tt.configMap != nil {
objs = append(objs, tt.configMap)
}

// Override newClientFunc to return fake client
originalNewClientFunc := newClientFunc
defer func() { newClientFunc = originalNewClientFunc }()
// Setup fake dynamic client
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)

newClientFunc = func(config *rest.Config, options client.Options) (client.Client, error) {
return fake.NewClientBuilder().WithRuntimeObjects(objs...).Build(), nil
}

// Call handler
params := api.ToolHandlerParams{
Context: context.Background(),
KubernetesClient: &mockKubernetesClient{restConfig: &rest.Config{}},
}
// Create a fake dynamic client with the objects
dynClient := fake.NewSimpleDynamicClient(scheme, objs...)
s.SetDynamicClient(dynClient)

result, err := getCoreDNSConfig(params)
// Call handler using suite params
result, err := getCoreDNSConfig(s.params)

if tt.expectError {
require.NoError(t, err) // Handler returns error in result, not as return value
require.NotNil(t, result)
require.Error(t, result.Error)
assert.Contains(t, result.Error.Error(), tt.errorContains)
s.Require().NoError(err) // Handler returns error in result, not as return value
s.Require().NotNil(result)
s.Require().Error(result.Error)
s.Assert().Contains(result.Error.Error(), tt.errorContains)
} else {
require.NoError(t, err)
require.NotNil(t, result)
require.NoError(t, result.Error)
assert.Equal(t, tt.expectedOutput, result.Content)
s.Require().NoError(err)
s.Require().NotNil(result)
s.Require().NoError(result.Error)
s.Assert().Equal(tt.expectedOutput, result.Content)
}
})
}
Expand Down
66 changes: 66 additions & 0 deletions pkg/toolsets/netedge/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package netedge

import (
"context"
"testing"

"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/stretchr/testify/suite"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)

// mockKubernetesClient implements api.KubernetesClient for testing
type mockKubernetesClient struct {
api.KubernetesClient
restConfig *rest.Config
dynamicClient dynamic.Interface
}

func (m *mockKubernetesClient) RESTConfig() *rest.Config {
return m.restConfig
}

func (m *mockKubernetesClient) DynamicClient() dynamic.Interface {
return m.dynamicClient
}

type NetEdgeTestSuite struct {
suite.Suite
params api.ToolHandlerParams
mockReq *mockToolCallRequest
mockClient *mockKubernetesClient
}

func (s *NetEdgeTestSuite) SetupTest() {
s.mockReq = &mockToolCallRequest{args: make(map[string]interface{})}
s.mockClient = &mockKubernetesClient{
restConfig: &rest.Config{},
}
s.params = api.ToolHandlerParams{
Context: context.Background(),
ToolCallRequest: s.mockReq,
KubernetesClient: s.mockClient,
}
}

func (s *NetEdgeTestSuite) SetArgs(args map[string]interface{}) {
s.mockReq.args = args
}

func (s *NetEdgeTestSuite) SetDynamicClient(dynClient dynamic.Interface) {
s.mockClient.dynamicClient = dynClient
s.params.KubernetesClient = s.mockClient
}

func TestNetEdgeSuite(t *testing.T) {
suite.Run(t, new(NetEdgeTestSuite))
}

type mockToolCallRequest struct {
args map[string]any
}

func (m *mockToolCallRequest) GetArguments() map[string]any {
return m.args
}