This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new dfad2701c fix(rmqctl): propagate configuration output failures (#6310)
dfad2701c is described below

commit dfad2701cb2667d4791c93a86b326c1e77732b82
Author: pilichoumao <[email protected]>
AuthorDate: Mon Oct 19 18:54:54 2026 +0800

    fix(rmqctl): propagate configuration output failures (#6310)
    
    Verified against `rocketmq-studio` at f81e73a49 before merging: 
缺陷仍在(`rmqctl/cmd/config.go:69-70` set-context、`:101-102` use-context、`:133-134` 
delete-context、`:175-176` current-context,四处仍写完 `return nil`;主干未动该文件,行
    
    merge-tree clean against the current trunk. Merged as-is (squash) per the 
maintainer review of 2026-10-19.
---
 rmqctl/cmd/config.go             |  18 +++---
 rmqctl/cmd/config_output_test.go | 122 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 132 insertions(+), 8 deletions(-)

diff --git a/rmqctl/cmd/config.go b/rmqctl/cmd/config.go
index ab1bf8d0a..0f264fec5 100644
--- a/rmqctl/cmd/config.go
+++ b/rmqctl/cmd/config.go
@@ -16,6 +16,8 @@ import (
        "github.com/spf13/pflag"
 )
 
+// Configuration changes are saved before confirmation output. Report output 
errors without
+// rolling back or replaying the successful change.
 func newConfigCommand(runtime commandRuntime) *cobra.Command {
        root := &cobra.Command{Use: "config", Short: "Manage local rmqctl 
contexts"}
        root.AddCommand(newConfigSetContextCommand(runtime))
@@ -66,8 +68,8 @@ func newConfigSetContextCommand(runtime commandRuntime) 
*cobra.Command {
                        if output.IsStructured(runtime.options.output) {
                                return output.Structured(out, 
runtime.options.output, map[string]any{"context": name, "created": !exists})
                        }
-                       fmt.Fprintf(out, "updated context %s\n", name)
-                       return nil
+                       _, err = fmt.Fprintf(out, "updated context %s\n", name)
+                       return err
                },
        }
        cmd.Flags().StringVar(&server, "server", "", "Studio Server URL")
@@ -98,8 +100,8 @@ func newConfigUseContextCommand(runtime commandRuntime) 
*cobra.Command {
                        if output.IsStructured(runtime.options.output) {
                                return output.Structured(out, 
runtime.options.output, map[string]string{"currentContext": args[0]})
                        }
-                       fmt.Fprintf(out, "current context: %s\n", args[0])
-                       return nil
+                       _, err = fmt.Fprintf(out, "current context: %s\n", 
args[0])
+                       return err
                },
        }
 }
@@ -130,8 +132,8 @@ func newConfigDeleteContextCommand(runtime commandRuntime) 
*cobra.Command {
                        if output.IsStructured(runtime.options.output) {
                                return output.Structured(out, 
runtime.options.output, map[string]any{"deleted": name})
                        }
-                       fmt.Fprintf(out, "deleted context %s\n", name)
-                       return nil
+                       _, err = fmt.Fprintf(out, "deleted context %s\n", name)
+                       return err
                },
        }
 }
@@ -172,8 +174,8 @@ func newConfigCurrentContextCommand(runtime commandRuntime) 
*cobra.Command {
                        if output.IsStructured(runtime.options.output) {
                                return output.Structured(out, 
runtime.options.output, map[string]string{"currentContext": cfg.CurrentContext})
                        }
-                       fmt.Fprintln(out, cfg.CurrentContext)
-                       return nil
+                       _, err = fmt.Fprintln(out, cfg.CurrentContext)
+                       return err
                },
        }
 }
