diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index c3673c7..ec8c32f 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -950,16 +950,23 @@ func main() { engine := "workers-ai" if useGemini { engine = "gemma" - cfg.GeminiAPIKey = key } + // 🔴 t190(leo 08-04 實撞):「我切到 Gemini 把內容刪掉,再切回 Workers AI, + // 儲存,回去發現 **Gemini Key 還在**」。 + // + // 舊版把 `cfg.GeminiAPIKey = key` 寫在 `if useGemini` 裡 ⇒ 選回雲端 AI 時 + // 整段跳過、舊金鑰原封不動 ⇒ **清空輸入框等於沒作用,金鑰刪不掉**。 + // leo 要求:「如果他不想留 Key 了,**要可以刪除**」。 + // + // 改成**無條件以輸入框為準**(清空=刪除),才符合使用者的心智模型: + // 我把欄位清空並按儲存,它就該不見。 + cfg.GeminiAPIKey = key // 機器層寫一份當預設;各帳號層一併寫,避免舊 config 殘留的空欄位繼承到別的值。 cfg.Extractor = engine cfg.ExtractorExplicit = true // ← 使用者主動選過,之後不再被預設覆蓋 for i := range cfg.Accounts { cfg.Accounts[i].Extractor = engine - if useGemini { - cfg.Accounts[i].GeminiAPIKey = key - } + cfg.Accounts[i].GeminiAPIKey = key // 同上:帳號層也要能被清空 } if err := saveConfig(cfg); err != nil { dialog.ShowError(err, win) diff --git a/cmd/arcrun-tray/t190_test.go b/cmd/arcrun-tray/t190_test.go new file mode 100644 index 0000000..2ec4356 --- /dev/null +++ b/cmd/arcrun-tray/t190_test.go @@ -0,0 +1,73 @@ +package main + +import "testing" + +// t190(leo 08-04 實撞):「我切到 Gemini 把內容刪掉,再切回 Workers AI,儲存, +// 回去發現 Gemini Key 還在」——清空輸入框必須真的把金鑰刪掉。 +// +// 這裡驗的是「儲存邏輯」的契約:金鑰**無條件以輸入框為準**, +// 不因為選了哪個引擎而跳過寫入(舊版的病就是寫在 if useGemini 裡)。 +func TestAISettingsSaveClearsKeyWhenEmptied(t *testing.T) { + // 模擬儲存邏輯(與 main.go 的 AI 設定 handler 同一套規則) + apply := func(cfg *directConfig, useGemini bool, key string) { + engine := "workers-ai" + if useGemini { + engine = "gemma" + } + cfg.GeminiAPIKey = key + cfg.Extractor = engine + cfg.ExtractorExplicit = true + for i := range cfg.Accounts { + cfg.Accounts[i].Extractor = engine + cfg.Accounts[i].GeminiAPIKey = key + } + } + + cfg := &directConfig{ + GeminiAPIKey: "old-key", + Accounts: []accountCfg{{GeminiAPIKey: "old-key"}, {GeminiAPIKey: "old-key"}}, + } + + // 使用者:切回「雲端 AI」且把金鑰欄位清空 → 金鑰必須消失 + apply(cfg, false, "") + + if cfg.GeminiAPIKey != "" { + t.Errorf("頂層金鑰沒被清掉:%q(leo 實撞的病)", cfg.GeminiAPIKey) + } + for i, a := range cfg.Accounts { + if a.GeminiAPIKey != "" { + t.Errorf("帳號[%d] 金鑰沒被清掉:%q", i, a.GeminiAPIKey) + } + } + if cfg.Extractor != "workers-ai" { + t.Errorf("引擎應為 workers-ai,got %q", cfg.Extractor) + } +} + +// 反向:選 Gemini 並填入金鑰時,金鑰要存進每一層(原本就該有的行為,不可被上面的修改弄壞) +func TestAISettingsSaveStoresKeyOnEveryLayer(t *testing.T) { + apply := func(cfg *directConfig, useGemini bool, key string) { + engine := "workers-ai" + if useGemini { + engine = "gemma" + } + cfg.GeminiAPIKey = key + cfg.Extractor = engine + cfg.ExtractorExplicit = true + for i := range cfg.Accounts { + cfg.Accounts[i].Extractor = engine + cfg.Accounts[i].GeminiAPIKey = key + } + } + cfg := &directConfig{Accounts: []accountCfg{{}, {}}} + apply(cfg, true, "new-key") + + if cfg.GeminiAPIKey != "new-key" || cfg.Extractor != "gemma" { + t.Errorf("頂層沒寫對:key=%q engine=%q", cfg.GeminiAPIKey, cfg.Extractor) + } + for i, a := range cfg.Accounts { + if a.GeminiAPIKey != "new-key" || a.Extractor != "gemma" { + t.Errorf("帳號[%d] 沒寫對:key=%q engine=%q", i, a.GeminiAPIKey, a.Extractor) + } + } +}