From 0034be446cd6597c0cc3eb654563cd12eb560029 Mon Sep 17 00:00:00 2001 From: Episkey Date: Tue, 21 Jul 2026 19:48:01 -0700 Subject: [PATCH] fix(cmd): preserve user-supplied base-url in ucloud init init unconditionally reset ConfigIns.BaseURL to DefaultBaseURL right before persisting, discarding the custom base-url entered at the prompt. The value only took effect during remote validation and the confirmation line echoed the overwritten default, so users could not tell their input was dropped. Dedicated-cloud customers pointing at a non-primary gateway had to work around it via `ucloud config update --base-url`. Remove the overwrite; ConfigBaseURL() already leaves BaseURL populated (the package-level default when the user just presses Enter). Timeout, MaxRetryTimes and Active still reset to their defaults since init has no prompt for them. Add TestInitPersistsUserSuppliedBaseURL, which drives the real NewCmdInit().Run() against a fake gateway and asserts the reloaded profile keeps the entered base-url. --- cmd/configure.go | 1 - cmd/configure_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/cmd/configure.go b/cmd/configure.go index 9bd82c6273..726b150198 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -99,7 +99,6 @@ func NewCmdInit() *cobra.Command { fmt.Println("No default project, skip.") } platform.ConfigIns.Timeout = platform.DefaultTimeoutSec - platform.ConfigIns.BaseURL = platform.DefaultBaseURL platform.ConfigIns.MaxRetryTimes = sdk.Int(platform.DefaultMaxRetryTimes) platform.ConfigIns.Active = true fmt.Printf("Configured default base url:%s\n", platform.ConfigIns.BaseURL) diff --git a/cmd/configure_test.go b/cmd/configure_test.go index e90cd82d9b..58b7b3f0ce 100644 --- a/cmd/configure_test.go +++ b/cmd/configure_test.go @@ -6,9 +6,13 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "path/filepath" "strings" "testing" + "time" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" "github.com/ucloud/ucloud-cli/cmd/internal/platform" ) @@ -203,3 +207,96 @@ func TestInitSaveOverwritesExistingOAuthOnlyProfile(t *testing.T) { got.AccessToken, got.RefreshToken, got.ExpiresAt) } } + +// stubStdin 把 os.Stdin 换成预置内容的临时文件,驱动 init 流程里的 fmt.Scanf 交互 +func stubStdin(t *testing.T, input string) { + t.Helper() + f, err := os.CreateTemp(t.TempDir(), "stdin") + if err != nil { + t.Fatal(err) + } + if _, err := f.WriteString(input); err != nil { + t.Fatal(err) + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + t.Fatal(err) + } + old := os.Stdin + os.Stdin = f + t.Cleanup(func() { + os.Stdin = old + f.Close() + }) +} + +// 回归:init 交互中用户输入的自定义 base-url 必须落盘。曾在流程末尾无条件回填 +// DefaultBaseURL,导致输入的专属云域名只在远程校验期生效、存盘的却是主站默认值, +// 且回显的也是覆盖后的值,用户无从察觉。 +func TestInitPersistsUserSuppliedBaseURL(t *testing.T) { + gateway := fakeGatewayServer(t) + defer gateway.Close() + + dir := t.TempDir() + cfgPath := filepath.Join(dir, "config.json") + credPath := filepath.Join(dir, "credential.json") + + m, err := platform.NewAggConfigManager(cfgPath, credPath) + if err != nil { + t.Fatal(err) + } + // 全新 init 现场:profile 尚未落盘,BaseURL 为包级默认值。timeout/max-retry 取小值让 + // 远程校验打本地假网关时快速收敛(init 末尾仍会把二者重置为各自默认值) + cfg := &platform.AggConfig{ + Profile: platform.DefaultProfile, + BaseURL: platform.DefaultBaseURL, + Timeout: 3, + MaxRetryTimes: sdk.Int(0), + } + + oldCfgPath, oldCredPath := platform.ConfigFilePath, platform.CredentialFilePath + oldM, oldC := platform.AggConfigListIns, platform.ConfigIns + oldCC, oldAC, oldRT := platform.ClientConfig, platform.AuthCredential, activeRuntime + // Run 末尾的 InitConfig 按包级路径读写配置,指向临时目录避免污染真实 ~/.ucloud + platform.ConfigFilePath, platform.CredentialFilePath = cfgPath, credPath + platform.AggConfigListIns, platform.ConfigIns = m, cfg + // ConfigPublicKey/ConfigPrivateKey 直接写 AuthCredential,为 nil 会 panic + platform.AuthCredential = &platform.CredentialConfig{} + // 末尾 printHello 经 activeRuntime 取 client:钉到假网关,避免测试真的打外网 + platform.ClientConfig = &sdk.Config{BaseUrl: gateway.URL, Timeout: 3 * time.Second} + setActiveRuntimeFromBaseGlobals() + defer func() { + platform.ConfigFilePath, platform.CredentialFilePath = oldCfgPath, oldCredPath + platform.AggConfigListIns, platform.ConfigIns = oldM, oldC + platform.ClientConfig, platform.AuthCredential, activeRuntime = oldCC, oldAC, oldRT + }() + + // init 依次 Scanf 读取:public-key、private-key、base-url、是否上传日志 + stubStdin(t, fmt.Sprintf("pub\npri\n%s\nno\n", gateway.URL)) + + cmd := NewCmdInit() + cmd.Run(cmd, nil) + + // 重新读盘验证持久化,而非只看内存 + m2, err := platform.NewAggConfigManager(cfgPath, credPath) + if err != nil { + t.Fatal(err) + } + got, ok := m2.GetAggConfigByProfile(platform.DefaultProfile) + if !ok { + t.Fatal("profile default missing after reload") + } + if got.BaseURL != gateway.URL { + t.Errorf("base_url on disk = %q, want user supplied %q (init must not overwrite it with the default %q)", + got.BaseURL, gateway.URL, platform.DefaultBaseURL) + } + // 同一流程里 init 本就该落的默认值,不能被本次修复带偏 + if got.Timeout != platform.DefaultTimeoutSec { + t.Errorf("timeout_sec on disk = %d, want default %d", got.Timeout, platform.DefaultTimeoutSec) + } + if got.MaxRetryTimes == nil || *got.MaxRetryTimes != platform.DefaultMaxRetryTimes { + t.Errorf("max_retry_times on disk = %v, want default %d", got.MaxRetryTimes, platform.DefaultMaxRetryTimes) + } + if !got.Active { + t.Error("active on disk = false, want true") + } +}