diff --git a/rmqctl/cmd/config_output_test.go b/rmqctl/cmd/config_output_test.go
new file mode 100644
index 000000000..55d3873c6
--- /dev/null
+++ b/rmqctl/cmd/config_output_test.go
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cmd
+
+import (
+       "bytes"
+       "errors"
+       "strings"
+       "testing"
+
+       "github.com/apache/rocketmq-dashboard/rmqctl/internal/config"
+)
+
+type configResultWriter struct {
+       buffer  bytes.Buffer
+       failure error
+}
+
+func (w *configResultWriter) Write(p []byte) (int, error) {
+       if w.failure != nil {
+               return 0, w.failure
+       }
+       return w.buffer.Write(p)
+}
+
+func TestConfigCommandOutput(t *testing.T) {
+       commands := []struct {
+               name string
+               args []string
+               text string
+       }{
+               {"set-context", []string{"set-context", "prod", "--server", 
"https://updated.example.com"}, "updated context prod\n"},
+               {"use-context", []string{"use-context", "dev"}, "current 
context: dev\n"},
+               {"delete-context", []string{"delete-context", "prod"}, "deleted 
context prod\n"},
+               {"current-context", []string{"current-context"}, "prod\n"},
+       }
+       for _, command := range commands {
+               for _, format := range []string{"table", "json", "yaml"} {
+                       for _, fail := range []bool{false, true} {
+                               mode := "success"
+                               if fail {
+                                       mode = "writer failure"
+                               }
+                               t.Run(command.name+"/"+format+"/"+mode, func(t 
*testing.T) {
+                                       out := &configResultWriter{}
+                                       if fail {
+                                               out.failure = 
errors.New("synthetic output unavailable")
+                                       }
+                                       stderr := &bytes.Buffer{}
+                                       app := NewApp(out, stderr)
+                                       app.Store.Getenv = emptyEnv
+                                       path := newTestConfigPath(t)
+                                       context := config.Context{Server: 
"https://studio.example.com";, Credential: config.CredentialRef{
+                                               AccessKeyRef: 
"env:SYNTHETIC_AK", SecretKeyRef: "env:SYNTHETIC_SK",
+                                       }}
+                                       if err := app.Store.Save(path, 
config.Config{CurrentContext: "prod", Contexts: map[string]config.Context{
+                                               "prod": context, "dev": context,
+                                       }}); err != nil {
+                                               t.Fatal(err)
+                                       }
+                                       args := append([]string{"--config", 
path, "--output", format, "config"}, command.args...)
+                                       code := app.Execute(args)
+                                       // An output failure must not roll back 
or replay an already saved mutation.
+                                       saved, err := app.Store.Load(path)
+                                       if err != nil {
+                                               t.Fatal(err)
+                                       }
+                                       switch command.name {
+                                       case "set-context":
+                                               if 
saved.Contexts["prod"].Server != "https://updated.example.com"; {
+                                                       t.Fatalf("saved 
context: %#v", saved)
+                                               }
+                                       case "use-context":
+                                               if saved.CurrentContext != 
"dev" {
+                                                       t.Fatalf("saved 
context: %#v", saved)
+                                               }
+                                       case "delete-context":
+                                               if _, exists := 
saved.Contexts["prod"]; exists || saved.CurrentContext != "" {
+                                                       t.Fatalf("saved 
context: %#v", saved)
+                                               }
+                                       case "current-context":
+                                               if saved.CurrentContext != 
"prod" {
+                                                       t.Fatalf("saved 
context: %#v", saved)
+                                               }
+                                       }
+                                       if fail {
+                                               if code == 0 {
+                                                       t.Fatal("command 
reported success despite failed output")
+                                               }
+                                               if 
!strings.Contains(stderr.String(), "synthetic output unavailable") {
+                                                       t.Fatalf("lost writer 
error: %s", stderr)
+                                               }
+                                       } else {
+                                               if code != 0 {
+                                                       t.Fatalf("exit %d: %s", 
code, stderr)
+                                               }
+                                               if format == "table" && 
out.buffer.String() != command.text {
+                                                       t.Fatalf("unexpected 
output: %q", out.buffer.String())
+                                               }
+                                               if out.buffer.Len() == 0 {
+                                                       t.Fatal("missing 
output")
+                                               }
+                                       }
+                               })
+                       }
+               }
+       }
+}

Reply via email to