diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index 2038f69f..517c5419 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -245,12 +245,21 @@ func getAuths(ctx context.Context, clients *shared.ClientFactory) ([]types.Slack } } if len(allAuths) == 0 { - auth := types.SlackAuth{} - err := validateAuth(ctx, clients, &auth) + // No workspaces connected - prompt user to login if interactive + if !clients.IO.IsTTY() { + return nil, slackerror.New(slackerror.ErrNotAuthed). + WithMessage("No workspaces connected"). + WithRemediation("Run %s to sign in to a workspace", style.Commandf("login", false)) + } + clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{ + Emoji: "wave", + Text: "No workspaces connected. Sign in to get started.", + })) + newAuth, _, err := authpkg.LoginWithClients(ctx, clients, "", false) if err != nil { - return nil, slackerror.New(slackerror.ErrNotAuthed) + return nil, err } - allAuths = append(allAuths, auth) + allAuths = append(allAuths, newAuth) } return allAuths, nil } diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index d1f6c8fa..f7e583f2 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -543,7 +543,8 @@ func TestPrompt_AppSelectPrompt_GetApps(t *testing.T) { }, }, }, - "returns unknown installation statuses for apps without auths": { + "returns unknown installation statuses for apps when status check fails": { + mockAuths: fakeAuthsByTeamDomainSlice, mockAppsSavedDeployed: []types.App{ deployedTeam1InstalledApp, }, @@ -559,6 +560,7 @@ func TestPrompt_AppSelectPrompt_GetApps(t *testing.T) { TeamID: team1TeamID, InstallStatus: types.AppInstallationStatusUnknown, }, + Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, }, @@ -1093,6 +1095,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { expectedError: slackerror.New(slackerror.ErrLocalAppNotSupported), }, "errors if team id flag does not have authorization": { + mockAuths: []types.SlackAuth{fakeAuthsByTeamDomain[team2TeamDomain]}, mockFlagTeam: team1TeamID, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowInstalledAndNewApps, @@ -1807,3 +1810,99 @@ func Test_ValidateAuth(t *testing.T) { }) } } + +// Test_getAuths_NoWorkspacesConnected tests the login prompt behavior when no +// workspaces are connected +func Test_getAuths_NoWorkspacesConnected(t *testing.T) { + tests := map[string]struct { + isTTY bool + expectedErr error + apiExchangeAuthTicketResultResponse api.ExchangeAuthTicketResult + apiExchangeAuthTicketResultError error + apiGenerateAuthTicketResultResponse api.GenerateAuthTicketResult + apiGenerateAuthTicketResultError error + }{ + "returns error when not interactive and no workspaces connected": { + isTTY: false, + expectedErr: slackerror.New(slackerror.ErrNotAuthed), + }, + "prompts for login when interactive and no workspaces connected": { + isTTY: true, + apiExchangeAuthTicketResultResponse: api.ExchangeAuthTicketResult{ + TeamDomain: team1TeamDomain, + TeamID: team1TeamID, + Token: team1Token, + UserID: team1UserID, + }, + apiGenerateAuthTicketResultResponse: api.GenerateAuthTicketResult{ + Ticket: "ticket123", + }, + expectedErr: nil, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + clientsMock := shared.NewClientsMock() + clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{}, nil) + clientsMock.IO.On("IsTTY").Return(tc.isTTY) + clientsMock.API.On( + "GenerateAuthTicket", + mock.Anything, + mock.Anything, + mock.Anything, + ).Return( + tc.apiGenerateAuthTicketResultResponse, + tc.apiGenerateAuthTicketResultError, + ) + clientsMock.API.On( + "ExchangeAuthTicket", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return( + tc.apiExchangeAuthTicketResultResponse, + tc.apiExchangeAuthTicketResultError, + ) + clientsMock.Auth.On( + "IsAPIHostSlackProd", + mock.Anything, + ).Return(true) + clientsMock.Auth.On( + "SetAuth", + mock.Anything, + mock.Anything, + ).Return( + types.SlackAuth{}, + "", + nil, + ) + clientsMock.IO.On( + "InputPrompt", + mock.Anything, + "Enter challenge code", + iostreams.InputPromptConfig{Required: true}, + ).Return( + "challengeCode", + nil, + ) + clientsMock.AddDefaultMocks() + clients := shared.NewClientFactory(clientsMock.MockClientFactory()) + + auths, err := getAuths(ctx, clients) + + if tc.expectedErr != nil { + require.Error(t, err) + assert.Equal(t, tc.expectedErr.(*slackerror.Error).Code, slackerror.ToSlackError(err).Code) + assert.Contains(t, slackerror.ToSlackError(err).Message, "No workspaces connected") + } else { + require.NoError(t, err) + require.Len(t, auths, 1) + assert.Equal(t, team1TeamID, auths[0].TeamID) + // Verify the welcome message was printed + assert.Contains(t, clientsMock.GetStdoutOutput(), "No workspaces connected") + } + }) + } +}