From b153a79787fe7e0da6b3dcb0ce05ac970c89c004 Mon Sep 17 00:00:00 2001 From: Alberto Gutierrez Date: Fri, 28 Nov 2025 16:23:08 +0100 Subject: [PATCH 1/2] feat(kiali): Keep defaults in a file Signed-off-by: Alberto Gutierrez --- pkg/kiali/defaults.go | 25 ++- pkg/kiali/graph.go | 30 +-- pkg/kiali/mesh.go | 4 +- pkg/toolsets/kiali/get_mesh_graph.go | 16 +- pkg/toolsets/kiali/get_metrics.go | 33 +-- pkg/toolsets/kiali/get_traces.go | 31 +-- pkg/toolsets/kiali/helpers.go | 135 ++++++++++++ pkg/toolsets/kiali/helpers_test.go | 294 +++++++++++++++++++++++++++ 8 files changed, 497 insertions(+), 71 deletions(-) create mode 100644 pkg/toolsets/kiali/helpers.go create mode 100644 pkg/toolsets/kiali/helpers_test.go diff --git a/pkg/kiali/defaults.go b/pkg/kiali/defaults.go index d9c1af69..34ada937 100644 --- a/pkg/kiali/defaults.go +++ b/pkg/kiali/defaults.go @@ -4,5 +4,28 @@ package kiali const ( // DefaultRateInterval is the default rate interval for fetching error rates and metrics. // This value is used when rateInterval is not explicitly provided in API calls. - DefaultRateInterval = "10m" + DefaultRateInterval = "10m" + DefaultGraphType = "versionedApp" + DefaultDuration = "30m" + DefaultStep = "15" + DefaultDirection = "outbound" + DefaultReporter = "source" + DefaultRequestProtocol = "http" + DefaultQuantiles = "0.5,0.95,0.99,0.999" + DefaultLimit = "100" + DefaultTail = "100" + + // Default graph parameters + DefaultIncludeIdleEdges = "false" + DefaultInjectServiceNodes = "true" + DefaultBoxBy = "cluster,namespace,app" + DefaultAmbientTraffic = "none" + DefaultAppenders = "deadNode,istio,serviceEntry,meshCheck,workloadEntry,health" + DefaultRateGrpc = "requests" + DefaultRateHttp = "requests" + DefaultRateTcp = "sent" + + // Default mesh status parameters + DefaultIncludeGateways = "false" + DefaultIncludeWaypoints = "false" ) diff --git a/pkg/kiali/graph.go b/pkg/kiali/graph.go index b7a2c717..3e83a653 100644 --- a/pkg/kiali/graph.go +++ b/pkg/kiali/graph.go @@ -16,26 +16,16 @@ func (k *Kiali) Graph(ctx context.Context, namespaces []string, queryParams map[ return "", err } q := u.Query() - // Static graph parameters per requirements - // Defaults with optional overrides via queryParams - duration := DefaultRateInterval - graphType := "versionedApp" - if v, ok := queryParams["rateInterval"]; ok && strings.TrimSpace(v) != "" { - duration = strings.TrimSpace(v) - } - if v, ok := queryParams["graphType"]; ok && strings.TrimSpace(v) != "" { - graphType = strings.TrimSpace(v) - } - q.Set("duration", duration) - q.Set("graphType", graphType) - q.Set("includeIdleEdges", "false") - q.Set("injectServiceNodes", "true") - q.Set("boxBy", "cluster,namespace,app") - q.Set("ambientTraffic", "none") - q.Set("appenders", "deadNode,istio,serviceEntry,meshCheck,workloadEntry,health") - q.Set("rateGrpc", "requests") - q.Set("rateHttp", "requests") - q.Set("rateTcp", "sent") + q.Set("duration", queryParams["rateInterval"]) + q.Set("graphType", queryParams["graphType"]) + q.Set("includeIdleEdges", DefaultIncludeIdleEdges) + q.Set("injectServiceNodes", DefaultInjectServiceNodes) + q.Set("boxBy", DefaultBoxBy) + q.Set("ambientTraffic", DefaultAmbientTraffic) + q.Set("appenders", DefaultAppenders) + q.Set("rateGrpc", DefaultRateGrpc) + q.Set("rateHttp", DefaultRateHttp) + q.Set("rateTcp", DefaultRateTcp) // Optional namespaces param cleaned := make([]string, 0, len(namespaces)) for _, ns := range namespaces { diff --git a/pkg/kiali/mesh.go b/pkg/kiali/mesh.go index c9043791..e18d7a66 100644 --- a/pkg/kiali/mesh.go +++ b/pkg/kiali/mesh.go @@ -15,8 +15,8 @@ func (k *Kiali) MeshStatus(ctx context.Context) (string, error) { return "", err } q := u.Query() - q.Set("includeGateways", "false") - q.Set("includeWaypoints", "false") + q.Set("includeGateways", DefaultIncludeGateways) + q.Set("includeWaypoints", DefaultIncludeWaypoints) u.RawQuery = q.Encode() return k.executeRequest(ctx, http.MethodGet, u.String(), "", nil) } diff --git a/pkg/toolsets/kiali/get_mesh_graph.go b/pkg/toolsets/kiali/get_mesh_graph.go index c2f8e6ab..682ed3fb 100644 --- a/pkg/toolsets/kiali/get_mesh_graph.go +++ b/pkg/toolsets/kiali/get_mesh_graph.go @@ -30,11 +30,13 @@ func initGetMeshGraph() []api.ServerTool { }, "rateInterval": { Type: "string", - Description: "Rate interval for fetching (e.g., '10m', '5m', '1h'). Default: '10m'", + Description: "Rate interval for fetching (e.g., '10m', '5m', '1h').", + Default: api.ToRawMessage(kialiclient.DefaultRateInterval), }, "graphType": { Type: "string", - Description: "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'. Default: 'versionedApp'", + Description: "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'", + Default: api.ToRawMessage(kialiclient.DefaultGraphType), }, }, Required: []string{}, @@ -89,13 +91,11 @@ func getMeshGraphHandler(params api.ToolHandlerParams) (*api.ToolCallResult, err // Extract optional query parameters queryParams := make(map[string]string) - rateInterval := kialiclient.DefaultRateInterval // default - if v, ok := params.GetArguments()["rateInterval"].(string); ok && v != "" { - rateInterval = v + if err := setQueryParam(params, queryParams, "rateInterval", kialiclient.DefaultRateInterval); err != nil { + return api.NewToolCallResult("", err), nil } - queryParams["rateInterval"] = rateInterval - if graphType, ok := params.GetArguments()["graphType"].(string); ok && graphType != "" { - queryParams["graphType"] = graphType + if err := setQueryParam(params, queryParams, "graphType", kialiclient.DefaultGraphType); err != nil { + return api.NewToolCallResult("", err), nil } k := params.NewKiali() content, err := k.GetMeshGraph(params.Context, namespaces, queryParams) diff --git a/pkg/toolsets/kiali/get_metrics.go b/pkg/toolsets/kiali/get_metrics.go index a67000c0..a34bc90b 100644 --- a/pkg/toolsets/kiali/get_metrics.go +++ b/pkg/toolsets/kiali/get_metrics.go @@ -57,23 +57,27 @@ func initGetMetrics() []api.ServerTool { }, "duration": { Type: "string", - Description: "Time range to get metrics for (optional string - if provided, gets metrics; if empty, get default 1800s).", + Description: "Time range to get metrics for (optional string - if provided, gets metrics (e.g., '1m', '5m', '1h'); if empty, get default 30m).", Default: api.ToRawMessage(kialiclient.DefaultDuration), }, "step": { Type: "string", Description: "Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds", + Default: api.ToRawMessage(kialiclient.DefaultStep), }, "rateInterval": { Type: "string", Description: "Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '10m'", + Default: api.ToRawMessage(kialiclient.DefaultRateInterval), }, "direction": { Type: "string", Description: "Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'", + Default: api.ToRawMessage(kialiclient.DefaultDirection), }, "reporter": { Type: "string", Description: "Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'", + Default: api.ToRawMessage(kialiclient.DefaultReporter), }, "requestProtocol": { Type: "string", @@ -82,6 +86,7 @@ func initGetMetrics() []api.ServerTool { "quantiles": { Type: "string", Description: "Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional", + Default: api.ToRawMessage(kialiclient.DefaultQuantiles), }, "byLabels": { Type: "string", @@ -129,28 +134,26 @@ func resourceMetricsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, } queryParams := make(map[string]string) - if duration, ok := params.GetArguments()["duration"].(string); ok && duration != "" { - queryParams["duration"] = duration + if err := setQueryParam(params, queryParams, "duration", kialiclient.DefaultDuration); err != nil { + return api.NewToolCallResult("", err), nil } - if step, ok := params.GetArguments()["step"].(string); ok && step != "" { - queryParams["step"] = step + if err := setQueryParam(params, queryParams, "step", kialiclient.DefaultStep); err != nil { + return api.NewToolCallResult("", err), nil } - rateInterval := kialiclient.DefaultRateInterval // default - if v, ok := params.GetArguments()["rateInterval"].(string); ok && v != "" { - rateInterval = v + if err := setQueryParam(params, queryParams, "rateInterval", kialiclient.DefaultRateInterval); err != nil { + return api.NewToolCallResult("", err), nil } - queryParams["rateInterval"] = rateInterval - if direction, ok := params.GetArguments()["direction"].(string); ok && direction != "" { - queryParams["direction"] = direction + if err := setQueryParam(params, queryParams, "direction", kialiclient.DefaultDirection); err != nil { + return api.NewToolCallResult("", err), nil } - if reporter, ok := params.GetArguments()["reporter"].(string); ok && reporter != "" { - queryParams["reporter"] = reporter + if err := setQueryParam(params, queryParams, "reporter", kialiclient.DefaultReporter); err != nil { + return api.NewToolCallResult("", err), nil } if requestProtocol, ok := params.GetArguments()["requestProtocol"].(string); ok && requestProtocol != "" { queryParams["requestProtocol"] = requestProtocol } - if quantiles, ok := params.GetArguments()["quantiles"].(string); ok && quantiles != "" { - queryParams["quantiles"] = quantiles + if err := setQueryParam(params, queryParams, "quantiles", kialiclient.DefaultQuantiles); err != nil { + return api.NewToolCallResult("", err), nil } if byLabels, ok := params.GetArguments()["byLabels"].(string); ok && byLabels != "" { queryParams["byLabels"] = byLabels diff --git a/pkg/toolsets/kiali/get_traces.go b/pkg/toolsets/kiali/get_traces.go index 4d3080bb..a6ce6531 100644 --- a/pkg/toolsets/kiali/get_traces.go +++ b/pkg/toolsets/kiali/get_traces.go @@ -79,6 +79,7 @@ func initGetTraces() []api.ServerTool { Type: "integer", Description: "Maximum number of traces to return (default: 100, only used when traceId is not provided)", Minimum: ptr.To(float64(1)), + Default: api.ToRawMessage(kialiclient.DefaultLimit), }, "minDuration": { Type: "integer", @@ -175,34 +176,14 @@ func TracesHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { endMicros = strconv.FormatInt(endTime.UnixMicro(), 10) } queryParams["endMicros"] = endMicros - // Handle limit: convert integer to string if provided - if limit := params.GetArguments()["limit"]; limit != nil { - switch v := limit.(type) { - case float64: - queryParams["limit"] = fmt.Sprintf("%.0f", v) - case int: - queryParams["limit"] = fmt.Sprintf("%d", v) - case int64: - queryParams["limit"] = fmt.Sprintf("%d", v) - case string: - if v != "" { - queryParams["limit"] = v - } - } + if err := setQueryParam(params, queryParams, "limit", kialiclient.DefaultLimit); err != nil { + return api.NewToolCallResult("", err), nil } + // Handle minDuration: convert integer to string if provided if minDuration := params.GetArguments()["minDuration"]; minDuration != nil { - switch v := minDuration.(type) { - case float64: - queryParams["minDuration"] = fmt.Sprintf("%.0f", v) - case int: - queryParams["minDuration"] = fmt.Sprintf("%d", v) - case int64: - queryParams["minDuration"] = fmt.Sprintf("%d", v) - case string: - if v != "" { - queryParams["minDuration"] = v - } + if err := setQueryParam(params, queryParams, "minDuration", ""); err != nil { + return api.NewToolCallResult("", err), nil } } if tags, ok := params.GetArguments()["tags"].(string); ok && tags != "" { diff --git a/pkg/toolsets/kiali/helpers.go b/pkg/toolsets/kiali/helpers.go new file mode 100644 index 00000000..81a20f2a --- /dev/null +++ b/pkg/toolsets/kiali/helpers.go @@ -0,0 +1,135 @@ +package kiali + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/containers/kubernetes-mcp-server/pkg/api" +) + +// getStringArgOrDefault returns the string argument value for the given key, +// or the provided default if the argument is absent or empty (after trimming). +// If key is "duration" or "rateInterval" and the provided value is a string, +// it is parsed via rateIntervalToSeconds and returned in seconds. +func getStringArgOrDefault(params api.ToolHandlerParams, key, defaultVal string) (string, error) { + if raw, ok := params.GetArguments()[key]; ok { + // Special handling for time-like keys that can be specified with suffixes + if key == "duration" || key == "rateInterval" { + if v, ok := raw.(string); ok { + s := strings.TrimSpace(v) + if s != "" { + secs, err := rateIntervalToSeconds(s) + if err != nil { + return "", err + } + return strconv.FormatInt(secs, 10), nil + } + // if empty string, treat as missing and fall through to default handling + } + } + + switch v := raw.(type) { + case string: + s := strings.TrimSpace(v) + if s != "" { + return s, nil + } + case float64: + // JSON numbers decode to float64 in maps; use -1 precision to trim trailing zeros + return strconv.FormatFloat(v, 'f', -1, 64), nil + case int: + return strconv.Itoa(v), nil + case int64: + return strconv.FormatInt(v, 10), nil + case json.Number: + return v.String(), nil + default: + s := strings.TrimSpace(fmt.Sprint(v)) + if s != "" { + return s, nil + } + } + } + // When missing or empty, for special keys also convert the default + if key == "duration" || key == "rateInterval" { + secs, err := rateIntervalToSeconds(defaultVal) + if err != nil { + return "", err + } + return strconv.FormatInt(secs, 10), nil + } + return defaultVal, nil +} + +// rateIntervalToSeconds converts a rate interval string (e.g., "10m", "5h", "2d", "30s", "15") +// into its equivalent duration in seconds. +// Accepted suffixes: +// - no suffix: seconds (e.g., "15" => 15) +// - s: seconds (e.g., "30s" => 30) +// - m: minutes (e.g., "10m" => 600) +// - h: hours (e.g., "1h" => 3600) +// - d: days (e.g., "2d" => 172800) +// +// Any other suffix returns an error. +func rateIntervalToSeconds(input string) (int64, error) { + s := strings.TrimSpace(input) + if s == "" { + return 0, fmt.Errorf("rateInterval/duration is empty") + } + + last := s[len(s)-1] + var multiplier int64 = 1 + var numberPart string + + switch last { + case 's': + multiplier = 1 + numberPart = strings.TrimSpace(s[:len(s)-1]) + case 'm': + multiplier = 60 + numberPart = strings.TrimSpace(s[:len(s)-1]) + case 'h': + multiplier = 60 * 60 + numberPart = strings.TrimSpace(s[:len(s)-1]) + case 'd': + multiplier = 24 * 60 * 60 + numberPart = strings.TrimSpace(s[:len(s)-1]) + default: + // If last char is a digit, treat as seconds with no suffix + if last >= '0' && last <= '9' { + numberPart = s + } else { + return 0, fmt.Errorf("invalid rateInterval/duration suffix: %q", string(last)) + } + } + + if numberPart == "" { + return 0, fmt.Errorf("missing numeric value in rateInterval/duration") + } + + // Only accept integer values + n, err := strconv.ParseInt(numberPart, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid numeric value in rateInterval/duration: %w", err) + } + + return n * multiplier, nil +} + +// setQueryParam sets queryParams[key] from tool arguments (with default handling). +// It uses getStringArgOrDefault and wraps errors with a useful message depending on the key. +func setQueryParam(params api.ToolHandlerParams, queryParams map[string]string, key, defaultVal string) error { + v, err := getStringArgOrDefault(params, key, defaultVal) + if err != nil { + switch key { + case "duration", "rateInterval": + return fmt.Errorf("invalid %s: %v, values must be in the format '10m', '5m', '1h', '2d' or seconds", key, err) + default: + return fmt.Errorf("invalid %s: %v", key, err) + } + } + queryParams[key] = v + return nil +} diff --git a/pkg/toolsets/kiali/helpers_test.go b/pkg/toolsets/kiali/helpers_test.go new file mode 100644 index 00000000..3eb704ad --- /dev/null +++ b/pkg/toolsets/kiali/helpers_test.go @@ -0,0 +1,294 @@ +package kiali + +import ( + "encoding/json" + "testing" + + "github.com/containers/kubernetes-mcp-server/pkg/api" +) + +type mockToolCallRequest struct { + args map[string]any +} + +func (m *mockToolCallRequest) GetArguments() map[string]any { + return m.args +} + +func TestGetStringArgOrDefault(t *testing.T) { + tests := []struct { + name string + args map[string]any + key string + defaultVal string + want string + wantErr bool + }{ + { + name: "rateInterval trims and converts to seconds", + args: map[string]any{"rateInterval": " 5m "}, + key: "rateInterval", + defaultVal: "10m", + want: "300", + wantErr: false, + }, + { + name: "rateInterval empty returns default converted to seconds", + args: map[string]any{"rateInterval": ""}, + key: "rateInterval", + defaultVal: "10m", + want: "600", + wantErr: false, + }, + { + name: "rateInterval invalid suffix returns error", + args: map[string]any{"rateInterval": "5x"}, + key: "rateInterval", + defaultVal: "10m", + want: "", + wantErr: true, + }, + { + name: "returns default when string is whitespace", + args: map[string]any{"graphType": " "}, + key: "graphType", + defaultVal: "app", + want: "app", + wantErr: false, + }, + { + name: "returns default when key missing", + args: map[string]any{}, + key: "missing", + defaultVal: "fallback", + want: "fallback", + wantErr: false, + }, + { + name: "converts float64 without trailing zeros", + args: map[string]any{"step": 10.0}, + key: "step", + defaultVal: "15", + want: "10", + wantErr: false, + }, + { + name: "converts float64 with decimals", + args: map[string]any{"step": 10.5}, + key: "step", + defaultVal: "15", + want: "10.5", + wantErr: false, + }, + { + name: "converts int", + args: map[string]any{"duration": 1800}, + key: "duration", + defaultVal: "3600", + want: "1800", + wantErr: false, + }, + { + name: "converts int64", + args: map[string]any{"duration": int64(7200)}, + key: "duration", + defaultVal: "3600", + want: "7200", + wantErr: false, + }, + { + name: "converts json.Number", + args: map[string]any{"duration": json.Number("300")}, + key: "duration", + defaultVal: "3600", + want: "300", + wantErr: false, + }, + { + name: "formats other types via Sprint", + args: map[string]any{"flag": true}, + key: "flag", + defaultVal: "false", + want: "true", + wantErr: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + params := api.ToolHandlerParams{ + ToolCallRequest: &mockToolCallRequest{args: tt.args}, + } + got, err := getStringArgOrDefault(params, tt.key, tt.defaultVal) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error but got nil") + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Fatalf("getStringArgOrDefault() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestRateIntervalToSeconds(t *testing.T) { + tests := []struct { + in string + want int64 + wantErr bool + testName string + }{ + {in: "10m", want: 600, wantErr: false, testName: "minutes"}, + {in: "10", want: 10, wantErr: false, testName: "no suffix seconds"}, + {in: "10s", want: 10, wantErr: false, testName: "seconds suffix"}, + {in: "1h", want: 3600, wantErr: false, testName: "hours"}, + {in: "2d", want: 172800, wantErr: false, testName: "days"}, + {in: " 5m ", want: 300, wantErr: false, testName: "trim spaces"}, + {in: "0m", want: 0, wantErr: false, testName: "zero value"}, + {in: "", want: 0, wantErr: true, testName: "empty"}, + {in: "m", want: 0, wantErr: true, testName: "missing number"}, + {in: "5x", want: 0, wantErr: true, testName: "invalid suffix"}, + {in: "10.5m", want: 0, wantErr: true, testName: "decimal not allowed"}, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.testName, func(t *testing.T) { + got, err := rateIntervalToSeconds(tt.in) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error, got nil (input=%q, got=%d)", tt.in, got) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tt.want { + t.Fatalf("rateIntervalToSeconds(%q) = %d, want %d", tt.in, got, tt.want) + } + }) + } +} + +func TestSetQueryParam(t *testing.T) { + type args struct { + key string + defaultVal string + errMsg string + args map[string]any + } + tests := []struct { + name string + in args + wantKey string + wantValue string + wantErr string + }{ + { + name: "sets string value from args", + in: args{ + key: "step", + defaultVal: "15", + errMsg: "invalid step", + args: map[string]any{"step": "30"}, + }, + wantKey: "step", + wantValue: "30", + }, + { + name: "uses default for missing non-special key", + in: args{ + key: "reporter", + defaultVal: "source", + errMsg: "invalid reporter", + args: map[string]any{}, + }, + wantKey: "reporter", + wantValue: "source", + }, + { + name: "converts special default duration", + in: args{ + key: "duration", + defaultVal: "10m", + errMsg: "invalid duration", + args: map[string]any{}, + }, + wantKey: "duration", + wantValue: "600", + }, + { + name: "converts special provided duration", + in: args{ + key: "duration", + defaultVal: "10m", + errMsg: "invalid duration", + args: map[string]any{"duration": "2h"}, + }, + wantKey: "duration", + wantValue: "7200", + }, + { + name: "returns custom error for invalid rateInterval", + in: args{ + key: "rateInterval", + defaultVal: "10m", + errMsg: "invalid rateInterval: invalid rateInterval/duration suffix: \"x\", values must be in the format '10m', '5m', '1h', '2d' or seconds", + args: map[string]any{"rateInterval": "5x"}, + }, + wantKey: "rateInterval", + wantErr: "invalid rateInterval: invalid rateInterval/duration suffix: \"x\", values must be in the format '10m', '5m', '1h', '2d' or seconds", + }, + { + name: "accepts numeric tail", + in: args{ + key: "tail", + defaultVal: "100", + errMsg: "invalid tail", + args: map[string]any{"tail": 200}, + }, + wantKey: "tail", + wantValue: "200", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + params := api.ToolHandlerParams{ + ToolCallRequest: &mockToolCallRequest{args: tt.in.args}, + } + q := make(map[string]string) + err := setQueryParam(params, q, tt.in.key, tt.in.defaultVal) + if tt.wantErr != "" { + if err == nil { + t.Fatalf("expected error %q, got nil", tt.wantErr) + } + if err.Error() != tt.wantErr { + t.Fatalf("error mismatch: got %q, want %q", err.Error(), tt.wantErr) + } + if _, ok := q[tt.wantKey]; ok { + t.Fatalf("queryParams[%q] should not be set on error", tt.wantKey) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + got, ok := q[tt.wantKey] + if !ok { + t.Fatalf("queryParams[%q] not set", tt.wantKey) + } + if got != tt.wantValue { + t.Fatalf("queryParams[%q] = %q, want %q", tt.wantKey, got, tt.wantValue) + } + }) + } +} From 0d23ae3be659e3a0460f97f0c44c5ac2378d2610 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 1 Dec 2025 12:16:01 +0100 Subject: [PATCH 2/2] test(kiali): Keep defaults in a file Signed-off-by: Marc Nuri --- pkg/mcp/testdata/toolsets-kiali-tools.json | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/mcp/testdata/toolsets-kiali-tools.json b/pkg/mcp/testdata/toolsets-kiali-tools.json index 5f5e9f48..29fed505 100644 --- a/pkg/mcp/testdata/toolsets-kiali-tools.json +++ b/pkg/mcp/testdata/toolsets-kiali-tools.json @@ -11,7 +11,8 @@ "type": "object", "properties": { "graphType": { - "description": "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'. Default: 'versionedApp'", + "default": "versionedApp", + "description": "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'", "type": "string" }, "namespace": { @@ -23,7 +24,8 @@ "type": "string" }, "rateInterval": { - "description": "Rate interval for fetching (e.g., '10m', '5m', '1h'). Default: '10m'", + "default": "10m", + "description": "Rate interval for fetching (e.g., '10m', '5m', '1h').", "type": "string" } } @@ -47,11 +49,13 @@ "type": "string" }, "direction": { + "default": "outbound", "description": "Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'", "type": "string" }, "duration": { - "description": "Time range to get metrics for (optional string - if provided, gets metrics; if empty, get default 1800s).", + "default": "30m", + "description": "Time range to get metrics for (optional string - if provided, gets metrics (e.g., '1m', '5m', '1h'); if empty, get default 30m).", "type": "string" }, "namespace": { @@ -59,14 +63,17 @@ "type": "string" }, "quantiles": { + "default": "0.5,0.95,0.99,0.999", "description": "Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional", "type": "string" }, "rateInterval": { + "default": "10m", "description": "Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '10m'", "type": "string" }, "reporter": { + "default": "source", "description": "Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'", "type": "string" }, @@ -87,6 +94,7 @@ "type": "string" }, "step": { + "default": "15", "description": "Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds", "type": "string" } @@ -152,6 +160,7 @@ "type": "string" }, "limit": { + "default": "100", "description": "Maximum number of traces to return (default: 100, only used when traceId is not provided)", "minimum": 1, "type": "integer"