From 3215666341b44941f1efacbdb4c7ad811d98c8ee Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Thu, 2 Apr 2026 22:31:35 -0700 Subject: [PATCH] fix: error in ci when interactive inputs are required --- internal/iostreams/prompts.go | 12 ++++ internal/iostreams/prompts_test.go | 91 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/internal/iostreams/prompts.go b/internal/iostreams/prompts.go index 9e7330b8..37ebf59d 100644 --- a/internal/iostreams/prompts.go +++ b/internal/iostreams/prompts.go @@ -192,18 +192,30 @@ func errInteractivityFlags(cfg PromptConfig) error { // ConfirmPrompt prompts the user for a "yes" or "no" (true or false) value for // the message func (io *IOStreams) ConfirmPrompt(ctx context.Context, message string, defaultValue bool) (bool, error) { + if !io.IsTTY() { + return false, errInteractivityFlags(ConfirmPromptConfig{}) + } return confirmForm(io, ctx, message, defaultValue) } // InputPrompt prompts the user for a string value for the message, which can // optionally be made required func (io *IOStreams) InputPrompt(ctx context.Context, message string, cfg InputPromptConfig) (string, error) { + if !io.IsTTY() { + if cfg.IsRequired() { + return "", errInteractivityFlags(cfg) + } + return "", nil + } return inputForm(io, ctx, message, cfg) } // MultiSelectPrompt prompts the user to select multiple values in a list and // returns the selected values func (io *IOStreams) MultiSelectPrompt(ctx context.Context, message string, options []string) ([]string, error) { + if !io.IsTTY() { + return nil, errInteractivityFlags(MultiSelectPromptConfig{}) + } return multiSelectForm(io, ctx, message, options) } diff --git a/internal/iostreams/prompts_test.go b/internal/iostreams/prompts_test.go index 79d8de07..6b6960f9 100644 --- a/internal/iostreams/prompts_test.go +++ b/internal/iostreams/prompts_test.go @@ -273,6 +273,97 @@ func TestPasswordPrompt(t *testing.T) { } } +func TestConfirmPrompt(t *testing.T) { + tests := map[string]struct { + expectedError string + }{ + "error if non-TTY": { + expectedError: slackerror.ErrPrompt, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + + fsMock := slackdeps.NewFsMock() + osMock := slackdeps.NewOsMock() + osMock.On("Stdout").Return(&slackdeps.FileMock{FileInfo: &slackdeps.FileInfoNamedPipe{}}) + cfg := config.NewConfig(fsMock, osMock) + io := NewIOStreams(cfg, fsMock, osMock) + + _, err := io.ConfirmPrompt(ctx, "Continue?", false) + + assert.Error(t, err) + assert.Equal(t, tc.expectedError, slackerror.ToSlackError(err).Code) + }) + } +} + +func TestInputPrompt(t *testing.T) { + tests := map[string]struct { + required bool + expectedError string + expectedValue string + }{ + "error if non-TTY and required": { + required: true, + expectedError: slackerror.ErrPrompt, + }, + "no error if non-TTY and optional": { + required: false, + expectedValue: "", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + + fsMock := slackdeps.NewFsMock() + osMock := slackdeps.NewOsMock() + osMock.On("Stdout").Return(&slackdeps.FileMock{FileInfo: &slackdeps.FileInfoNamedPipe{}}) + cfg := config.NewConfig(fsMock, osMock) + io := NewIOStreams(cfg, fsMock, osMock) + + value, err := io.InputPrompt(ctx, "Enter name", InputPromptConfig{ + Required: tc.required, + }) + + if err != nil { + assert.Equal(t, tc.expectedError, slackerror.ToSlackError(err).Code) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedValue, value) + } + }) + } +} + +func TestMultiSelectPrompt(t *testing.T) { + tests := map[string]struct { + expectedError string + }{ + "error if non-TTY": { + expectedError: slackerror.ErrPrompt, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + + fsMock := slackdeps.NewFsMock() + osMock := slackdeps.NewOsMock() + osMock.On("Stdout").Return(&slackdeps.FileMock{FileInfo: &slackdeps.FileInfoNamedPipe{}}) + cfg := config.NewConfig(fsMock, osMock) + io := NewIOStreams(cfg, fsMock, osMock) + + _, err := io.MultiSelectPrompt(ctx, "Pick items", []string{"a", "b"}) + + assert.Error(t, err) + assert.Equal(t, tc.expectedError, slackerror.ToSlackError(err).Code) + }) + } +} + func TestSelectPrompt(t *testing.T) { tests := map[string]struct { flagValue string