diff --git a/internal/utils/prompts.go b/internal/utils/prompts.go index 889f24dd..7282d02d 100644 --- a/internal/utils/prompts.go +++ b/internal/utils/prompts.go @@ -252,11 +252,15 @@ var updateResourceTagsPromptFn = func(existingTags map[string]string, noColor bo return nil, err } - // Empty value means remove the tag - if value == "" && tags[key] != "" { - delete(tags, key) - fmt.Fprintf(os.Stderr, " Removed tag: %s\n", key) - } else if value != "" { + // Empty value means remove the tag, if it exists. + if value == "" { + if _, exists := tags[key]; exists { + delete(tags, key) + fmt.Fprintf(os.Stderr, " Removed tag: %s\n", key) + } else { + fmt.Fprintf(os.Stderr, " Tag '%s' does not exist, nothing to remove\n", key) + } + } else { tags[key] = value fmt.Fprintf(os.Stderr, " Updated tag: %s: %s\n", key, value) } diff --git a/internal/utils/prompts_test.go b/internal/utils/prompts_test.go index 1639dfa7..369fda64 100644 --- a/internal/utils/prompts_test.go +++ b/internal/utils/prompts_test.go @@ -703,6 +703,34 @@ func TestUpdateResourceTagsPrompt_DefaultModifyExistingRemovesTag(t *testing.T) assert.Empty(t, stdout) } +// TestUpdateResourceTagsPrompt_DefaultModifyRemovesEmptyValueTag exercises +// removing an existing tag whose current value is already empty. +func TestUpdateResourceTagsPrompt_DefaultModifyRemovesEmptyValueTag(t *testing.T) { + mockPromptSequence(t, []bool{true, true}, []string{"2", "foo", "", ""}) + stdout, stderr := withMockedIO("", func() { + tags, err := UpdateResourceTagsPrompt(map[string]string{"foo": ""}, true) + assert.NoError(t, err) + assert.Empty(t, tags) + }) + assert.Contains(t, stderr, "Removed tag: foo") + assert.Empty(t, stdout) +} + +// TestUpdateResourceTagsPrompt_DefaultModifyEmptyValueForNewKey exercises +// entering an empty value for a key that does not exist yet: it must not be +// added to the tag set, and the user gets a clear "nothing to remove" notice +// instead of the key silently vanishing. +func TestUpdateResourceTagsPrompt_DefaultModifyEmptyValueForNewKey(t *testing.T) { + mockPromptSequence(t, []bool{true, true}, []string{"2", "baz", "", ""}) + stdout, stderr := withMockedIO("", func() { + tags, err := UpdateResourceTagsPrompt(map[string]string{"foo": "bar"}, true) + assert.NoError(t, err) + assert.Equal(t, map[string]string{"foo": "bar"}, tags) + }) + assert.Contains(t, stderr, "Tag 'baz' does not exist, nothing to remove") + assert.Empty(t, stdout) +} + // TestDesignConfirmPrompt_DefaultRendering verifies the default // designConfirmPromptFn renders design-stage wording instead of // BuyConfirmPrompt's purchase wording. diff --git a/internal/utils/prompts_wasm.go b/internal/utils/prompts_wasm.go index 5dd228c6..38148ebe 100644 --- a/internal/utils/prompts_wasm.go +++ b/internal/utils/prompts_wasm.go @@ -270,11 +270,15 @@ func wasmUpdateResourceTagsPrompt(existingTags map[string]string, noColor bool) return nil, err } - // Empty value means remove the tag - if value == "" && tags[key] != "" { - delete(tags, key) - buf.add(fmt.Sprintf(" Removed tag: %s", key)) - } else if value != "" { + // Empty value means remove the tag, if it exists. + if value == "" { + if _, exists := tags[key]; exists { + delete(tags, key) + buf.add(fmt.Sprintf(" Removed tag: %s", key)) + } else { + buf.add(fmt.Sprintf(" Tag '%s' does not exist, nothing to remove", key)) + } + } else { tags[key] = value buf.add(fmt.Sprintf(" Updated tag: %s: %s", key, value)) } diff --git a/internal/utils/prompts_wasm_test.go b/internal/utils/prompts_wasm_test.go index 6866b0df..85f700a3 100644 --- a/internal/utils/prompts_wasm_test.go +++ b/internal/utils/prompts_wasm_test.go @@ -389,6 +389,24 @@ func TestWasmUpdateResourceTagsPrompt(t *testing.T) { }, expectError: false, }, + { + name: "empty value for key that does not exist is a no-op", + existingTags: map[string]string{ + "foo": "bar", + }, + mockResponses: []string{ + "y", // Continue + "2", // Start with existing + "baz", // Key that does not exist + "", // Empty value = attempt remove + "", // Finish + "y", // Apply changes + }, + expectedTags: map[string]string{ + "foo": "bar", + }, + expectError: false, + }, { name: "no existing tags - add new", existingTags: map[string]string{},