From 749b3b063f3512f52f4a56ddd30e09694e19ebed Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 18:10:14 -0700 Subject: [PATCH 1/9] chore: default to bolt experiment behavior for app selection --- internal/prompts/app_select.go | 560 +---- internal/prompts/app_select_test.go | 3490 ++------------------------- 2 files changed, 244 insertions(+), 3806 deletions(-) diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index a5a26a82..5355debf 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -117,34 +117,6 @@ func (apps *TeamApps) IsEmpty() bool { return apps.Equals(TeamApps{}) } -// authOrAppTeamDomain greedily returns a team domain corresponding to the TeamApps -// depending on what app and auth information is available. -// -// * When Auth is known, returns auth's team domain IFF it matches either the Hosted -// or local app's team domain. -// -// * When Auth is unknown or doesn't match the Hosted or Local app's team ID, -// then it returns app's team domain. -// -// Basically, treat as a convenience getter intended for use in the context where -// you have a team you want to filter against and you don't care whether it's an -// auth or an app that corresponds. E.g. when you are comparing to --team flags -func (apps *TeamApps) authOrAppTeamDomain() string { - if apps.Auth.TeamDomain != "" && (apps.Auth.TeamID == apps.Hosted.App.TeamID || apps.Auth.TeamID == apps.Local.App.TeamID) { - // Auth whose team id matches either hosted or local app's team - // Can be safely returned - return apps.Auth.TeamDomain - } - - // If we get here we might be missing an auth OR the auth doesn't - // match any included app team ids (that is the case when the auth is org - // resolved for a workspace app) - if apps.Hosted.App.TeamID != "" { - return apps.Hosted.App.TeamDomain - } - return apps.Local.App.TeamDomain -} - // authOrAppTeamID greedily returns a team ID corresponding to the TeamApps // depending on what app and auth information is available. // @@ -177,7 +149,6 @@ var appTransferDisclaimer = style.TextSection{ }, } -var appInstallPromptNew = "Install to a new team" var SelectTeamPrompt = "Select a team" // getApps returns the apps saved to files with known credentials @@ -705,125 +676,6 @@ func filterByAppID(workspaceApps map[string]TeamApps, appID string) (SelectedApp return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound) } -// filterByTeamFlag returns the possible combos of auth and apps that correspond to a given team flag. -// Team flag can be team_domain or team_id -func filterByTeamFlag(workspaceApps map[string]TeamApps, teamFlag string) (TeamApps, error) { - - for _, selection := range workspaceApps { - if teamFlag == selection.authOrAppTeamDomain() || teamFlag == selection.authOrAppTeamID() { - - return selection, nil - } - } - return TeamApps{}, slackerror.New(slackerror.ErrTeamNotFound) -} - -// selectAppWorkspace prompts for a choice of workspace with status apps -func selectAppWorkspace(ctx context.Context, clients *shared.ClientFactory, workspaceApps map[string]TeamApps, status AppInstallStatus) (TeamApps, error) { - teamIDsWithApps, domainsWithApps, domainsWithAppsLabels := []string{}, []string{}, []string{} - unusedTeamIDs, unusedDomains, unusedDomainsLabels := []string{}, []string{}, []string{} - - for _, workspace := range workspaceApps { - - if includeInAppSelect(workspace.Hosted.App, status) { - domainsWithApps = append(domainsWithApps, workspace.Hosted.App.TeamDomain) - teamIDsWithApps = append(teamIDsWithApps, workspace.Hosted.App.TeamID) - - // to show the user - domainsWithAppsLabels = append(domainsWithAppsLabels, style.TeamSelectLabel(workspace.Hosted.App.TeamDomain, workspace.Hosted.App.TeamID)) - continue - } else if includeInAppSelect(workspace.Local.App, status) { - domainsWithApps = append(domainsWithApps, workspace.Local.App.TeamDomain) - teamIDsWithApps = append(teamIDsWithApps, workspace.Local.App.TeamID) - - // to show the user - domainsWithAppsLabels = append(domainsWithAppsLabels, style.TeamSelectLabel(workspace.Local.App.TeamDomain, workspace.Local.App.TeamID)) - continue - } - if workspace.Auth.TeamID != "" { - unusedDomains = append(unusedDomains, workspace.Auth.TeamDomain) - unusedTeamIDs = append(unusedTeamIDs, workspace.Auth.TeamID) - unusedDomainsLabels = append(unusedDomainsLabels, style.TeamSelectLabel(workspace.Auth.TeamDomain, workspace.Auth.TeamID)) - } - } - - if (status == ShowInstalledAppsOnly || status == ShowInstalledAndUninstalledApps) && len(domainsWithApps) == 0 { - return TeamApps{}, slackerror.New(slackerror.ErrInstallationRequired) - } - - // Prepare the options for the user - if err := SortAlphaNumeric(domainsWithApps, domainsWithAppsLabels, teamIDsWithApps); err != nil { - return TeamApps{}, err - } - if err := SortAlphaNumeric(unusedDomains, unusedDomainsLabels, unusedTeamIDs); err != nil { - return TeamApps{}, err - } - - // Optionally, add prompt for creating new app - if status == ShowAllApps || status == ShowInstalledAndNewApps && len(unusedDomains) > 0 { - installPrompt := style.Secondary(appInstallPromptNew) - domainsWithAppsLabels = append(domainsWithAppsLabels, installPrompt) - } - - var selectedTeamID string - - // Get the users selection of team - selection, err := clients.IO.SelectPrompt(ctx, SelectTeamPrompt, domainsWithAppsLabels, iostreams.SelectPromptConfig{ - Flag: clients.Config.Flags.Lookup("team"), - Required: true, - }) - if err != nil { - return TeamApps{}, err - } - - // If a flag was used, return the team selection by the flag - if selection.Flag { - return getTeamByFlag(selection.Option, workspaceApps, status) - } - - if selection.Index < len(domainsWithApps) { - // Selected an existing domain - selectedTeamID = teamIDsWithApps[selection.Index] - } else { - // Somehow did not select an existing app domain - // Would we ever fall into this section of code? - selection, err = clients.IO.SelectPrompt(ctx, appInstallPromptNew, unusedDomainsLabels, iostreams.SelectPromptConfig{ - Required: true, - }) - if err != nil { - return TeamApps{}, err - } - if selection.Prompt { - selectedTeamID = unusedTeamIDs[selection.Index] - } - clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(appTransferDisclaimer)) - } - - return workspaceApps[selectedTeamID], nil -} - -// getTeamByFlag returns the matching workspace with a given status for a flag -func getTeamByFlag(flag string, workspaceApps map[string]TeamApps, status AppInstallStatus) (TeamApps, error) { - var selectedTeam TeamApps - for _, team := range workspaceApps { - if team.authOrAppTeamID() == flag || team.authOrAppTeamDomain() == flag { - selectedTeam = team - } - } - if selectedTeam.IsEmpty() { - return TeamApps{}, slackerror.New(slackerror.ErrCredentialsNotFound) - } - if (!selectedTeam.Hosted.IsEmpty() || !selectedTeam.Local.IsEmpty()) && status == ShowAllApps { - return selectedTeam, nil - } - if ((!selectedTeam.Hosted.IsEmpty() && includeInAppSelect(selectedTeam.Hosted.App, status)) || - (selectedTeam.Local.IsEmpty() && includeInAppSelect(selectedTeam.Local.App, status))) && - (status == ShowInstalledAppsOnly || status == ShowInstalledAndUninstalledApps || status == ShowInstalledAndNewApps) { - return selectedTeam, nil - } - return TeamApps{}, slackerror.New(slackerror.ErrAppNotFound) -} - // includeInAppSelect is a helper function for app selection that determines whether an existing app should be displayed given the install status func includeInAppSelect(app types.App, status AppInstallStatus) bool { @@ -856,164 +708,6 @@ func showOptionsForNewAppCreation(app types.App, status AppInstallStatus) bool { return !appExists(app) && (status == ShowAllApps || status == ShowInstalledAndNewApps) } -// selectAppEnvironment prompts for the environment choice of a status app -func selectAppEnvironment(ctx context.Context, clients *shared.ClientFactory, workspace TeamApps, status AppInstallStatus) (SelectedApp, error) { - - showLocalApp := includeInAppSelect(workspace.Local.App, status) || showOptionsForNewAppCreation(workspace.Local.App, status) - showDeployedApp := includeInAppSelect(workspace.Hosted.App, status) || showOptionsForNewAppCreation(workspace.Hosted.App, status) - appOptions, appLabels := []SelectedApp{}, []string{} - - if showLocalApp { - appOptions = append(appOptions, workspace.Local) - appLabels = append(appLabels, style.AppSelectLabel("Local", workspace.Local.App.AppID, workspace.Local.App.IsUninstalled())) - } - if showDeployedApp { - appOptions = append(appOptions, workspace.Hosted) - appLabels = append(appLabels, style.AppSelectLabel("Deployed", workspace.Hosted.App.AppID, workspace.Hosted.App.IsUninstalled())) - } - - var selectedApp SelectedApp - selection, err := clients.IO.SelectPrompt(ctx, "Choose an app environment", appLabels, iostreams.SelectPromptConfig{ - Flag: clients.Config.Flags.Lookup("app"), - Required: true, - }) - if err != nil { - return SelectedApp{}, err - } else if selection.Flag { - switch { - case showLocalApp && types.IsAppFlagLocal(selection.Option): - selectedApp = workspace.Local - case showDeployedApp && types.IsAppFlagDeploy(selection.Option): - selectedApp = workspace.Hosted - default: - return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag) - } - } else if selection.Prompt { - selectedApp = appOptions[selection.Index] - } - return selectedApp, nil -} - -type workspaceOptions struct { - environment AppEnvironmentType - installStatus AppInstallStatus -} - -// selectTeamApp prompts user to select a SelectedApp matching the provided options -func selectTeamApp(ctx context.Context, clients *shared.ClientFactory, workspaceApps map[string]TeamApps, options workspaceOptions) (string, SelectedApp, error) { - - includeApp := func(app types.App) bool { - if app.IsDev && options.environment == ShowHostedOnly || !app.IsDev && options.environment == ShowLocalOnly { - return false - } - return includeInAppSelect(app, options.installStatus) - } - - teamIDsWithApps, domainsWithApps, domainsWithAppsLabels := []string{}, []string{}, []string{} - unusedTeamIDs, unusedDomains, unusedDomainsLabels := []string{}, []string{}, []string{} - for _, workspace := range workspaceApps { - if includeApp(workspace.Hosted.App) { - - teamIDsWithApps = append(teamIDsWithApps, workspace.Hosted.App.TeamID) - domainsWithApps = append(domainsWithApps, workspace.Hosted.App.TeamDomain) - domainsWithAppsLabels = append(domainsWithAppsLabels, - style.TeamAppSelectLabel(workspace.Hosted.App.TeamDomain, workspace.Hosted.App.TeamID, workspace.Hosted.App.AppID, workspace.Hosted.App.IsUninstalled())) - - } else if includeApp(workspace.Local.App) { - - teamIDsWithApps = append(teamIDsWithApps, workspace.Local.App.TeamID) - domainsWithApps = append(domainsWithApps, workspace.Local.App.TeamDomain) - domainsWithAppsLabels = append(domainsWithAppsLabels, - style.TeamAppSelectLabel(workspace.Local.App.TeamDomain, workspace.Local.App.TeamID, workspace.Local.App.AppID, workspace.Local.App.IsUninstalled())) - } else { - // Neither hosted nor local app can be included, so this teamApp's remaining auth domain is unused - if workspace.Auth.TeamID != "" { - unusedTeamIDs = append(unusedTeamIDs, workspace.Auth.TeamID) - unusedDomains = append(unusedDomains, workspace.Auth.TeamDomain) - unusedDomainsLabels = append(unusedDomainsLabels, style.TeamSelectLabel(workspace.Auth.TeamDomain, workspace.Auth.TeamID)) - } - } - } - - if options.installStatus == ShowInstalledAppsOnly && len(domainsWithApps) == 0 { - return "", SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - - if err := SortAlphaNumeric(domainsWithApps, domainsWithAppsLabels, teamIDsWithApps); err != nil { - return "", SelectedApp{}, err - } - if err := SortAlphaNumeric(unusedDomains, unusedDomainsLabels, unusedTeamIDs); err != nil { - return "", SelectedApp{}, err - } - - // Add prompt for creating new apps if unused domains exist - if len(unusedDomains) > 0 && (options.installStatus == ShowAllApps || options.installStatus == ShowInstalledAndNewApps) { - installPrompt := style.Secondary(appInstallPromptNew) - domainsWithAppsLabels = append(domainsWithAppsLabels, installPrompt) - } - - switch options.environment { - case ShowLocalOnly: - SelectTeamPrompt = "Choose a local environment" - case ShowHostedOnly: - SelectTeamPrompt = "Choose a deployed environment" - } - - var selectedDomain string - var selectedTeamID string - - selection, err := clients.IO.SelectPrompt(ctx, SelectTeamPrompt, domainsWithAppsLabels, iostreams.SelectPromptConfig{ - Flag: clients.Config.Flags.Lookup("team"), - Required: true, - }) - if err != nil { - return "", SelectedApp{}, err - } else if selection.Flag { - - if selectedTeam, err := getTeamByFlag(selection.Option, workspaceApps, options.installStatus); err != nil { - return "", SelectedApp{}, err - } else { - selectedDomain = selectedTeam.authOrAppTeamDomain() - selectedTeamID = selectedTeam.authOrAppTeamID() - } - } else if selection.Prompt && selection.Index < len(domainsWithApps) { - selectedDomain = domainsWithApps[selection.Index] - selectedTeamID = teamIDsWithApps[selection.Index] - } else if selection.Prompt { - selection, err = clients.IO.SelectPrompt(ctx, appInstallPromptNew, unusedDomainsLabels, iostreams.SelectPromptConfig{ - Flag: clients.Config.Flags.Lookup("team"), - Required: true, - }) - if err != nil { - return "", SelectedApp{}, err - } else if selection.Flag { - if selectedTeam, err := getTeamByFlag(selection.Option, workspaceApps, options.installStatus); err != nil { - return "", SelectedApp{}, err - } else { - selectedDomain = selectedTeam.authOrAppTeamDomain() - selectedTeamID = selectedTeam.authOrAppTeamID() - } - } else if selection.Prompt { - selectedDomain = unusedDomains[selection.Index] - selectedTeamID = unusedTeamIDs[selection.Index] - } - clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(appTransferDisclaimer)) - } - - workspace := workspaceApps[selectedTeamID] - - // Depending on the environment, return the SelectedApp that matches - var selectedApp SelectedApp - switch options.environment { - case ShowLocalOnly: - selectedApp = workspace.Local - case ShowHostedOnly: - selectedApp = workspace.Hosted - } - - return selectedDomain, selectedApp, nil -} - // flatAppSelectPrompt reveals options for apps that match the install status func flatAppSelectPrompt( ctx context.Context, @@ -1291,13 +985,8 @@ func flatAppSelectPrompt( // AppSelectPrompt prompts the user to select a workspace then environment for the current command, // returning the selected app. This app might require installation before use if `status == ShowAllApps`. func AppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, status AppInstallStatus) (SelectedApp, error) { - var selectedApp SelectedApp - var selectedTeam TeamApps - var tokenAuth types.SlackAuth - var appFlag = clients.Config.AppFlag // e.g. 'local', 'deploy', 'deployed', A12345 var tokenFlag = clients.Config.TokenFlag // e.g. xoxe.xoxp.xxxx - var teamFlag = clients.Config.TeamFlag // e.g. T12345678, 'acme-org', 'acme-workspace' if clients.Config.SkipLocalFs() { clients.IO.PrintDebug(ctx, "selecting app based on token value and app id value '%s'", appFlag) @@ -1312,103 +1001,7 @@ func AppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, status return selection, nil } - if clients.Config.WithExperimentOn(experiment.BoltFrameworks) { - return flatAppSelectPrompt(ctx, clients, ShowAllEnvironments, status) - } - - // Get all apps - teamApps, err := getTeamApps(ctx, clients) - if err != nil { - return SelectedApp{}, err - } - - if tokenFlag != "" { - tokenAuth, err = filterAuthsByToken(ctx, clients, teamApps) - if err != nil { - return SelectedApp{}, err - } - - selectedTeam = teamApps[tokenAuth.TeamID] - if types.IsAppID(appFlag) && - selectedTeam.Hosted.App.AppID != appFlag && - selectedTeam.Local.App.AppID != appFlag { - return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound) - } - if status == ShowInstalledAppsOnly && - !appExists(selectedTeam.Hosted.App) && - !appExists(selectedTeam.Local.App) { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - if !types.IsAppID(appFlag) { - clients.IO.PrintInfo(ctx, false, style.Secondary("Selecting team '%s' with token belonging to '%s'..."), selectedTeam.authOrAppTeamDomain(), selectedTeam.Auth.TeamDomain) - } - } else if teamFlag != "" { - selectedTeam, err = filterByTeamFlag(teamApps, teamFlag) - if err != nil { - return SelectedApp{}, err - } - - if types.IsTeamID(teamFlag) { - clients.IO.PrintInfo(ctx, false, style.Secondary("Selecting team '%s' with token belonging to '%s'..."), selectedTeam.authOrAppTeamDomain(), selectedTeam.Auth.TeamDomain) - } - } else if !types.IsAppID(appFlag) { - selectedTeam, err = selectAppWorkspace(ctx, clients, teamApps, status) - if err != nil { - return SelectedApp{}, err - } - } - - if appFlag != "" { - if types.IsAppID(appFlag) { - // If an App ID (e.g. A12345) is supplied we filter the workspaceApps using the App ID - selectedApp, err = filterByAppID(teamApps, appFlag) - if err != nil { - return SelectedApp{}, err - } - - // User team selection via --token and or --team flags doesn't match the user app selection - // via --app flag - if selectedTeam.Auth.TeamID != "" && (selectedApp.App.TeamID != selectedTeam.Auth.TeamID && - selectedApp.App.EnterpriseID != selectedTeam.Auth.TeamID) { - return SelectedApp{}, slackerror.New(slackerror.ErrAppAuthTeamMismatch) - } - } else if types.IsAppFlagDeploy(appFlag) { - selectedApp = selectedTeam.Hosted - } else if types.IsAppFlagLocal(appFlag) { - selectedApp = selectedTeam.Local - } else { - return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag) - } - - if status == ShowInstalledAppsOnly && !appExists(selectedApp.App) { - if types.IsAppFlagEnvironment(appFlag) { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - if !appExists(selectedApp.App) || selectedApp.App.IsNew() { - return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound). - WithMessage("App \"%s\" not found", appFlag) - } - } - } else { - selectedApp, err = selectAppEnvironment(ctx, clients, selectedTeam, status) - if err != nil { - return SelectedApp{}, err - } - } - - // Use provided authentication information if available - if clients.Config.TokenFlag != "" { - selectedApp.Auth = tokenAuth - } - - // Validate auth for the selected app - err = validateAuth(ctx, clients, &selectedApp.Auth) - if err != nil { - return selectedApp, err - } - - clients.Auth().SetSelectedAuth(ctx, selectedApp.Auth, clients.Config, clients.Os) - return selectedApp, nil + return flatAppSelectPrompt(ctx, clients, ShowAllEnvironments, status) } // flatTeamSelectPrompt shows choices for authenticated teams @@ -1497,7 +1090,6 @@ func flatTeamSelectPrompt( // TeamAppSelectPrompt prompts the user to select an app from a specified team environment, // returning the selected app. This app might require installation before use if `status == ShowAllApps`. func TeamAppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, env AppEnvironmentType, status AppInstallStatus) (SelectedApp, error) { - var teamFlag = clients.Config.TeamFlag var appFlag = clients.Config.AppFlag var tokenFlag = clients.Config.TokenFlag @@ -1536,155 +1128,7 @@ func TeamAppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, env return selection, nil } - if clients.Config.WithExperimentOn(experiment.BoltFrameworks) { - return flatAppSelectPrompt(ctx, clients, env, status) - } - - // Get all apps - teamApps, err := getTeamApps(ctx, clients) - if err != nil { - return SelectedApp{}, err - } - - // Shortcut selections if a --token flag is provided - if tokenFlag != "" { - var selection SelectedApp - var err error - selection.Auth, err = filterAuthsByToken(ctx, clients, teamApps) - if err != nil { - return SelectedApp{}, err - } - clients.IO.PrintDebug(ctx, "selecting team '%s' based on the token value", selection.Auth.TeamDomain) - - teamApps := teamApps[selection.Auth.TeamID] - switch env { - case ShowHostedOnly: - selection.App = teamApps.Hosted.App - case ShowLocalOnly: - selection.App = teamApps.Local.App - } - if status == ShowInstalledAppsOnly && - selection.App.InstallStatus != types.AppStatusInstalled { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - clients.Auth().SetSelectedAuth(ctx, selection.Auth, clients.Config, clients.Os) - return selection, nil - } - - // When the --team flag is given, lookup the auth associated with the workspace - if teamFlag != "" { - // Find installed apps with a matching workspace flag - filteredByTeamFlag, err := filterByTeamFlag(teamApps, teamFlag) - if err != nil { - return SelectedApp{}, err - } - - // Choose the app environment specific to this prompt - var selection SelectedApp - var appEnvironment string - - switch env { - case ShowHostedOnly: - selection = filteredByTeamFlag.Hosted - appEnvironment = "Deployed" - if appFlag != "" && appFlag == filteredByTeamFlag.Local.App.AppID { - return SelectedApp{}, slackerror.New(slackerror.ErrLocalAppNotSupported) - } - case ShowLocalOnly: - selection = filteredByTeamFlag.Local - appEnvironment = "Local" - if appFlag != "" && appFlag == filteredByTeamFlag.Hosted.App.AppID { - return SelectedApp{}, slackerror.New(slackerror.ErrDeployedAppNotSupported) - } - } - - // Error if the app flag is an app ID and does not match an installed app - if types.IsAppID(appFlag) && appFlag != selection.App.AppID { - return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound). - WithMessage("%s app \"%s\" not found in team \"%s\"", - appEnvironment, appFlag, clients.Config.TeamFlag) - } - - // If we only want to show installed apps but there are none, error out - if status == ShowInstalledAppsOnly && !appExists(selection.App) { - if env == ShowLocalOnly { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired). - WithRemediation("Install the app to a workspace with %s", style.Commandf("run", false)) - } - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - - if selection.Auth.Token == "" { - return SelectedApp{}, slackerror.New(slackerror.ErrCredentialsNotFound). - WithMessage("No credentials found for team \"%s\"", filteredByTeamFlag.authOrAppTeamDomain()) - } - - clients.Auth().SetSelectedAuth(ctx, selection.Auth, clients.Config, clients.Os) - return selection, err - } - - // When the --app flag is given, lookup the auth associated with the app - if appFlag != "" { - - // Require that the flag is an app ID - if !types.IsAppID(appFlag) { - return SelectedApp{}, slackerror.New(slackerror.ErrTeamFlagRequired). - WithRemediation("Choose a workspace with %s or %s\nOr specify the app with %s", - style.Highlight("--team "), style.Highlight("--team "), style.Highlight("--app ")) - } - - // Find the app with a matching app ID - selection, err := filterByAppID(teamApps, appFlag) - if err != nil { - return SelectedApp{}, err - } - - var appEnvironment string - switch env { - case ShowHostedOnly: - if appExists(selection.App) && selection.App.IsDev { - return SelectedApp{}, slackerror.New(slackerror.ErrLocalAppNotSupported) - } - appEnvironment = "Deployed" - case ShowLocalOnly: - if appExists(selection.App) && !selection.App.IsDev { - return SelectedApp{}, slackerror.New(slackerror.ErrDeployedAppNotSupported) - } - appEnvironment = "Local" - } - - // Error if the app flag does not match the workspace app OR installation is required but the app is not installed - if appFlag != selection.App.AppID || (status == ShowInstalledAppsOnly && (!appExists(selection.App) || selection.App.IsNew())) { - return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound). - WithMessage("%s app \"%s\" not found", appEnvironment, appFlag) - } - - // Set the auth context and return the app - clients.Auth().SetSelectedAuth(ctx, selection.Auth, clients.Config, clients.Os) - return selection, nil - } - - selectedDomain, selection, err := selectTeamApp(ctx, clients, teamApps, workspaceOptions{ - environment: env, - installStatus: status, - }) - if err != nil { - return SelectedApp{}, err - } - - // Validate auth for the selected app - err = validateAuth(ctx, clients, &selection.Auth) - if err != nil { - return selection, err - } - - if auth, err := clients.Auth().AuthWithTeamID(ctx, selection.Auth.TeamID); err == nil { - clients.Auth().SetSelectedAuth(ctx, auth, clients.Config, clients.Os) - return selection, err - } - - return SelectedApp{}, slackerror.New(slackerror.ErrCredentialsNotFound). - WithMessage("No credentials found for team \"%s\"", selectedDomain) + return flatAppSelectPrompt(ctx, clients, env, status) } // OrgSelectWorkspacePrompt prompts the user to select a single workspace to grant app access to, or grant all workspaces within the org. diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 4b282b25..22f9f3f7 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -15,7 +15,6 @@ package prompts import ( - "context" "errors" "fmt" "testing" @@ -574,63 +573,6 @@ func Test_FilterAuthsByToken_Flags(t *testing.T) { // AppSelectPrompt tests // -func TestPrompt_AppSelectPrompt_SelectedAuthExpired_UserReAuthenticates(t *testing.T) { - // Setup - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - // Auth is present but invalid - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - mockReauthentication(clientsMock) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 1, - }, nil) - - clientsMock.IO.On(SelectPrompt, mock.Anything, "Select a team", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - err := clients.AppClient().SaveDeployed(ctx, deployedTeam1InstalledApp) - require.NoError(t, err) - - // Execute test - - selection, err := AppSelectPrompt(ctx, clients, ShowAllApps) - require.NoError(t, err) - selection.Auth.LastUpdated = time.Time{} // ignore time for this test - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - clientsMock.API.AssertCalled(t, "ExchangeAuthTicket", mock.Anything, mock.Anything, mock.Anything, mock.Anything) -} - -func TestPrompt_AppSelectPrompt_AuthsNoApps(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - clientsMock.AddDefaultMocks() - - // Execute test - selectedApp, err := AppSelectPrompt(ctx, clients, AppInstallStatus(ShowInstalledAppsOnly)) - require.Equal(t, selectedApp, SelectedApp{}) - require.Error(t, err, slackerror.New(slackerror.ErrInstallationRequired)) -} - func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { tests := map[string]struct { tokenFlag string @@ -720,875 +662,85 @@ func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { } } -func TestPrompt_AppSelectPrompt_AuthsWithDeployedAppInstalled_ShowAllApps(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - }) - require.NoError(t, err) - - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 1, - }, nil) - - // Execute test - selectedApp, err := AppSelectPrompt(ctx, clients, ShowAllApps) - require.NoError(t, err) - - app, err := clients.AppClient().GetDeployed(ctx, team1TeamID) - require.NoError(t, err) - app.InstallStatus = types.AppStatusInstalled - require.Equal(t, SelectedApp{App: app, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, selectedApp) -} - -func TestPrompt_AppSelectPrompt_AuthsWithDeployedAppInstalled_ShowInstalledAppsOnly(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - // Installed app - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - }) - require.NoError(t, err) - - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 0, - }, nil) - - // Execute test - selectedApp, err := AppSelectPrompt(ctx, clients, ShowInstalledAppsOnly) - require.NoError(t, err) - - app, err := clients.AppClient().GetDeployed(ctx, team1TeamID) - app.InstallStatus = types.AppStatusInstalled - require.NoError(t, err) - require.Equal(t, selectedApp, SelectedApp{App: app, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}) -} - -func TestPrompt_AppSelectPrompt_AuthsWithDeployedAppInstalled_InstalledAppOnly_Flags(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Installed app - deployedApp := types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - } - - err := clients.AppClient().SaveDeployed(ctx, deployedApp) - require.NoError(t, err) - - // Execute tests - tests := []struct { - title string - appFlag string - teamFlag string - err *slackerror.Error - selection SelectedApp +func TestPrompt_AppSelectPrompt_GetApps(t *testing.T) { + tests := map[string]struct { + mockAppsSavedDeployed []types.App + mockAppsSavedDeployedError error + mockAppsSavedLocal []types.App + mockAppsSavedLocalError error + mockAuths []types.SlackAuth + mockEnterprise1SavedAuth types.SlackAuth + mockEnterprise1SavedAuthError error + mockEnterprise2SavedAuth types.SlackAuth + mockEnterprise2SavedAuthError error + mockTeam1SavedAuth types.SlackAuth + mockTeam1SavedAuthError error + mockTeam1SavedDeployed types.App + mockTeam1SavedDeployedError error + mockTeam1SavedLocal types.App + mockTeam1SavedLocalError error + mockTeam1Status api.GetAppStatusResult + mockTeam1StatusAppIDs []string + mockTeam1StatusError error + mockTeam2SavedAuth types.SlackAuth + mockTeam2SavedAuthError error + mockTeam2SavedDeployed types.App + mockTeam2SavedDeployedError error + mockTeam2SavedLocal types.App + mockTeam2SavedLocalError error + mockTeam2Status api.GetAppStatusResult + mockTeam2StatusAppIDs []string + mockTeam2StatusError error + expectedApps map[string]SelectedApp + expectedError error }{ - { - "standalone app ID", - "A1EXAMPLE01", - "", - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "standalone app environment", - "deployed", - "", - slackerror.New(slackerror.ErrCredentialsNotFound), - SelectedApp{}, - }, { - "standalone team ID", - "", - team1TeamDomain, - slackerror.New(slackerror.ErrInvalidAppFlag), - SelectedApp{}, - }, { - "app ID with matching team", - "A1EXAMPLE01", - team1TeamDomain, - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "app ID with mismatched team", - "A1EXAMPLE01", - team2TeamDomain, - slackerror.New(slackerror.ErrAppAuthTeamMismatch), - SelectedApp{}, - }, { - "unknown app ID", - "A1EXAMPLE23", - "", - slackerror.New(slackerror.ErrAppNotFound), - SelectedApp{}, - }, { - "flags for deployed environment on a team", - "deploy", - team1TeamDomain, - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "flags for local environment on a team", - "local", - team1TeamDomain, - slackerror.New(slackerror.ErrInstallationRequired), - SelectedApp{}, - }, { - "invalid app ID flag", - "brokenflag", - team1TeamDomain, - slackerror.New(slackerror.ErrInvalidAppFlag), - SelectedApp{}, - }, - } - - for _, test := range tests { - clientsMock.Config.AppFlag = test.appFlag - clientsMock.Config.TeamFlag = test.teamFlag - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Flag: true, - Option: test.teamFlag, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Flag: true, - Option: test.appFlag, - }, nil) - - selectedApp, err := AppSelectPrompt(ctx, clients, ShowInstalledAppsOnly) - if test.err == nil { - require.NoError(t, err, test.title) - - _, err := clients.AppClient().GetDeployed(ctx, test.selection.App.TeamID) - require.NoError(t, err, test.title) - require.Equal(t, selectedApp, test.selection, test.title) - } else if assert.Error(t, err, test.title) { - assert.Equal(t, test.err.Code, err.(*slackerror.Error).Code, test.title) - require.Equal(t, selectedApp, SelectedApp{}, test.title) - } - } -} - -func TestPrompt_AppSelectPrompt_AuthsWithBothEnvsInstalled_InstalledAppOnly_Flags(t *testing.T) { - ctx := slackcontext.MockContext(t.Context()) - - mockAuthTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - mockAuthTeam1.Token = team1Token - mockAuthTeam2 := fakeAuthsByTeamDomain[team2TeamDomain] - mockAuthTeam2.Token = team2Token - - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01", Installed: true}, - {AppID: "A1EXAMPLE02", Installed: true}, - }, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mockAuthTeam1.TeamID).Return(mockAuthTeam1, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mockAuthTeam2.TeamID).Return(mockAuthTeam2, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - - clientsMock.Auth.On(AuthWithToken, mock.Anything, team1Token). - Return(mockAuthTeam1, nil) - clientsMock.Auth.On(AuthWithToken, mock.Anything, team2Token). - Return(mockAuthTeam2, nil) - - clientsMock.AddDefaultMocks() - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Flag: true, - Option: "A1EXAMPLE02", - }, nil) - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Installed app team 1 deployed - deployedApp := types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - } - - err := clients.AppClient().SaveDeployed(ctx, deployedApp) - require.NoError(t, err) - - // Installed app team 1 local - localApp := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - UserID: team1UserID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - } - - err = clients.AppClient().SaveLocal(ctx, localApp) - require.NoError(t, err) - - // Execute tests - tests := []struct { - title string - appFlag string - teamFlag string - tokenFlag string - err *slackerror.Error - expectedSelection SelectedApp - }{ - { - "standalone app ID for deployed app", - "A1EXAMPLE01", - "", - "", - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "standalone token, select the deployed app", - "deploy", - "", - team1Token, - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "app environment and team domain for deployed app", - "deployed", - team1TeamDomain, - "", - nil, - SelectedApp{App: deployedApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "standalone app ID for local app", - "A1EXAMPLE02", - "", - "", - nil, - SelectedApp{App: localApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "app environment and team domain for local app", - "local", - team1TeamDomain, - "", - nil, - SelectedApp{App: localApp, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "mismatched app ID for team domain", - "A1EXAMPLE01", - team2TeamDomain, - "", - slackerror.New(slackerror.ErrAppAuthTeamMismatch), - SelectedApp{}, - }, { - "app environment not installed for team domain", - "local", - team2TeamDomain, - "", - slackerror.New(slackerror.ErrInstallationRequired), - SelectedApp{}, - }, { - "unknown team domain", - "local", - "team3", - "", - slackerror.New(slackerror.ErrTeamNotFound), - SelectedApp{}, - }, - } - - for _, test := range tests { - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - clients.Config.TokenFlag = test.tokenFlag - - actualSelected, err := AppSelectPrompt(ctx, clients, ShowInstalledAppsOnly) - actualSelected.Auth.LastUpdated = time.Time{} // ignore time for this test - - if test.err == nil { - require.NoError(t, err) - - // App should exist - _, err := clients.AppClient().GetDeployed(ctx, test.expectedSelection.App.TeamID) - require.NoError(t, err) - - if test.tokenFlag != "" { - assert.Equal(t, test.tokenFlag, actualSelected.Auth.Token, test.title, "should use provided token") - test.expectedSelection.Auth.Token = test.tokenFlag - } - - require.Equal(t, test.expectedSelection, actualSelected, test.title) - } else if assert.Error(t, err) { - assert.Equal(t, test.err.Code, err.(*slackerror.Error).Code) - require.Equal(t, SelectedApp{}, actualSelected) - } - } -} - -func TestPrompt_AppSelectPrompt_AuthsWithBothEnvsInstalled_MultiWorkspaceAllApps_Flags(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01", Installed: true}, - {AppID: "A1EXAMPLE02", Installed: true}, - {AppID: "A1EXAMPLE03", Installed: true}, - }}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Installed app - - // Deployed App Team 1 - deployedApp1 := types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - } - - err := clients.AppClient().SaveDeployed(ctx, deployedApp1) - require.NoError(t, err) - - // Local App Team 1 - localApp1 := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - UserID: team1UserID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - } - - err = clients.AppClient().SaveLocal(ctx, localApp1) - require.NoError(t, err) - - // Deployed App Team 2 - deployedApp2 := types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A1EXAMPLE03", - InstallStatus: types.AppStatusInstalled, - } - - err = clients.AppClient().SaveDeployed(ctx, deployedApp2) - require.NoError(t, err) - - // This should be a new app since we have not saved any pre-existing - // local app - localApp2, err := clients.AppClient().GetLocal(ctx, team2TeamID) - require.NoError(t, err) - require.True(t, localApp2.IsNew()) - - // Execute tests - tests := []struct { - appFlag string - teamFlag string - err *slackerror.Error - expectedSelection SelectedApp - }{ - { - "A1EXAMPLE01", - "", - nil, - SelectedApp{App: deployedApp1, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "A1EXAMPLE02", - "", - nil, - SelectedApp{App: localApp1, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "A1EXAMPLE03", - "", - nil, - SelectedApp{App: deployedApp2, Auth: fakeAuthsByTeamDomain[team2TeamDomain]}, - }, { - "deployed", - team1TeamDomain, - nil, - SelectedApp{App: deployedApp1, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "deploy", - team2TeamDomain, - nil, - SelectedApp{App: deployedApp2, Auth: fakeAuthsByTeamDomain[team2TeamDomain]}, - }, { - "local", - team1TeamDomain, - nil, - SelectedApp{App: localApp1, Auth: fakeAuthsByTeamDomain[team1TeamDomain]}, - }, { - "local", - team2TeamDomain, - nil, - SelectedApp{App: localApp2, Auth: fakeAuthsByTeamDomain[team2TeamDomain]}, - }, - } - - for _, test := range tests { - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - selectedApp, err := AppSelectPrompt(ctx, clients, ShowAllApps) - - if test.err == nil { - require.NoError(t, err) - - _, err := clients.AppClient().GetDeployed(ctx, test.expectedSelection.App.TeamID) - - require.NoError(t, err) - require.Equal(t, test.expectedSelection, selectedApp) - } else if assert.Error(t, err) { - assert.Equal(t, test.err.Code, err.(*slackerror.Error).Code) - require.Equal(t, selectedApp, SelectedApp{}) - } - } -} - -func TestPrompt_AppSelectPrompt_AuthsWithHostedInstalled_AllApps_CreateNew(t *testing.T) { - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01", Installed: true}, - {AppID: "A1EXAMPLE02", Installed: true}, - }}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Installed apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - // Create a new local app in team2 - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "Install to a new workspace", - Index: 1, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 0, - }, nil) - - // Execute test - selectedApp, err := AppSelectPrompt(ctx, clients, ShowAllApps) - require.NoError(t, err) - - app, err := clients.AppClient().GetLocal(ctx, team2TeamID) - require.NoError(t, err) - expected := SelectedApp{App: app, Auth: fakeAuthsByTeamDomain[team2TeamDomain]} - require.Equal(t, expected, selectedApp) -} - -func TestPrompt_AppSelectPrompt_ShowExpectedLabels(t *testing.T) { - - // Set up mocks - - setupClientsMock := func() *shared.ClientsMock { - clientsMock := shared.NewClientsMock() - auths := append(fakeAuthsByTeamDomainSlice, types.SlackAuth{ - TeamDomain: "team3", - TeamID: "T3", - UserID: "U3", - Token: "xoxe.xoxp-2-token", - }) - clientsMock.Auth.On(Auths, mock.Anything).Return(auths, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: deployedTeam1InstalledAppID, Installed: deployedTeam1AppIsInstalled}, - {AppID: localTeam1UninstalledAppID, Installed: localTeam1AppIsInstalled}, - {AppID: deployedTeam2UninstalledAppID, Installed: deployedTeam2AppIsInstalled}, - {AppID: localTeam2InstalledAppID, Installed: localTeam2AppIsInstalled}, - }, - }, nil) - clientsMock.AddDefaultMocks() - - return clientsMock - } - - saveApps := func(ctx context.Context, clients *shared.ClientFactory) { - // Save deployed apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: deployedTeam1InstalledAppID, - TeamID: team1TeamID, - }) - require.NoError(t, err) - err = clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: deployedTeam2UninstalledAppID, - TeamID: team2TeamID, - }) - require.NoError(t, err) - - // Save local apps - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: localTeam1UninstalledAppID, - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - }) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: localTeam2InstalledAppID, - TeamID: team2TeamID, - IsDev: true, - UserID: team2UserID, - }) - require.NoError(t, err) - } - - // Execute tests - - var tests = []struct { - name string - status AppInstallStatus - expectedTeamLabels []string - selectedTeamIndex int - expectedAppLabels []string - selectedAppIndex int - expectedApp func(ctx context.Context, clients *shared.ClientFactory) SelectedApp - }{ - { - name: "All apps; select local installed app", - status: ShowAllApps, - expectedTeamLabels: []string{ - style.TeamSelectLabel(team1TeamDomain, team1TeamID), - style.TeamSelectLabel(team2TeamDomain, team2TeamID), - style.Secondary(appInstallPromptNew), - }, - selectedTeamIndex: 1, - expectedAppLabels: []string{ - style.AppSelectLabel(Local, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - style.AppSelectLabel(Deployed, deployedTeam2UninstalledAppID, !deployedTeam2AppIsInstalled), - }, - selectedAppIndex: 0, - expectedApp: func(ctx context.Context, clients *shared.ClientFactory) SelectedApp { - app, _ := clients.AppClient().GetLocal(ctx, team2TeamID) - app.InstallStatus = types.AppStatusInstalled - return SelectedApp{ - App: app, - Auth: fakeAuthsByTeamDomain[team2TeamDomain], - } - }, - }, - { - name: "Installed apps only; select deployed installed app", - status: ShowInstalledAppsOnly, - expectedTeamLabels: []string{ - style.TeamSelectLabel(team1TeamDomain, team1TeamID), - style.TeamSelectLabel(team2TeamDomain, team2TeamID), - }, - selectedTeamIndex: 0, - expectedAppLabels: []string{ - style.AppSelectLabel(Deployed, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - }, - selectedAppIndex: 0, - expectedApp: func(ctx context.Context, clients *shared.ClientFactory) SelectedApp { - - app, _ := clients.AppClient().GetDeployed(ctx, team1TeamID) - app.InstallStatus = types.AppStatusInstalled - return SelectedApp{ - App: app, - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - } - }, - }, - { - name: "Installed apps only; select local installed app", - status: ShowInstalledAppsOnly, - expectedTeamLabels: []string{ - style.TeamSelectLabel(team1TeamDomain, team1TeamID), - style.TeamSelectLabel(team2TeamDomain, team2TeamID), - }, - selectedTeamIndex: 1, // should select team2 - expectedAppLabels: []string{ - style.AppSelectLabel(Local, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - }, - selectedAppIndex: 0, // should select the local app - expectedApp: func(ctx context.Context, clients *shared.ClientFactory) SelectedApp { - app, _ := clients.AppClient().GetLocal(ctx, team2TeamID) - app.InstallStatus = types.AppStatusInstalled - return SelectedApp{ - App: app, - Auth: fakeAuthsByTeamDomain[team2TeamDomain], - } - }, - }, - { - name: "Installed and uninstalled apps; select deployed uninstalled app", - status: ShowInstalledAndUninstalledApps, - expectedTeamLabels: []string{ - style.TeamSelectLabel(team1TeamDomain, team1TeamID), - style.TeamSelectLabel(team2TeamDomain, team2TeamID), - }, - selectedTeamIndex: 1, - expectedAppLabels: []string{ - style.AppSelectLabel(Local, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - style.AppSelectLabel(Deployed, deployedTeam2UninstalledAppID, !deployedTeam2AppIsInstalled), - }, - selectedAppIndex: 1, - expectedApp: func(ctx context.Context, clients *shared.ClientFactory) SelectedApp { - - app, _ := clients.AppClient().GetDeployed(ctx, team2TeamID) - app.InstallStatus = types.AppStatusUninstalled - return SelectedApp{ - App: app, - Auth: fakeAuthsByTeamDomain[team2TeamDomain], - } - }, - }, - { - name: "Installed and non-existent apps; select deployed installed app", - status: ShowInstalledAndNewApps, - expectedTeamLabels: []string{ - style.TeamSelectLabel(team1TeamDomain, team1TeamID), - style.TeamSelectLabel(team2TeamDomain, team2TeamID), - style.Secondary(appInstallPromptNew), - }, - selectedTeamIndex: 0, - expectedAppLabels: []string{ - style.AppSelectLabel(Deployed, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - }, - selectedAppIndex: 0, - expectedApp: func(ctx context.Context, clients *shared.ClientFactory) SelectedApp { - - app, _ := clients.AppClient().GetDeployed(ctx, team1TeamID) - app.InstallStatus = types.AppStatusInstalled - return SelectedApp{ - App: app, - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - } - }, - }, - } - - for _, test := range tests { - ctx := slackcontext.MockContext(t.Context()) - clientsMock := setupClientsMock() - - // On select a team, choose - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, test.expectedTeamLabels, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.expectedTeamLabels[test.selectedTeamIndex], - Index: test.selectedTeamIndex, - }, nil) - - // On chosen deployed or local - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", test.expectedAppLabels, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.expectedAppLabels[test.selectedAppIndex], - Index: test.selectedAppIndex, - }, nil) - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - saveApps(ctx, clients) - - selectedApp, err := AppSelectPrompt(ctx, clients, test.status) - require.NoError(t, err) - - expectedApp := test.expectedApp(ctx, clients) - require.Equal(t, expectedApp, selectedApp) - } -} - -func TestPrompt_AppSelectPrompt_GetApps(t *testing.T) { - tests := map[string]struct { - mockAppsSavedDeployed []types.App - mockAppsSavedDeployedError error - mockAppsSavedLocal []types.App - mockAppsSavedLocalError error - mockAuths []types.SlackAuth - mockEnterprise1SavedAuth types.SlackAuth - mockEnterprise1SavedAuthError error - mockEnterprise2SavedAuth types.SlackAuth - mockEnterprise2SavedAuthError error - mockTeam1SavedAuth types.SlackAuth - mockTeam1SavedAuthError error - mockTeam1SavedDeployed types.App - mockTeam1SavedDeployedError error - mockTeam1SavedLocal types.App - mockTeam1SavedLocalError error - mockTeam1Status api.GetAppStatusResult - mockTeam1StatusAppIDs []string - mockTeam1StatusError error - mockTeam2SavedAuth types.SlackAuth - mockTeam2SavedAuthError error - mockTeam2SavedDeployed types.App - mockTeam2SavedDeployedError error - mockTeam2SavedLocal types.App - mockTeam2SavedLocalError error - mockTeam2Status api.GetAppStatusResult - mockTeam2StatusAppIDs []string - mockTeam2StatusError error - expectedApps map[string]SelectedApp - expectedError error - }{ - "returns deployed and local apps with matching auths": { - mockAuths: fakeAuthsByTeamDomainSlice, - mockAppsSavedDeployed: []types.App{ - deployedTeam1InstalledApp, - deployedTeam2UninstalledApp, - }, - mockAppsSavedLocal: []types.App{ - localTeam1UninstalledApp, - localTeam2InstalledApp, - }, - mockTeam1StatusAppIDs: []string{ - deployedTeam1InstalledAppID, - localTeam1UninstalledAppID, - }, - mockTeam1Status: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - deployedTeam1InstalledAppStatus, - localTeam1UninstallAppStatus, - }, - }, - mockTeam2StatusAppIDs: []string{ - deployedTeam2UninstalledAppID, - localTeam2InstalledAppID, - }, - mockTeam2Status: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - deployedTeam2UninstalledAppStatus, - localTeam2InstalledAppStatus, - }, - }, - expectedApps: map[string]SelectedApp{ - deployedTeam1InstalledAppID: { - App: deployedTeam1InstalledApp, - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - }, - localTeam1UninstalledAppID: { - App: localTeam1UninstalledApp, - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - }, - deployedTeam2UninstalledAppID: { - App: deployedTeam2UninstalledApp, - Auth: fakeAuthsByTeamDomain[team2TeamDomain], - }, - localTeam2InstalledAppID: { - App: localTeam2InstalledApp, - Auth: fakeAuthsByTeamDomain[team2TeamDomain], - }, + "returns deployed and local apps with matching auths": { + mockAuths: fakeAuthsByTeamDomainSlice, + mockAppsSavedDeployed: []types.App{ + deployedTeam1InstalledApp, + deployedTeam2UninstalledApp, + }, + mockAppsSavedLocal: []types.App{ + localTeam1UninstalledApp, + localTeam2InstalledApp, + }, + mockTeam1StatusAppIDs: []string{ + deployedTeam1InstalledAppID, + localTeam1UninstalledAppID, + }, + mockTeam1Status: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{ + deployedTeam1InstalledAppStatus, + localTeam1UninstallAppStatus, + }, + }, + mockTeam2StatusAppIDs: []string{ + deployedTeam2UninstalledAppID, + localTeam2InstalledAppID, + }, + mockTeam2Status: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{ + deployedTeam2UninstalledAppStatus, + localTeam2InstalledAppStatus, + }, + }, + expectedApps: map[string]SelectedApp{ + deployedTeam1InstalledAppID: { + App: deployedTeam1InstalledApp, + Auth: fakeAuthsByTeamDomain[team1TeamDomain], + }, + localTeam1UninstalledAppID: { + App: localTeam1UninstalledApp, + Auth: fakeAuthsByTeamDomain[team1TeamDomain], + }, + deployedTeam2UninstalledAppID: { + App: deployedTeam2UninstalledApp, + Auth: fakeAuthsByTeamDomain[team2TeamDomain], + }, + localTeam2InstalledAppID: { + App: localTeam2InstalledApp, + Auth: fakeAuthsByTeamDomain[team2TeamDomain], + }, }, }, "returns enterprise workspace apps with matching auths": { @@ -1789,7 +941,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { expectedStdout string expectedStderr string }{ - "selects a saved applications using prompts": { + "returns a saved applications using prompts": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ { @@ -1841,7 +993,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, - "creates new application if selected": { + "returns new application if selected": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{}, mockManifestSource: config.ManifestSourceLocal, @@ -1896,7 +1048,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { }}). WithRemediation("To learn more run: %s", style.Commandf("app list", false)), }, - "creates new application for app environment flag and team id flag if not app saved": { + "returns new application for app environment flag and team id flag if not app saved": { mockAuths: fakeAuthsByTeamDomainSlice, mockFlagApp: "deployed", mockFlagTeam: team1TeamID, @@ -1932,7 +1084,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team2TeamDomain], }, }, - "filters deployed apps for app environment flag before selection": { + "returns filtered deployed apps for app environment flag before selection": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ { @@ -1980,7 +1132,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team2TeamDomain], }, }, - "filters local apps for app environment flag before selection": { + "returns filtered local apps for app environment flag before selection": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ { @@ -2100,6 +1252,8 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, + // "returns selection for app id flag and team id flag after reauthentication": { + // }, "errors if app id flag has a team id flag that does not match": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ @@ -2235,7 +1389,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, - "creates new application with token flag and team id flag if app not saved": { + "returns new application with team id flag and token flag if app not saved": { mockAuthWithToken: fakeAuthsByTeamDomain[team1TeamDomain], mockAuthWithTeamIDError: slackerror.New(slackerror.ErrCredentialsNotFound), mockAuthWithTeamIDTeamID: team1TeamID, @@ -2469,2314 +1623,178 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Flag: tt.appPromptResponseFlag, Prompt: tt.appPromptResponsePrompt, Option: tt.appPromptResponseOption, - Index: tt.appPromptResponseIndex, - }, - nil, - ) - clientsMock.AddDefaultMocks() - projectConfigMock := config.NewProjectConfigMock() - projectConfigMock.On( - "GetManifestSource", - mock.Anything, - ).Return( - tt.mockManifestSource, - nil, - ) - clientsMock.Config.AppFlag = tt.mockFlagApp - clientsMock.Config.ProjectConfig = projectConfigMock - clientsMock.Config.TeamFlag = tt.mockFlagTeam - clientsMock.Config.TokenFlag = tt.mockFlagToken - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - for _, app := range tt.mockAppsDeployed { - err := clients.AppClient().SaveDeployed(ctx, app) - require.NoError(t, err) - } - for _, app := range tt.mockAppsLocal { - err := clients.AppClient().SaveLocal(ctx, app) - require.NoError(t, err) - } - selectedApp, err := flatAppSelectPrompt(ctx, clients, tt.appPromptConfigEnvironment, tt.appPromptConfigStatus) - require.Equal(t, tt.expectedError, err) - require.Equal(t, tt.expectedSelection, selectedApp) - require.Contains(t, clientsMock.GetStdoutOutput(), tt.expectedStdout) - require.Contains(t, clientsMock.GetStderrOutput(), tt.expectedStderr) - }) - } -} - -// -// TeamAppSelectPrompt tests -// - -func TestPrompt_TeamAppSelectPrompt_SelectedAuthExpired_UserReAuthenticates(t *testing.T) { - // Setup - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - // Auth is present but invalid - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - mockReauthentication(clientsMock) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - err := clients.AppClient().SaveDeployed(ctx, deployedTeam1InstalledApp) - require.NoError(t, err) - - // Execute test - - selection, err := TeamAppSelectPrompt(ctx, clients, ShowHostedOnly, ShowAllApps) - require.NoError(t, err) - selection.Auth.LastUpdated = time.Time{} // ignore time for this test - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - clientsMock.API.AssertCalled(t, "ExchangeAuthTicket", mock.Anything, mock.Anything, mock.Anything, mock.Anything) -} - -func TestPrompt_TeamAppSelectPrompt_NoAuths_UserReAuthenticates(t *testing.T) { - // Setup - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - // No auths present - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{}, nil) - mockReauthentication(clientsMock) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - err := clients.AppClient().SaveDeployed(ctx, deployedTeam1InstalledApp) - require.NoError(t, err) - - // Execute test - - selection, err := TeamAppSelectPrompt(ctx, clients, ShowHostedOnly, ShowAllApps) - require.NoError(t, err) - selection.Auth.LastUpdated = time.Time{} // ignore time for this test - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) -} - -func TestPrompt_TeamAppSelectPrompt_TokenAppFlag(t *testing.T) { - tests := map[string]struct { - tokenFlag string - tokenAuth types.SlackAuth - appFlag string - appStatus api.GetAppStatusResult - statusErr error - saveLocal []types.App - selectEnv AppEnvironmentType - selectStatus AppInstallStatus - expectedApp SelectedApp - expectedErr error - }{ - "error if an error occurred while collecting app info": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: localTeam1UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{}, - statusErr: slackerror.New(slackerror.ErrAppNotFound), - selectEnv: ShowHostedOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrAppNotFound), - }, - "continue if a saved local app is used for a deployed only prompt": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: localTeam1UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: localTeam1UninstalledAppID, - Installed: localTeam1AppIsInstalled, - Hosted: false, - }}, - }, - statusErr: nil, - saveLocal: []types.App{localTeam1UninstalledApp}, - selectEnv: ShowHostedOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{ - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - App: localTeam1UninstalledApp, - }, - expectedErr: slackerror.New(slackerror.ErrLocalAppNotSupported), - }, - "error if a deployed app is used for a local only prompt": { - tokenFlag: team2Token, - tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], - appFlag: deployedTeam2UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam2UninstalledApp.AppID, - Installed: deployedTeam2AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowLocalOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrDeployedAppNotSupported), - }, - "error if an uninstalled app is used for an installed only prompt": { - tokenFlag: team2Token, - tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], - appFlag: deployedTeam2UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam2UninstalledApp.AppID, - Installed: deployedTeam2AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowHostedOnly, - selectStatus: ShowInstalledAppsOnly, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrInstallationRequired), - }, - "returns known information about the request app": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: deployedTeam1InstalledAppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam1InstalledAppID, - Installed: deployedTeam1AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowHostedOnly, - selectStatus: ShowInstalledAppsOnly, - expectedApp: SelectedApp{ - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - App: deployedTeam1InstalledApp, - }, - expectedErr: nil, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(AuthWithToken, mock.Anything, test.tokenFlag). - Return(test.tokenAuth, nil) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(test.appStatus, test.statusErr) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - for _, app := range test.saveLocal { - err := clients.AppClient().SaveLocal(ctx, app) - require.NoError(t, err) - } - clients.Config.TokenFlag = test.tokenFlag - clients.Config.AppFlag = test.appFlag - - selection, err := TeamAppSelectPrompt(ctx, clients, test.selectEnv, test.selectStatus) - - if test.statusErr != nil && assert.Error(t, err) { - require.Equal(t, test.statusErr, err) - } else if test.expectedErr != nil && assert.Error(t, err) { - require.Equal(t, test.expectedErr, err) - } else { - require.NoError(t, err) - assert.Equal(t, test.expectedApp.Auth, selection.Auth) - expectedApp := test.expectedApp.App - expectedApp.UserID = test.expectedApp.Auth.UserID - assert.Equal(t, expectedApp, selection.App) - } - }) - } -} - -func TestPrompt_TeamAppSelectPrompt_TeamNotFoundFor_TeamFlag(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = "unauthed-domain" // resets each loop - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - if assert.Error(t, err) { - assert.Equal(t, slackerror.ErrTeamNotFound, err.(*slackerror.Error).Code) - } - require.Equal(t, types.SlackAuth{}, selection.Auth) - } -} - -func TestPrompt_TeamAppSelectPrompt_NoApps(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(fakeAuthsByTeamDomain[team1TeamDomain], nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team2TeamID).Return(fakeAuthsByTeamDomain[team2TeamDomain], nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install the app to team1 - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: appInstallPromptNew, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: appInstallPromptNew, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 0, - }, nil) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors if installation is required, otherwise expect a successful choice - if test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_NoInstalls_TeamFlagDomain(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team1TeamDomain - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors if installation is required, otherwise expect a successful choice - if test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_NoInstalls_TeamFlagID(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team2TeamID - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors if installation is required, otherwise expect a successful choice - if test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_NoInstalls_Flags(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Execute tests - tests := []struct { - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - err *slackerror.Error - expectedAuth types.SlackAuth - }{ - { - ShowHostedOnly, - ShowInstalledAppsOnly, - "deploy", - "", - slackerror.New(slackerror.ErrTeamFlagRequired), - types.SlackAuth{}, - }, { - ShowLocalOnly, - ShowInstalledAppsOnly, - "local", - "", - slackerror.New(slackerror.ErrTeamFlagRequired), - types.SlackAuth{}, - }, { - ShowHostedOnly, - ShowInstalledAppsOnly, - "deploy", - team1TeamDomain, - slackerror.New(slackerror.ErrInstallationRequired), - types.SlackAuth{}, - }, { - ShowLocalOnly, - ShowInstalledAppsOnly, - "local", - team2TeamDomain, - slackerror.New(slackerror.ErrInstallationRequired), - types.SlackAuth{}, - }, { - ShowHostedOnly, - ShowAllApps, - "local", - "", - slackerror.New(slackerror.ErrLocalAppNotSupported), - types.SlackAuth{}, - }, { - ShowLocalOnly, - ShowAllApps, - "deployed", - "", - slackerror.New(slackerror.ErrDeployedAppNotSupported), - types.SlackAuth{}, - }, { - ShowHostedOnly, - ShowAllApps, - "A1234567890", - "", - slackerror.New(slackerror.ErrAppNotFound), - types.SlackAuth{}, - }, { - ShowLocalOnly, - ShowAllApps, - "A1234567890", - "", - slackerror.New(slackerror.ErrAppNotFound), - types.SlackAuth{}, - }, { - ShowLocalOnly, - ShowAllApps, - "local", - team1TeamDomain, - nil, - fakeAuthsByTeamDomain[team1TeamDomain], - }, { - ShowHostedOnly, - ShowAllApps, - "deploy", - team2TeamDomain, - nil, - fakeAuthsByTeamDomain[team2TeamDomain], - }, - } - - for _, test := range tests { - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - if test.err == nil { - require.NoError(t, err) - - _, err := clients.AppClient().GetDeployed(ctx, test.expectedAuth.TeamID) - require.NoError(t, err) - require.Equal(t, test.expectedAuth, selection.Auth) - } else if assert.Error(t, err) { - assert.Equal(t, test.err.Code, err.(*slackerror.Error).Code) - require.Equal(t, types.SlackAuth{}, selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_TokenFlag(t *testing.T) { - appInstallStatus := []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01", Installed: true}, - {AppID: "A1EXAMPLE02", Installed: true}, - {AppID: "A1EXAMPLE04", Installed: false}, - } - installedHostedApp := types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - } - installedLocalApp := types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - InstallStatus: types.AppStatusInstalled, - UserID: "U1", - IsDev: true, - } - uninstalledHostedApp := types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A1EXAMPLE03", - InstallStatus: types.AppInstallationStatusUnknown, - } - uninstalledLocalApp := types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A1EXAMPLE04", - InstallStatus: types.AppStatusUninstalled, - UserID: "U2", - IsDev: true, - } - - var tests = map[string]struct { - env AppEnvironmentType - status AppInstallStatus - teamDomain string - token string - app types.App - err *slackerror.Error - }{ - "return the hosted app of a valid token": { - ShowHostedOnly, - ShowAllApps, - team1TeamDomain, - team1Token, - installedHostedApp, - nil, - }, - "error when installation is required": { - ShowHostedOnly, - ShowInstalledAppsOnly, - team2TeamDomain, - team2Token, - uninstalledHostedApp, - slackerror.New(slackerror.ErrInstallationRequired), - }, - "return an uninstalled app if allowed": { - ShowLocalOnly, - ShowAllApps, - team2TeamDomain, - team2Token, - uninstalledLocalApp, - nil, - }, - "return the local app of a valid token": { - ShowLocalOnly, - ShowInstalledAppsOnly, - team1TeamDomain, - team1Token, - installedLocalApp, - nil, - }, - } - - for name, test := range tests { - ctx := slackcontext.MockContext(t.Context()) - - mockAuth := fakeAuthsByTeamDomain[test.teamDomain] - mockAuth.Token = test.token - - clientsMock := shared.NewClientsMock() - - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{Apps: appInstallStatus}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything). - Return(types.SlackAuth{}, slackerror.New(slackerror.ErrCredentialsNotFound)) - clientsMock.Auth.On(AuthWithToken, mock.Anything, test.token). - Return(mockAuth, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - var err error - err = clients.AppClient().SaveDeployed(ctx, installedHostedApp) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, installedLocalApp) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, uninstalledLocalApp) - require.NoError(t, err) - - clients.Config.TokenFlag = test.token - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - if test.err != nil && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, selection.Auth, fakeAuthsByTeamDomain[test.teamDomain], name) - require.Equal(t, selection.App, test.app, name) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_HostedAppsOnly(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}, {AppID: "A124", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Select team2 - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team2TeamDomain, - Index: 1, - }, nil) - - // Install the app to team2 - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 1, - }, nil) - - // Installed apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A124", - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors if installation is required, otherwise expect a successful choice - if test.status == ShowInstalledAppsOnly && test.env == ShowLocalOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_HostedAppsOnly_TeamFlagDomain(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team1TeamDomain - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors when installation is required for local apps, otherwise expect a successful choice - if test.env == ShowLocalOnly && test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, selection.Auth, fakeAuthsByTeamDomain[team1TeamDomain]) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_HostedAppsOnly_TeamFlagID(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - InstallStatus: types.AppStatusInstalled, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team1TeamID - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors when installation is required for local apps, otherwise expect a successful choice - if test.env == ShowLocalOnly && test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_LocalAppsOnly(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A1EXAMPLE01", Installed: true}, {AppID: "A124", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(fakeAuthsByTeamDomain[team1TeamDomain], nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team2TeamID).Return(fakeAuthsByTeamDomain[team2TeamDomain], nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install the app to team1 - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: team1TeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 0, - }, nil) - - // Installed apps - err := clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: "dev", - AppID: "A1EXAMPLE01", - TeamID: team1TeamID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - UserID: team1UserID, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: "dev", - AppID: "A124", - TeamID: team2TeamID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - UserID: team2UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors if installation is required, otherwise expect a successful choice - if test.status == ShowInstalledAppsOnly && test.env == ShowHostedOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_LocalAppsOnly_TeamFlagDomain(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A124", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(fakeAuthsByTeamDomain[team1TeamDomain], nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team2TeamID).Return(fakeAuthsByTeamDomain[team2TeamDomain], nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - err := clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: "A124", - TeamID: team2TeamID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - UserID: team2UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team2TeamDomain - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors when installation is required for hosted apps, otherwise expect a successful choice - if test.env == ShowHostedOnly && test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_LocalAppsOnly_TeamFlagID(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{AppID: "A124", Installed: true}}, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(fakeAuthsByTeamDomain[team1TeamDomain], nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team2TeamID).Return(fakeAuthsByTeamDomain[team2TeamDomain], nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - err := clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: "A124", - TeamID: team2TeamID, - IsDev: true, - InstallStatus: types.AppStatusInstalled, - UserID: team2UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team2TeamID - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - // Check for errors when installation is required for hosted apps, otherwise expect a successful choice - if test.env == ShowHostedOnly && test.status == ShowInstalledAppsOnly && assert.Error(t, err) { - assert.Equal(t, slackerror.ErrInstallationRequired, err.(*slackerror.Error).Code) - } else { - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_AllApps(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01", Installed: true}, - {AppID: "A124", Installed: true}, - {AppID: "A1EXAMPLE01dev", Installed: true}, - {AppID: "A124dev", Installed: true}, - }, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Select team2 - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 1, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 1, - }, nil) - - // Install apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - }) - require.NoError(t, err) - - err = clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A124", - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01dev", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: "A124dev", - TeamID: team2TeamID, - IsDev: true, - UserID: team2UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - } -} - -func TestPrompt_TeamAppSelectPrompt_LegacyDevApps(t *testing.T) { - // Test to ensure that legacy apps.dev.json entries which have - // team_domain set as "dev" are overridden with the correct team_domain when the auth - // context is known - - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: "A1EXAMPLE01dev", Installed: true}, - {AppID: "A124dev", Installed: true}, - }, - }, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Select team2 - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 1, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: 1, - }, nil) - - err := clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: "dev", // mock legacy apps.dev.json which has teamDomain as 'dev' - AppID: "A1EXAMPLE01dev", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: "dev", // mock legacy apps.dev.json which has teamDomain as 'dev' - AppID: "A124dev", - TeamID: team2TeamID, - IsDev: true, - UserID: team2UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team2TeamDomain], selection.Auth) - - team1App, err := clients.AppClient().GetLocal(ctx, team1TeamID) - require.NoError(t, err) - // app team domain should be overridden - require.Equal(t, team1TeamDomain, team1App.TeamDomain) - require.NotEqual(t, "dev", team1App.TeamDomain) - - team2App, err := clients.AppClient().GetLocal(ctx, team2TeamID) - require.NoError(t, err) - // app team domain should be overridden from "dev" - // app team domain should be overridden - require.Equal(t, team2TeamDomain, team2App.TeamDomain) - require.NotEqual(t, "dev", team2App.TeamDomain) - } -} - -func TestPrompt_TeamAppSelectPrompt_ShowExpectedLabels(t *testing.T) { - - // Set up mocks - - setupClientsMock := func() *shared.ClientsMock { - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( - api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - {AppID: deployedTeam1InstalledAppID, Installed: deployedTeam1AppIsInstalled}, - {AppID: localTeam1UninstalledAppID, Installed: localTeam1AppIsInstalled}, - {AppID: deployedTeam2UninstalledAppID, Installed: deployedTeam2AppIsInstalled}, - {AppID: localTeam2InstalledAppID, Installed: localTeam2AppIsInstalled}, - }, - }, nil) - auths := append(fakeAuthsByTeamDomainSlice, types.SlackAuth{ - TeamDomain: "team3", - TeamID: "T3", - UserID: "U3", - Token: "xoxe.xoxp-2-token", - }) - clientsMock.Auth.On(Auths, mock.Anything).Return(auths, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - return clientsMock - } - - saveApps := func(ctx context.Context, clients *shared.ClientFactory) { - // Save deployed apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: deployedTeam1InstalledAppID, - TeamID: team1TeamID, - }) - require.NoError(t, err) - err = clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: deployedTeam2UninstalledAppID, - TeamID: team2TeamID, - }) - require.NoError(t, err) - - // Save local apps - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: localTeam1UninstalledAppID, - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - }) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team2TeamDomain, - AppID: localTeam2InstalledAppID, - TeamID: team2TeamID, - IsDev: true, - UserID: team2UserID, - }) - require.NoError(t, err) - } - - // Execute tests - - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - promptText string - expectedTeamLabels []string - selectedTeamIndex int - expectedTeamSelection string - }{ - { - ShowHostedOnly, - ShowAllApps, - "Choose a deployed environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, deployedTeam2UninstalledAppID, !deployedTeam2AppIsInstalled), - style.Secondary(appInstallPromptNew), - }, - 1, - team2TeamDomain, - }, - { - ShowHostedOnly, - ShowInstalledAppsOnly, - "Choose a deployed environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - }, - 0, - team1TeamDomain, - }, - { - ShowHostedOnly, - ShowInstalledAndNewApps, - "Choose a deployed environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - style.Secondary(appInstallPromptNew), - }, - 0, - team1TeamDomain, - }, - { - ShowHostedOnly, - ShowInstalledAndUninstalledApps, - "Choose a deployed environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, deployedTeam1InstalledAppID, !deployedTeam1AppIsInstalled), - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, deployedTeam2UninstalledAppID, !deployedTeam2AppIsInstalled), - }, - 1, - team2TeamDomain, - }, - { - ShowLocalOnly, - ShowAllApps, - "Choose a local environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, localTeam1UninstalledAppID, !localTeam1AppIsInstalled), - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - style.Secondary(appInstallPromptNew), - }, - 1, - team2TeamDomain, - }, - { - ShowLocalOnly, - ShowInstalledAppsOnly, - "Choose a local environment", - []string{ - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - }, - 0, - team2TeamDomain, - }, - { - ShowLocalOnly, - ShowInstalledAndNewApps, - "Choose a local environment", - []string{ - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - style.Secondary(appInstallPromptNew), - }, - 0, - team2TeamDomain, - }, - { - ShowLocalOnly, - ShowInstalledAndUninstalledApps, - "Choose a local environment", - []string{ - style.TeamAppSelectLabel(team1TeamDomain, team1TeamID, localTeam1UninstalledAppID, !localTeam1AppIsInstalled), - style.TeamAppSelectLabel(team2TeamDomain, team2TeamID, localTeam2InstalledAppID, !localTeam2AppIsInstalled), - }, - 0, - team1TeamDomain, - }, - } - - for _, test := range tests { - ctx := slackcontext.MockContext(t.Context()) - clientsMock := setupClientsMock() - clientsMock.IO.On(SelectPrompt, mock.Anything, test.promptText, test.expectedTeamLabels, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: mock.Anything, - Index: test.selectedTeamIndex, - }, nil) - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - saveApps(ctx, clients) - - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[test.expectedTeamSelection], selection.Auth) - } -} - -func TestPrompt_TeamAppSelectPrompt_AllApps_TeamFlagID(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - TeamID: team1TeamID, - }) - require.NoError(t, err) - - err = clients.AppClient().SaveLocal(ctx, types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - }) - require.NoError(t, err) - - // Perform tests - var tests = []struct { - env AppEnvironmentType - status AppInstallStatus - }{ - {ShowHostedOnly, ShowAllApps}, - {ShowHostedOnly, ShowInstalledAppsOnly}, - {ShowLocalOnly, ShowAllApps}, - {ShowLocalOnly, ShowInstalledAppsOnly}, - } - - for _, test := range tests { - clients.Config.TeamFlag = team1TeamID - selection, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - require.NoError(t, err) - require.Equal(t, fakeAuthsByTeamDomain[team1TeamDomain], selection.Auth) - } -} - -func TestPrompt_TeamAppSelectPrompt_AllApps_Flags(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Install apps - appTeam1Hosted := types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - } - err := clients.AppClient().SaveDeployed(ctx, appTeam1Hosted) - require.NoError(t, err) - - appTeam1Local := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - } - err = clients.AppClient().SaveLocal(ctx, appTeam1Local) - require.NoError(t, err) - - appTeam2Hosted := types.App{ - TeamDomain: team2TeamDomain, - TeamID: team2TeamID, - AppID: "A1EXAMPLE03", - } - err = clients.AppClient().SaveDeployed(ctx, appTeam2Hosted) - require.NoError(t, err) - - // Execute tests - tests := []struct { - desc string - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - err *slackerror.Error - expected SelectedApp - }{ - { - "valid flags: --app deploy --team team1", - ShowHostedOnly, - ShowInstalledAppsOnly, - "deploy", - team1TeamDomain, - nil, - SelectedApp{Auth: fakeAuthsByTeamDomain[team1TeamDomain], App: appTeam1Hosted}, - }, { - "valid flags: --app ", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE03", - "", - nil, - SelectedApp{Auth: fakeAuthsByTeamDomain[team2TeamDomain], App: appTeam2Hosted}, - }, { - "valid flags: --app local --team team1", - ShowLocalOnly, - ShowInstalledAppsOnly, - "local", - team1TeamDomain, - nil, - SelectedApp{Auth: fakeAuthsByTeamDomain[team1TeamDomain], App: appTeam1Local}, - }, { - "valid flags: --app ", - ShowLocalOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE02", - "", - nil, - SelectedApp{Auth: fakeAuthsByTeamDomain[team1TeamDomain], App: appTeam1Local}, - }, { - "valid flags: --app --team team1", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE01", - team1TeamDomain, - nil, - SelectedApp{Auth: fakeAuthsByTeamDomain[team1TeamDomain], App: appTeam1Hosted}, - }, { - "invalid flags: --app --team team2", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE01", - team2TeamDomain, - slackerror.New(slackerror.ErrAppNotFound), - SelectedApp{}, - }, { - "invalid flags: --app ", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE04", - "", - slackerror.New(slackerror.ErrAppNotFound), - SelectedApp{}, - }, { - "invalid flags: --app --team team2", - ShowLocalOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE02", - team2TeamDomain, - slackerror.New(slackerror.ErrAppNotFound), - SelectedApp{}, - }, { - "invalid flags for local only: --app ", - ShowLocalOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE01", - "", - slackerror.New(slackerror.ErrDeployedAppNotSupported), - SelectedApp{}, - }, { - "invalid flags for deploy only: --app ", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE02", - "", - slackerror.New(slackerror.ErrLocalAppNotSupported), - SelectedApp{}, - }, { - "invalid flags for local only: --app --team team1", - ShowLocalOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE01", - team1TeamDomain, - slackerror.New(slackerror.ErrDeployedAppNotSupported), - SelectedApp{}, - }, { - "invalid flags for deploy only: --app --team team1", - ShowHostedOnly, - ShowInstalledAppsOnly, - "A1EXAMPLE02", - team1TeamDomain, - slackerror.New(slackerror.ErrLocalAppNotSupported), - SelectedApp{}, - }, - } - - for _, test := range tests { - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - actualSelected, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - if test.err == nil { - require.NoError(t, err) - - _, err := clients.AppClient().GetDeployed(ctx, test.expected.Auth.TeamID) - require.NoError(t, err) - require.Equal(t, test.expected, actualSelected) - } else if assert.Error(t, err) { - assert.Equal(t, test.err.Code, err.(*slackerror.Error).Code, test.desc) - require.Equal(t, test.expected, actualSelected) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_AppSelectPrompt_EnterpriseWorkspaceApps_HasWorkspaceAuth(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - - // Auths - // Enterprise (org) Auth - // Mock the enterprise that Team1 belongs to - const enterprise1TeamDomain = "enterprise1" - const enterprise1TeamID = "E1" - const enterprise1UserID = "UE1" - const enterprise1Token = "xoxp-xoxe-12345" - - authEnterprise1 := types.SlackAuth{ - TeamID: enterprise1TeamID, - TeamDomain: enterprise1TeamDomain, - Token: enterprise1Token, - IsEnterpriseInstall: true, - UserID: enterprise1UserID, - EnterpriseID: enterprise1TeamID, - } - - // Set up team 1 to belong to enterprise 1 - authTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - authTeam1.EnterpriseID = authEnterprise1.TeamID - - // Return one auth - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(authTeam1, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, enterprise1TeamID).Return(authEnterprise1, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - - // - // This test uses a single auth - Mock the underlying auth - // - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{ - authTeam1, - }, nil) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Save apps - // Save a hosted and local enterprise workspace-level app - appTeam1Hosted := types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - EnterpriseID: enterprise1TeamID, - } - - appTeam1Local := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - EnterpriseID: enterprise1TeamID, - } - - err := clients.AppClient().SaveDeployed(ctx, appTeam1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appTeam1Local) - require.NoError(t, err) - - // Save a hosted and local enterprise / org level app - appEnterprise1Hosted := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE03", - EnterpriseID: enterprise1TeamID, - } - - appEnterprise1Local := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE04", - IsDev: true, - EnterpriseID: enterprise1TeamID, - } - - err = clients.AppClient().SaveDeployed(ctx, appEnterprise1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appEnterprise1Local) - require.NoError(t, err) - - // We won't mock standard workspace apps and auths in this test, as that - // Should be well covered by other tests in this file. - - // Test cases - // Execute tests - tests := []struct { - desc string - - expected SelectedApp - expectedError *slackerror.Error - - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - - authState []types.SlackAuth - authError error - - selectTeamDomain string - selectEnvironment string - }{ - { - // Test description - "When there is an existing workspace auth, workspace app returned with existing auth", - - // Expected result - SelectedApp{ - App: appTeam1Hosted, - Auth: authTeam1, - }, - nil, - - // Env + app install status - ShowHostedOnly, - ShowInstalledAppsOnly, - - // flags - "", - "", - - // Auth state - []types.SlackAuth{ - authTeam1, - }, - nil, - - // Selector preferences team 1 - team1TeamDomain, - "Deployed", - }, - } - - for _, test := range tests { - - // Set app flags - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - - // Return the auth state depending on test specs - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{ - authTeam1, - }, nil) - - clientsMock.AddDefaultMocks() - - // Finally we mock the select prompt based on test specs - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectTeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectEnvironment, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectEnvironment, - Index: 0, - }, nil) - // App selector mock - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 1, - }, nil) - - actualSelected1, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - actualSelected2, err2 := AppSelectPrompt(ctx, clients, test.status) - - if test.expectedError == nil { - require.NoError(t, err) - require.NoError(t, err2) - - // There's a valid auth for this auth record - _, err := clients.AppClient().GetDeployed(ctx, test.expected.Auth.TeamID) - require.NoError(t, err) - - require.Equal(t, test.expected, actualSelected1) - require.Equal(t, test.expected, actualSelected2) - } else if assert.Error(t, err) { - assert.Equal(t, test.expectedError.Code, err.(*slackerror.Error).Code, test.desc) - - require.Equal(t, test.expected, actualSelected1) - require.Equal(t, test.expected, actualSelected2) - } - } -} - -func TestPrompt_TeamAppSelectPrompt_AppSelectPrompt_EnterpriseWorkspaceApps_MissingWorkspaceAuth_MissingOrgAuth_UserReAuthenticates(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - - mockReauthentication(clientsMock) - - // Auths - // Enterprise (org) Auth - // Mock the enterprise that Team1 belongs to - const enterprise1TeamDomain = "enterprise1" - const enterprise1TeamID = "E1" - const enterprise1UserID = "UE1" - const enterprise1Token = "xoxp-xoxe-12345" - - authEnterprise1 := types.SlackAuth{ - TeamID: enterprise1TeamID, - TeamDomain: enterprise1TeamDomain, - Token: enterprise1Token, - IsEnterpriseInstall: true, - UserID: enterprise1UserID, - EnterpriseID: enterprise1TeamID, - } - - // Set up team 1 to belong to enterprise 1 - authTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - authTeam1.EnterpriseID = authEnterprise1.TeamID - - // Return one auth - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(authTeam1, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, enterprise1TeamID).Return(authEnterprise1, nil) - - // This test uses zero auths - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{}, nil) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Save apps - // Save a hosted and local enterprise workspace-level app - appTeam1Hosted := types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - EnterpriseID: enterprise1TeamID, - } - - appTeam1Local := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - EnterpriseID: enterprise1TeamID, - } - - err := clients.AppClient().SaveDeployed(ctx, appTeam1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appTeam1Local) - require.NoError(t, err) - - // Save a hosted and local enterprise / org level app - appEnterprise1Hosted := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE03", - EnterpriseID: enterprise1TeamID, - } - - appEnterprise1Local := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE04", - IsDev: true, - EnterpriseID: enterprise1TeamID, - } - - err = clients.AppClient().SaveDeployed(ctx, appEnterprise1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appEnterprise1Local) - require.NoError(t, err) - - // We won't mock standard workspace apps and auths in this test, as that - // Should be well covered by other tests in this file. - - // Test cases - // Execute tests - tests := []struct { - desc string - - expected SelectedApp - expectedError *slackerror.Error - - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - - authState []types.SlackAuth - authError error - - selectTeamDomain string - selectEnvironment string - }{ - { - // Test description - "When there is no existing workspace auth, returns empty SelectedApp with error", - - // Expected results - SelectedApp{Auth: fakeAuthsByTeamDomain[team1TeamDomain], App: appTeam1Hosted}, - nil, - - // Env + app install status - ShowHostedOnly, - ShowInstalledAppsOnly, - - // flags - "", - "", - - // Auth state - []types.SlackAuth{}, - nil, - - // Selector preferences team 1 - team1TeamDomain, - "Deployed", - }, - } - - for _, test := range tests { - - // Set app flags - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - - // Finally we mock the select prompt based on test specs - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectTeamDomain, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectEnvironment, - Index: 0, - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a local environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectEnvironment, - Index: 0, - }, nil) - - // App selector mock - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 1, - }, nil) - - actualSelected1, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - actualSelected2, err2 := AppSelectPrompt(ctx, clients, test.status) - - actualSelected1.Auth.LastUpdated = time.Time{} // ignore time for this test - actualSelected2.Auth.LastUpdated = time.Time{} // ignore time for this test - - require.NoError(t, err) - require.NoError(t, err2) - - // There's a valid auth for this auth record - _, err = clients.AppClient().GetDeployed(ctx, test.expected.Auth.TeamID) - require.NoError(t, err) - - require.Equal(t, test.expected, actualSelected1) - require.Equal(t, test.expected, actualSelected2) + Index: tt.appPromptResponseIndex, + }, + nil, + ) + clientsMock.AddDefaultMocks() + projectConfigMock := config.NewProjectConfigMock() + projectConfigMock.On( + "GetManifestSource", + mock.Anything, + ).Return( + tt.mockManifestSource, + nil, + ) + clientsMock.Config.AppFlag = tt.mockFlagApp + clientsMock.Config.ProjectConfig = projectConfigMock + clientsMock.Config.TeamFlag = tt.mockFlagTeam + clientsMock.Config.TokenFlag = tt.mockFlagToken + clients := shared.NewClientFactory(clientsMock.MockClientFactory()) + for _, app := range tt.mockAppsDeployed { + err := clients.AppClient().SaveDeployed(ctx, app) + require.NoError(t, err) + } + for _, app := range tt.mockAppsLocal { + err := clients.AppClient().SaveLocal(ctx, app) + require.NoError(t, err) + } + selectedApp, err := flatAppSelectPrompt(ctx, clients, tt.appPromptConfigEnvironment, tt.appPromptConfigStatus) + require.Equal(t, tt.expectedError, err) + require.Equal(t, tt.expectedSelection, selectedApp) + require.Contains(t, clientsMock.GetStdoutOutput(), tt.expectedStdout) + require.Contains(t, clientsMock.GetStderrOutput(), tt.expectedStderr) + }) } } -func TestPrompt_TeamAppSelectPrompt_EnterpriseWorkspaceApps_MissingWorkspaceAuth_HasOrgAuth(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - - // Auths - // Enterprise (org) Auth - // Mock the enterprise that Team1 belongs to - const enterprise1TeamDomain = "enterprise1" - const enterprise1TeamID = "E1" - const enterprise1UserID = "UE1" - const enterprise1Token = "xoxp-xoxe-12345" - - authEnterprise1 := types.SlackAuth{ - TeamID: enterprise1TeamID, - TeamDomain: enterprise1TeamDomain, - Token: enterprise1Token, - IsEnterpriseInstall: true, - UserID: enterprise1UserID, - EnterpriseID: enterprise1TeamID, - } - - // Set up team 1 to belong to enterprise 1 - authTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - authTeam1.EnterpriseID = authEnterprise1.TeamID - - // For this test we want to make sure that no auth is found for team1, and a credentials not found - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(types.SlackAuth{}, slackerror.New(slackerror.ErrCredentialsNotFound)) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, enterprise1TeamID).Return(authEnterprise1, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - - // This test uses a single auth - the enterprise auth - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{ - authEnterprise1, - }, nil) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Save apps - // Save a hosted and local enterprise workspace-level app - appTeam1Hosted := types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - EnterpriseID: enterprise1TeamID, - } - - appTeam1Local := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - EnterpriseID: enterprise1TeamID, - } - - err := clients.AppClient().SaveDeployed(ctx, appTeam1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appTeam1Local) - require.NoError(t, err) - - // Save a hosted and local enterprise / org level app - appEnterprise1Hosted := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE03", - EnterpriseID: enterprise1TeamID, - } - - appEnterprise1Local := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE04", - IsDev: true, - EnterpriseID: enterprise1TeamID, - } - - err = clients.AppClient().SaveDeployed(ctx, appEnterprise1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appEnterprise1Local) - require.NoError(t, err) - - // Test cases - // Execute tests - tests := []struct { - desc string - - expected SelectedApp - expectedError *slackerror.Error - - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - - authState []types.SlackAuth - authError error +// +// TeamAppSelectPrompt tests +// - selectTeamDomain string - selectEnvironment string +func TestPrompt_TeamAppSelectPrompt_TokenAppFlag(t *testing.T) { + tests := map[string]struct { + tokenFlag string + tokenAuth types.SlackAuth + appFlag string + appStatus api.GetAppStatusResult + statusErr error + saveLocal []types.App + selectEnv AppEnvironmentType + selectStatus AppInstallStatus + expectedApp SelectedApp + expectedErr error }{ - { - // Test description - "Has no existing workspace auth, but has enterprise/org auth, return App and enterprise Auth", - - // Expected results - SelectedApp{ - App: appTeam1Hosted, - Auth: authEnterprise1, + "error if an error occurred while collecting app info": { + tokenFlag: team1Token, + tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], + appFlag: localTeam1UninstalledApp.AppID, + appStatus: api.GetAppStatusResult{}, + statusErr: slackerror.New(slackerror.ErrAppNotFound), + selectEnv: ShowHostedOnly, + selectStatus: ShowAllApps, + expectedApp: SelectedApp{}, + expectedErr: slackerror.New(slackerror.ErrAppNotFound), + }, + "continue if a saved local app is used for a deployed only prompt": { + tokenFlag: team1Token, + tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], + appFlag: localTeam1UninstalledApp.AppID, + appStatus: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{{ + AppID: localTeam1UninstalledAppID, + Installed: localTeam1AppIsInstalled, + Hosted: false, + }}, }, - nil, - - // Env + app install status - ShowHostedOnly, - ShowInstalledAppsOnly, - - // flags - "", - "", - - // Auth state - []types.SlackAuth{ - authEnterprise1, + statusErr: nil, + saveLocal: []types.App{localTeam1UninstalledApp}, + selectEnv: ShowHostedOnly, + selectStatus: ShowAllApps, + expectedApp: SelectedApp{ + Auth: fakeAuthsByTeamDomain[team1TeamDomain], + App: localTeam1UninstalledApp, }, - nil, - - // Selector preferences team 1 - team1TeamDomain, - "Deployed", + expectedErr: slackerror.New(slackerror.ErrLocalAppNotSupported), }, - } - - for _, test := range tests { - - // Set app flags - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - - // Finally we mock the select prompt based on test specs - - // Workspace selector mocks - clientsMock.IO.On(SelectPrompt, mock.Anything, appInstallPromptNew, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectTeamDomain, - Index: 1, // should select team1 - }, nil) - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose a deployed environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectEnvironment, - Index: 1, // should select team1 - }, nil) - - actualSelected1, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - - if test.expectedError == nil { - require.NoError(t, err) - - // There's a valid auth for this auth record - _, err := clients.AppClient().GetDeployed(ctx, test.expected.Auth.TeamID) - require.NoError(t, err) - - require.Equal(t, test.expected, actualSelected1) - } else if assert.Error(t, err) { - assert.Equal(t, test.expectedError.Code, err.(*slackerror.Error).Code, test.desc) - - require.Equal(t, test.expected, actualSelected1) - } - } -} - -func TestPrompt_AppSelectPrompt_EnterpriseWorkspaceApps_MissingWorkspaceAuth_HasOrgAuth(t *testing.T) { - // Set up mocks - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - - // Auths - // Enterprise (org) Auth - // Mock the enterprise that Team1 belongs to - const enterprise1TeamDomain = "enterprise1" - const enterprise1TeamID = "E1" - const enterprise1UserID = "UE1" - const enterprise1Token = "xoxp-xoxe-12345" - - authEnterprise1 := types.SlackAuth{ - TeamID: enterprise1TeamID, - TeamDomain: enterprise1TeamDomain, - Token: enterprise1Token, - IsEnterpriseInstall: true, - UserID: enterprise1UserID, - EnterpriseID: enterprise1TeamID, - } - - // Set up team 1 to belong to enterprise 1 - authTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - authTeam1.EnterpriseID = authEnterprise1.TeamID - - // For this test we want to make sure that no auth is found for team1, and a credentials not found - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, team1TeamID).Return(types.SlackAuth{}, slackerror.New(slackerror.ErrCredentialsNotFound)) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, enterprise1TeamID).Return(authEnterprise1, nil) - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) - - // This test uses a single auth - the enterprise auth - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{ - authEnterprise1, - }, nil) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - // Save apps - // Save a hosted and local enterprise workspace-level app - appTeam1Hosted := types.App{ - TeamDomain: team1TeamDomain, - TeamID: team1TeamID, - AppID: "A1EXAMPLE01", - EnterpriseID: enterprise1TeamID, - } - - appTeam1Local := types.App{ - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE02", - TeamID: team1TeamID, - IsDev: true, - UserID: team1UserID, - EnterpriseID: enterprise1TeamID, - } - - err := clients.AppClient().SaveDeployed(ctx, appTeam1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appTeam1Local) - require.NoError(t, err) - - // Save a hosted and local enterprise / org level app - appEnterprise1Hosted := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE03", - EnterpriseID: enterprise1TeamID, - } - - appEnterprise1Local := types.App{ - TeamDomain: enterprise1TeamDomain, - TeamID: enterprise1TeamID, - AppID: "A1EXAMPLE04", - IsDev: true, - EnterpriseID: enterprise1TeamID, - } - - err = clients.AppClient().SaveDeployed(ctx, appEnterprise1Hosted) - require.NoError(t, err) - err = clients.AppClient().SaveLocal(ctx, appEnterprise1Local) - require.NoError(t, err) - - // Test cases - // Execute tests - tests := []struct { - desc string - - expected SelectedApp - expectedError *slackerror.Error - - env AppEnvironmentType - status AppInstallStatus - - appFlag string - teamFlag string - - authState []types.SlackAuth - authError error - - selectTeamDomain string - selectEnvironment string - }{ - { - // Test description - "Has no existing workspace auth, but has enterprise/org auth, return App and enterprise Auth", - - // Expected results - SelectedApp{ - App: appTeam1Hosted, - Auth: authEnterprise1, + "error if a deployed app is used for a local only prompt": { + tokenFlag: team2Token, + tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], + appFlag: deployedTeam2UninstalledApp.AppID, + appStatus: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{{ + AppID: deployedTeam2UninstalledApp.AppID, + Installed: deployedTeam2AppIsInstalled, + Hosted: true, + }}, }, - nil, - - // Env + app install status - ShowHostedOnly, - ShowInstalledAppsOnly, - - // flags - "", - "", - - // Auth state - []types.SlackAuth{ - authEnterprise1, + statusErr: nil, + selectEnv: ShowLocalOnly, + selectStatus: ShowAllApps, + expectedApp: SelectedApp{}, + expectedErr: slackerror.New(slackerror.ErrDeployedAppNotSupported), + }, + "error if an uninstalled app is used for an installed only prompt": { + tokenFlag: team2Token, + tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], + appFlag: deployedTeam2UninstalledApp.AppID, + appStatus: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{{ + AppID: deployedTeam2UninstalledApp.AppID, + Installed: deployedTeam2AppIsInstalled, + Hosted: true, + }}, }, - nil, - - // Selector preferences team 1 - team1TeamDomain, - "Deployed", + statusErr: nil, + selectEnv: ShowHostedOnly, + selectStatus: ShowInstalledAppsOnly, + expectedApp: SelectedApp{}, + expectedErr: slackerror.New(slackerror.ErrInstallationRequired), + }, + "returns known information about the request app": { + tokenFlag: team1Token, + tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], + appFlag: deployedTeam1InstalledAppID, + appStatus: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{{ + AppID: deployedTeam1InstalledAppID, + Installed: deployedTeam1AppIsInstalled, + Hosted: true, + }}, + }, + statusErr: nil, + selectEnv: ShowHostedOnly, + selectStatus: ShowInstalledAppsOnly, + expectedApp: SelectedApp{ + Auth: fakeAuthsByTeamDomain[team1TeamDomain], + App: deployedTeam1InstalledApp, + }, + expectedErr: nil, }, } - for _, test := range tests { - - // Set app flags - clients.Config.AppFlag = test.appFlag - clients.Config.TeamFlag = test.teamFlag - - // Finally we mock the select prompt based on test specs - // App selector mocks - clientsMock.IO.On(SelectPrompt, mock.Anything, "Choose an app environment", mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("app"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: "deployed", - Index: 1, // should select team1 - }, nil) - - clientsMock.IO.On(SelectPrompt, mock.Anything, SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ - Flag: clientsMock.Config.Flags.Lookup("team"), - })).Return(iostreams.SelectPromptResponse{ - Prompt: true, - Option: test.selectTeamDomain, - Index: 1, - }, nil) - - // actualSelected1, err := TeamAppSelectPrompt(ctx, clients, test.env, test.status) - actualSelected2, err := AppSelectPrompt(ctx, clients, test.status) + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + clientsMock := shared.NewClientsMock() + clientsMock.Auth.On(AuthWithToken, mock.Anything, test.tokenFlag). + Return(test.tokenAuth, nil) + clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(test.appStatus, test.statusErr) + clientsMock.AddDefaultMocks() - if test.expectedError == nil { - require.NoError(t, err) + clients := shared.NewClientFactory(clientsMock.MockClientFactory()) + for _, app := range test.saveLocal { + err := clients.AppClient().SaveLocal(ctx, app) + require.NoError(t, err) + } + clients.Config.TokenFlag = test.tokenFlag + clients.Config.AppFlag = test.appFlag - // There's a valid auth for this auth record - _, err := clients.AppClient().GetDeployed(ctx, test.expected.Auth.TeamID) - require.NoError(t, err) + selection, err := TeamAppSelectPrompt(ctx, clients, test.selectEnv, test.selectStatus) - require.Equal(t, test.expected, actualSelected2) - } else if assert.Error(t, err) { - assert.Equal(t, test.expectedError.Code, err.(*slackerror.Error).Code, test.desc) - require.Equal(t, test.expected, actualSelected2) - } + if test.statusErr != nil && assert.Error(t, err) { + require.Equal(t, test.statusErr, err) + } else if test.expectedErr != nil && assert.Error(t, err) { + require.Equal(t, test.expectedErr, err) + } else { + require.NoError(t, err) + assert.Equal(t, test.expectedApp.Auth, selection.Auth) + expectedApp := test.expectedApp.App + expectedApp.UserID = test.expectedApp.Auth.UserID + assert.Equal(t, expectedApp, selection.App) + } + }) } } @@ -5181,27 +2199,3 @@ func Test_ValidateAuth(t *testing.T) { }) } } - -// mockReauthentication is a test helper function to cut down on code duplication -func mockReauthentication(clientsMock *shared.ClientsMock) { - // Default mocks - clientsMock.Os.AddDefaultMocks() - clientsMock.API.AddDefaultMocks() - // Enable interactivity - clientsMock.IO.On("IsTTY").Return(true) - clientsMock.IO.AddDefaultMocks() - - // Mock invalid auth response - clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, fmt.Errorf(slackerror.ErrInvalidAuth)) - clientsMock.Auth.On("FilterKnownAuthErrors", mock.Anything, mock.Anything).Return(true, nil) - // Mocks for reauthentication - clientsMock.API.On("GenerateAuthTicket", mock.Anything, mock.Anything, mock.Anything).Return(api.GenerateAuthTicketResult{}, nil) - clientsMock.IO.On("InputPrompt", mock.Anything, "Enter challenge code", iostreams.InputPromptConfig{ - Required: true, - }).Return("challengeCode", nil) - clientsMock.API.On("ExchangeAuthTicket", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.ExchangeAuthTicketResult{Token: fakeAuthsByTeamDomain[team1TeamDomain].Token, TeamDomain: team1TeamDomain, - TeamID: team1TeamID, UserID: "U1"}, nil) - clientsMock.Auth.On("IsAPIHostSlackProd", mock.Anything).Return(true) - clientsMock.Auth.On("SetAuth", mock.Anything, mock.Anything).Return(types.SlackAuth{}, "", nil) - clientsMock.Auth.On("SetSelectedAuth", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() -} From 6adb327c1a0203b6575b52e2195df5d4afaeae85 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 18:46:38 -0700 Subject: [PATCH 2/9] revert: undo changes saved for a follow up pull request - --- internal/prompts/app_select_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 22f9f3f7..50a87adb 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -1252,8 +1252,6 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, - // "returns selection for app id flag and team id flag after reauthentication": { - // }, "errors if app id flag has a team id flag that does not match": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ @@ -1389,7 +1387,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, - "returns new application with team id flag and token flag if app not saved": { + "returns new application with token flag and team id flag if app not saved": { mockAuthWithToken: fakeAuthsByTeamDomain[team1TeamDomain], mockAuthWithTeamIDError: slackerror.New(slackerror.ErrCredentialsNotFound), mockAuthWithTeamIDTeamID: team1TeamID, From 6df5ce2e98d40c7f93b196b3356e68bd73bf7276 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 19:06:09 -0700 Subject: [PATCH 3/9] refactor: delete unreferenced code --- internal/prompts/app_select.go | 334 ---------------------------- internal/prompts/app_select_test.go | 294 ------------------------ 2 files changed, 628 deletions(-) diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index 5355debf..2abe2628 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -75,22 +75,6 @@ type SelectedApp struct { App types.App } -// Equals returns true if the fields of the two SelectedApp objects match -func (apps *SelectedApp) Equals(otherApps SelectedApp) bool { - if !apps.App.Equals(otherApps.App) { - return false - } - if apps.Auth != otherApps.Auth { - return false - } - return true -} - -// IsEmpty returns true if the selection has no app and no auth -func (apps *SelectedApp) IsEmpty() bool { - return apps.Equals(SelectedApp{}) -} - // TeamApps contains the apps (local and hosted), auth and information for a team type TeamApps struct { Auth types.SlackAuth @@ -98,47 +82,6 @@ type TeamApps struct { Local SelectedApp } -// Equals returns true if the fields of the two TeamApps objects match -func (apps *TeamApps) Equals(otherApps TeamApps) bool { - if !apps.Hosted.Equals(otherApps.Hosted) { - return false - } - if !apps.Local.Equals(otherApps.Local) { - return false - } - if apps.Auth != otherApps.Auth { - return false - } - return true -} - -// IsEmpty returns true if the object is empty (all fields are their zero value) -func (apps *TeamApps) IsEmpty() bool { - return apps.Equals(TeamApps{}) -} - -// authOrAppTeamID greedily returns a team ID corresponding to the TeamApps -// depending on what app and auth information is available. -// -// * When Auth is known, returns auth's team domain IFF it matches either the Hosted -// or local app's team ID. -// -// * When Auth is unknown or doesn't match the Hosted or Local app's team ID, -// then it returns app's team ID. -// -// Basically, treat as a convenience getter intended for use in the context where -// you have a team you want to filter against and you don't care whether it's an -// auth or an app that corresponds. E.g. when you are comparing to --team flags -func (apps *TeamApps) authOrAppTeamID() string { - if apps.Auth.TeamID != "" && (apps.Hosted.App.TeamID == apps.Auth.TeamID || apps.Local.App.TeamID == apps.Auth.TeamID) { - return apps.Auth.TeamID - } - if apps.Hosted.App.TeamID != "" { - return apps.Hosted.App.TeamID - } - return apps.Local.App.TeamID -} - // appTransferDisclaimer contains a notice of lost app management permissions // if the installed workspace is left var appTransferDisclaimer = style.TextSection{ @@ -314,201 +257,6 @@ func getAuths(ctx context.Context, clients *shared.ClientFactory) ([]types.Slack return allAuths, nil } -// getTeamApps creates a map from team ID to applications and authentications -// -// Details are collected from both the credentials.json and apps.*.json files. -// Existing apps or placeholder apps will be added to each team. Apps without -// authentications and authentications with placeholder apps are both possible. -// -// Additional filters should check that apps and authentications meet criteria. -// -// Note: Providing both the "app" and "token" flag should skip this check and -// instead use the flag options provided. -func getTeamApps(ctx context.Context, clients *shared.ClientFactory) (map[string]TeamApps, error) { - teamApps := map[string]TeamApps{} - - allAuths, err := getAuths(ctx, clients) - if err != nil { - return nil, err - } - - // Get deployed and local apps - deployedApps, _, err := clients.AppClient().GetDeployedAll(ctx) - if err != nil { - return nil, err - } - localApps, err := clients.AppClient().GetLocalAll(ctx) - if err != nil { - return nil, err - } - - // First add all the apps for the authed teams - for _, auth := range allAuths { - deployedApp, err := clients.AppClient().GetDeployed(ctx, auth.TeamID) - if err != nil { - return nil, err - } - hostedApp := SelectedApp{ - Auth: auth, - App: deployedApp, - } - - app, err := clients.AppClient().GetLocal(ctx, auth.TeamID) - if err != nil { - return nil, err - } - localApp := SelectedApp{ - Auth: auth, - App: app, - } - - if localApp.App.TeamDomain == "dev" && auth.TeamID == app.TeamID { - // Handle legacy apps.dev.json format - // Legacy dev apps have team_domain as "dev" - // instead of the team domain of the team they - // were created in. Selector UI that relies on the - // app's team domain will incorrectly display "dev" - // So we override the TeamDomain when the auth - // context is known and after we've confirmed that - // auth.TeamID matches the app.TeamID - localApp.App.TeamDomain = auth.TeamDomain - _ = clients.AppClient().SaveLocal(ctx, localApp.App) - } - - selection := TeamApps{ - Auth: auth, - Hosted: hostedApp, - Local: localApp, - } - - // Fetch installation status for the apps - selection = appendAppInstallStatus(ctx, clients, auth, selection) - - teamApps[selection.authOrAppTeamID()] = selection - } - - // Then add remaining "hosted" apps that did not have saved credentials and - // were therefore not saved to team apps - for _, deployedApp := range deployedApps { - if teamApps[deployedApp.TeamID].Hosted.App.AppID != "" { - continue - } - var resolvedAuth types.SlackAuth - - // Try to find an auth that matches the app's team id - _, err := clients.Auth().AuthWithTeamID(ctx, deployedApp.TeamID) - if err == nil { - continue - } else { - if slackerror.ToSlackError(err).Code != slackerror.ErrCredentialsNotFound { - return map[string]TeamApps{}, err - } - if deployedApp.IsEnterpriseWorkspaceApp() { - // We can search to see whether we can find an existing org-level auth - // in credentials.json. If found, use that auth as the resolved auth - // for this workspace app - - resolvedAuth, err = clients.Auth().AuthWithTeamID(ctx, deployedApp.EnterpriseID) - if err != nil && slackerror.ToSlackError(err).Code != slackerror.ErrCredentialsNotFound { - // Fetching an auth by team id failed for some other reason than credentials not being found - return map[string]TeamApps{}, err - } - - // Do some debug logging - logResolvedEnterpriseAuth(ctx, clients, deployedApp, resolvedAuth) - } - } - // Set the Auth as resolved Auth - hostedApp := SelectedApp{ - Auth: resolvedAuth, - App: deployedApp, - } - - // Create a dummy local app - localApp := SelectedApp{ - Auth: resolvedAuth, - App: types.App{TeamID: deployedApp.TeamID}, - } - - // Assume the caller is a collaborator of the local app - for _, a := range localApps { - if a.TeamID == deployedApp.TeamID { - localApp.App = a - } - } - - selection := TeamApps{ - Auth: resolvedAuth, - Hosted: hostedApp, - Local: localApp, - } - - selection = appendAppInstallStatus(ctx, clients, resolvedAuth, selection) - - teamApps[selection.authOrAppTeamID()] = selection - } - - // Then add remaining "local" apps that did not have saved credentials and - // were therefore not saved to team apps - for _, localApp := range localApps { - if teamApps[localApp.TeamID].Local.App.AppID != "" { - continue - } - var resolvedAuth types.SlackAuth - - _, err = clients.Auth().AuthWithTeamID(ctx, localApp.TeamID) - if err == nil { - continue - } else { - if slackerror.ToSlackError(err).Code != slackerror.ErrCredentialsNotFound { - return map[string]TeamApps{}, err - } - - if localApp.IsEnterpriseWorkspaceApp() { - // We can search to see whether we can find an existing org-level auth - // in credentials.json. If found, use that auth as the resolved auth - // for this workspace app - - resolvedAuth, err = clients.Auth().AuthWithTeamID(ctx, localApp.EnterpriseID) - - if err != nil && slackerror.ToSlackError(err).Code != slackerror.ErrCredentialsNotFound { - // Fetching an auth by team id failed for some other reason than credentials not being found - return map[string]TeamApps{}, err - } - - logResolvedEnterpriseAuth(ctx, clients, localApp, resolvedAuth) - } - } - // Don't override any existing Hosted / deployed selections or auth - var existingHosted SelectedApp - var existingAuth types.SlackAuth - - _, ok := teamApps[localApp.TeamID] - - if ok { - existingHosted = teamApps[localApp.TeamID].Hosted - existingAuth = teamApps[localApp.TeamID].Auth - } - - newLocal := SelectedApp{ - Auth: resolvedAuth, - App: localApp, - } - - selection := TeamApps{ - Auth: existingAuth, - Hosted: existingHosted, - Local: newLocal, - } - - selection = appendAppInstallStatus(ctx, clients, resolvedAuth, selection) - - teamApps[selection.authOrAppTeamID()] = selection - } - - return teamApps, nil -} - // getTokenApp gathers app and auth info from the API using the token and app ID func getTokenApp(ctx context.Context, clients *shared.ClientFactory, token string, appID string) (SelectedApp, error) { auth, err := clients.Auth().AuthWithToken(ctx, token) @@ -548,38 +296,6 @@ func getTokenApp(ctx context.Context, clients *shared.ClientFactory, token strin return SelectedApp{Auth: auth, App: app}, nil } -// appendAppInstallStatus gets and appends an apps installation status to the selections -func appendAppInstallStatus(ctx context.Context, clients *shared.ClientFactory, auth types.SlackAuth, selection TeamApps) TeamApps { - appIDs := []string{} - if appExists(selection.Local.App) { - appIDs = append(appIDs, selection.Local.App.AppID) - } - if appExists(selection.Hosted.App) { - appIDs = append(appIDs, selection.Hosted.App.AppID) - } - if len(appIDs) > 0 { - var apiHost = "" - if auth.APIHost != nil { - apiHost = *auth.APIHost - } - appInfo, err := getInstallationStatuses(ctx, clients, auth.Token, appIDs, auth.TeamID, apiHost) - if err != nil { - clients.IO.PrintDebug(ctx, "error fetching installation status for the following %s in team %s %v: %s", style.Pluralize("app", "apps", len(appIDs)), auth.TeamDomain, appIDs, err.Error()) - } - for _, i := range appInfo { - if i.AppID == selection.Local.App.AppID { - selection.Local.App.InstallStatus = i.InstallationState - selection.Local.App.EnterpriseGrants = i.EnterpriseGrants - } - if i.AppID == selection.Hosted.App.AppID { - selection.Hosted.App.InstallStatus = i.InstallationState - selection.Hosted.App.EnterpriseGrants = i.EnterpriseGrants - } - } - } - return selection -} - // logResolvedEnterpriseAuth logs out which org auth was resolved for which enterprise workspace app func logResolvedEnterpriseAuth(ctx context.Context, clients *shared.ClientFactory, app types.App, resolvedAuth types.SlackAuth) { clients.IO.PrintDebug(ctx, "Workspace token missing for Enterprise Workspace App ID: %s, Team: %s (%s)", app.AppID, app.TeamDomain, app.TeamID) @@ -626,56 +342,6 @@ func getInstallationStatuses(ctx context.Context, clients *shared.ClientFactory, return appInfos, nil } -// filterAuthsByToken returns any matching workspace authentication for the token flag -func filterAuthsByToken(ctx context.Context, clients *shared.ClientFactory, workspaceApps map[string]TeamApps) (types.SlackAuth, error) { - var teamFlag = clients.Config.TeamFlag // team_id, domain of a workspace or an org, i.e. T123445678, 'acme-corp', 'acme-workspace' - var appFlag = clients.Config.AppFlag // an app_id, local, deploy, or deployed - - // Fetch an existing Auth that matches supplied token OR return a brand new Auth object - auth, err := clients.Auth().AuthWithToken(ctx, clients.Config.TokenFlag) - if err != nil { - return types.SlackAuth{}, err - } - - var teamFlagIsAuthTeamDomain = auth.TeamDomain != "" && teamFlag == auth.TeamDomain - var teamFlagIsAuthTeamID = auth.TeamID != "" && teamFlag == auth.TeamID - - if teamFlag != "" && !teamFlagIsAuthTeamDomain && !teamFlagIsAuthTeamID { - // If a team flag is provided and it doesn't match either auth team domain or auth team id - return types.SlackAuth{}, slackerror.New(slackerror.ErrInvalidToken).WithDetails(slackerror.ErrorDetails{ - slackerror.ErrorDetail{Message: "The team flag is not associated with the provided token"}, - }) - } - - if types.IsAppID(appFlag) { - filtered, err := filterByAppID(workspaceApps, appFlag) - if err != nil { - return types.SlackAuth{}, err - } - - if auth.TeamID != "" && (auth.TeamID != filtered.App.TeamID) && (auth.TeamID != filtered.App.EnterpriseID) { - // Auth team domain and app team domain don't match - return types.SlackAuth{}, slackerror.New(slackerror.ErrInvalidToken).WithDetails(slackerror.ErrorDetails{ - slackerror.ErrorDetail{Message: "The app flag is not associated with the provided token"}, - }) - } - } - return auth, nil -} - -// filterByAppID returns the app with a matching appID and errors if it cannot find an app by that ID -func filterByAppID(workspaceApps map[string]TeamApps, appID string) (SelectedApp, error) { - for _, selection := range workspaceApps { - if selection.Hosted.App.AppID == appID { - return selection.Hosted, nil - } - if selection.Local.App.AppID == appID { - return selection.Local, nil - } - } - return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound) -} - // includeInAppSelect is a helper function for app selection that determines whether an existing app should be displayed given the install status func includeInAppSelect(app types.App, status AppInstallStatus) bool { diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 50a87adb..d623a9dd 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -15,7 +15,6 @@ package prompts import ( - "errors" "fmt" "testing" "time" @@ -169,129 +168,6 @@ const ( Local = "Local" ) -// -// getTeamApps tests -// - -func TestGetTeamApps(t *testing.T) { - tests := map[string]struct { - tokenFlag string - deployedApps []types.App - localApps []types.App - mockAuthWithTokenResponse types.SlackAuth - mockAuthWithTokenError error - mockAuthWithTeamIDResponse types.SlackAuth - mockAuthWithTeamIDError error - mockGetAppStatusResponse api.GetAppStatusResult - mockGetAppStatusError error - mockValidateSessionResponse api.AuthSession - mockValidateSessionError error - expectedAuth types.SlackAuth - expectedTeamID string - expectedError error - }{ - "error when the token authentication returns an error": { - tokenFlag: team1Token, - mockAuthWithTokenError: slackerror.New(slackerror.ErrTokenExpired), - expectedAuth: types.SlackAuth{}, - expectedError: slackerror.New(slackerror.ErrTokenExpired), - }, - "retrieve apps and authentication associated with a token": { - tokenFlag: team1Token, - deployedApps: []types.App{deployedTeam1InstalledApp}, - localApps: []types.App{localTeam1UninstalledApp}, - mockAuthWithTokenResponse: fakeAuthsByTeamDomain[team1TeamDomain], - mockAuthWithTeamIDError: slackerror.New(slackerror.ErrCredentialsNotFound), - mockGetAppStatusResponse: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{ - { - AppID: deployedTeam1InstalledAppID, - Installed: true, - }, - { - AppID: localTeam1UninstalledAppID, - Installed: false, - }, - }, - }, - expectedTeamID: team1TeamID, - expectedAuth: fakeAuthsByTeamDomain[team1TeamDomain], - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On( - AuthWithToken, - mock.Anything, - mock.Anything, - ).Return( - tt.mockAuthWithTokenResponse, - tt.mockAuthWithTokenError, - ) - clientsMock.Auth.On( - AuthWithTeamID, - mock.Anything, - tt.mockAuthWithTokenResponse.TeamID, - ).Return( - tt.mockAuthWithTeamIDResponse, - tt.mockAuthWithTeamIDError, - ) - clientsMock.API.On( - GetAppStatus, - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return( - tt.mockGetAppStatusResponse, - tt.mockGetAppStatusError, - ) - clientsMock.API.On( - "ValidateSession", - mock.Anything, - mock.Anything, - ).Return( - tt.mockValidateSessionResponse, - tt.mockValidateSessionError, - ) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - for _, app := range tt.deployedApps { - err := clients.AppClient().SaveDeployed(ctx, app) - require.NoError(t, err) - } - for _, app := range tt.localApps { - err := clients.AppClient().SaveLocal(ctx, app) - require.NoError(t, err) - } - clients.Config.TokenFlag = tt.tokenFlag - - actual, err := getTeamApps(ctx, clients) - if tt.expectedError != nil { - require.Error(t, err) - assert.Equal( - t, - slackerror.ToSlackError(err).Code, - slackerror.ToSlackError(tt.expectedError).Code, - ) - } else { - require.NoError(t, err) - if tt.tokenFlag != "" { - tt.expectedAuth.Token = tt.tokenFlag // expect the same token - } - assert.Equal( - t, - tt.tokenFlag, - actual[tt.expectedTeamID].Auth.Token, - ) - } - }) - } -} - // // getTokenApp tests // @@ -399,176 +275,6 @@ func TestGetTokenApp(t *testing.T) { } } -// -// filterAuthsByToken tests -// - -func TestFilterAuthsByToken_NoLogin(t *testing.T) { - test := struct { - title string - TokenFlag string - expectedAuth types.SlackAuth - }{ - title: "return the correct mocked api response", - TokenFlag: team1Token, - expectedAuth: fakeAuthsByTeamDomain[team1TeamDomain], - } - test.expectedAuth.Token = test.TokenFlag // expect the same token - - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(AuthWithToken, mock.Anything, test.TokenFlag). - Return(test.expectedAuth, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return([]types.SlackAuth{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, errors.New(slackerror.ErrCredentialsNotFound)) - clientsMock.Auth.On(SetAuth, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - clients.Config.TokenFlag = test.TokenFlag - - workspaceApps, err := getTeamApps(ctx, clients) - assert.NoError(t, err) - - auth, err := filterAuthsByToken(ctx, clients, workspaceApps) - auth.LastUpdated = time.Time{} // ignore time for this test - - assert.NoError(t, err) - assert.Equal(t, test.expectedAuth, auth) -} - -func Test_FilterAuthsByToken_Flags(t *testing.T) { - ctx := slackcontext.MockContext(t.Context()) - - mockAuthTeam1 := fakeAuthsByTeamDomain[team1TeamDomain] - mockAuthTeam1.Token = team1Token - mockAuthTeam2 := fakeAuthsByTeamDomain[team2TeamDomain] - mockAuthTeam2.Token = team2Token - - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(AuthWithToken, mock.Anything, team1Token). - Return(mockAuthTeam1, nil) - clientsMock.Auth.On(AuthWithToken, mock.Anything, team2Token). - Return(mockAuthTeam2, nil) - - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(api.GetAppStatusResult{}, nil) - clientsMock.Auth.On(Auths, mock.Anything).Return(fakeAuthsByTeamDomainSlice, nil) - clientsMock.Auth.On(SetAuth, mock.Anything).Return(types.SlackAuth{}, nil) - clientsMock.Auth.On(AuthWithTeamID, mock.Anything, mock.Anything).Return(types.SlackAuth{}, errors.New(slackerror.ErrCredentialsNotFound)) - - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - - err := clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team2TeamID, - TeamDomain: team2TeamDomain, - AppID: "A1EXAMPLE03", - }) - require.NoError(t, err) - - err = clients.AppClient().SaveDeployed(ctx, types.App{ - TeamID: team1TeamID, - TeamDomain: team1TeamDomain, - AppID: "A1EXAMPLE01", - }) - require.NoError(t, err) - - tests := map[string]struct { - AppFlag string - TokenFlag string - TeamFlag string - expectedAuth types.SlackAuth - expectedErr slackerror.Error - }{ - "standalone token to select team": { - AppFlag: "", - TokenFlag: team1Token, - TeamFlag: "", - expectedAuth: mockAuthTeam1, - expectedErr: slackerror.Error{}, - }, - "token with matching workspace flag": { - AppFlag: "", - TokenFlag: team1Token, - TeamFlag: team1TeamDomain, - expectedAuth: mockAuthTeam1, - expectedErr: slackerror.Error{}, - }, - "token with mismatched workspace flag": { - AppFlag: "", - TokenFlag: team1Token, - TeamFlag: team2TeamDomain, - expectedAuth: types.SlackAuth{}, - expectedErr: *slackerror.New(slackerror.ErrInvalidToken), - }, - "standalone token for another team": { - AppFlag: "", - TokenFlag: team2Token, - TeamFlag: "", - expectedAuth: mockAuthTeam2, - expectedErr: slackerror.Error{}, - }, - "token with matching app flag": { - AppFlag: "A1EXAMPLE03", - TokenFlag: team2Token, - TeamFlag: "", - expectedAuth: mockAuthTeam2, - expectedErr: slackerror.Error{}, - }, - "token with matching app and workspace flag": { - AppFlag: "A1EXAMPLE03", - TokenFlag: team2Token, - TeamFlag: team2TeamID, - expectedAuth: mockAuthTeam2, - expectedErr: slackerror.Error{}, - }, - "token and workspace with app flag for an app that doesn't exist": { - AppFlag: "A1EXAMPLE04", - TokenFlag: team2Token, - TeamFlag: team2TeamID, - expectedAuth: types.SlackAuth{}, - expectedErr: *slackerror.New(slackerror.ErrAppNotFound), - }, - "token and workspace with mismatched app flag for an app that does exist": { - AppFlag: "A1EXAMPLE03", // this app exists just not for team1 - TokenFlag: team1Token, - TeamFlag: team1TeamID, - expectedAuth: mockAuthTeam1, - expectedErr: *slackerror.New(slackerror.ErrInvalidToken), - }, - "token with mismatched app flag": { - AppFlag: "A1EXAMPLE01", - TokenFlag: team2Token, - TeamFlag: "", - expectedAuth: types.SlackAuth{}, - expectedErr: *slackerror.New(slackerror.ErrInvalidToken), - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - clients.Config.AppFlag = test.AppFlag - clients.Config.TokenFlag = test.TokenFlag - clients.Config.TeamFlag = test.TeamFlag - - workspaceApps, err := getTeamApps(ctx, clients) - require.NoError(t, err) - - auth, err := filterAuthsByToken(ctx, clients, workspaceApps) - auth.LastUpdated = time.Time{} // ignore time for this test - - if err != nil { - require.Error(t, err) - assert.Equal(t, test.expectedErr.Code, slackerror.ToSlackError(err).Code) - } else { - require.NoError(t, err) - assert.Equal(t, test.expectedAuth, auth) - } - }) - } -} - // // AppSelectPrompt tests // From 01951d8aea0a13233351e32efd7f32ca0973c3b1 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 23:13:38 -0700 Subject: [PATCH 4/9] chore: remove unused formatting functions --- internal/style/format.go | 20 -------- internal/style/format_test.go | 94 ----------------------------------- 2 files changed, 114 deletions(-) diff --git a/internal/style/format.go b/internal/style/format.go index 9101c682..3659795a 100644 --- a/internal/style/format.go +++ b/internal/style/format.go @@ -274,26 +274,6 @@ func LocalRunDisplayName(name string) string { return name } -// AppIDLabel formats the appID to indicate the installation status -func AppIDLabel(appID string, isUninstalled bool) string { - if appID != "" && isUninstalled { - return "\x1b[0m" + Secondary(appID) + Secondary(" (uninstalled)") - } - - return "\x1b[0m" + Selector(appID) -} - -// AppSelectLabel formats a label with the environment and installed status of an app -func AppSelectLabel(environment string, appID string, isUninstalled bool) string { - return fmt.Sprintf("%s %s", environment, AppIDLabel(appID, isUninstalled)) -} - -// TeamAppSelectLabel formats an app select label for a team app. -// The environment of this app is implied by the prompting function. -func TeamAppSelectLabel(teamDomain string, teamID string, appID string, isUninstalled bool) string { - return fmt.Sprintf("%s %s", TeamSelectLabel(teamDomain, teamID), AppIDLabel(appID, isUninstalled)) -} - // TeamSelectLabel formats a team label with the teamID func TeamSelectLabel(teamDomain string, teamID string) string { return fmt.Sprintf("%s %s", teamDomain, Faint(teamID)) diff --git a/internal/style/format_test.go b/internal/style/format_test.go index c838d99f..8278953b 100644 --- a/internal/style/format_test.go +++ b/internal/style/format_test.go @@ -394,97 +394,3 @@ func TestLocalRunDisplayNamePlain(t *testing.T) { }) } } - -func TestAppSelectLabel_NoAppID(t *testing.T) { - environment := "Hosted" - appID := "" - label := AppSelectLabel(environment, appID, false) - - // Absence of app ID conveys that the app does not exist - - if !strings.Contains(label, environment) { - t.Error("App environment is missing from label") - } -} - -func TestAppSelectLabel_AppID(t *testing.T) { - environment := "Hosted" - appID := "A123456" - label := AppSelectLabel(environment, appID, false) - - if !strings.Contains(label, appID) { - t.Error("App installation status is missing from label") - } - if !strings.Contains(label, environment) { - t.Error("App environment is missing from label") - } -} - -func TestAppSelectLabel_AppID_Uninstalled(t *testing.T) { - environment := "Hosted" - appID := "A123456" - label := AppSelectLabel(environment, appID, true /*isUninstalled*/) - - if !strings.Contains(label, appID) { - t.Error("App installation status is missing from label") - } - if !strings.Contains(label, environment) { - t.Error("App environment is missing from label") - } - if !strings.Contains(label, "(uninstalled)") { - t.Error("Uninstalled text is missing from label") - } -} - -func TestTeamAppSelectLabel_AppID(t *testing.T) { - teamDomain := "slackbox" - teamID := "T01234" - appID := "A123456" - label := TeamAppSelectLabel(teamDomain, teamID, appID, false) - - if !strings.Contains(label, teamDomain) { - t.Error("Team domain is missing from label") - } - if !strings.Contains(label, teamID) { - t.Error("Team ID is missing from label") - } - if !strings.Contains(label, appID) { - t.Error("App ID is missing from label") - } -} - -func TestTeamAppSelectLabel_NoAppID(t *testing.T) { - teamDomain := "slackbox" - teamID := "T01234" - appID := "" - label := TeamAppSelectLabel(teamDomain, teamID, appID, false) - - if !strings.Contains(label, teamDomain) { - t.Error("Team domain is missing from label") - } - if !strings.Contains(label, teamID) { - t.Error("Team ID is missing from label") - } - - // Absence of app ID conveys that the app does not exist -} - -func TestTeamAppSelectLabel_AppID_Uninstalled(t *testing.T) { - teamDomain := "slackbox" - teamID := "T01234" - appID := "A123456" - label := TeamAppSelectLabel(teamDomain, teamID, appID, true) - - if !strings.Contains(label, teamDomain) { - t.Error("Team domain is missing from label") - } - if !strings.Contains(label, teamID) { - t.Error("Team ID is missing from label") - } - if !strings.Contains(label, appID) { - t.Error("App ID is missing from label") - } - if !strings.Contains(label, "(uninstalled)") { - t.Error("Uninstalled text is missing from label") - } -} From 623db4c1ee5a699fec7100aedaf0233215b94a90 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 23:09:45 -0700 Subject: [PATCH 5/9] refactor: combine the app selector and team selector --- cmd/app/add.go | 4 +- cmd/app/add_test.go | 24 +-- cmd/app/delete.go | 2 +- cmd/app/delete_test.go | 8 +- cmd/app/settings.go | 2 +- cmd/app/settings_test.go | 16 ++ cmd/app/uninstall.go | 2 +- cmd/app/uninstall_test.go | 4 +- cmd/collaborators/add.go | 2 +- cmd/collaborators/add_test.go | 12 +- cmd/collaborators/collaborators.go | 4 +- cmd/collaborators/collaborators_test.go | 4 +- cmd/collaborators/list.go | 2 +- cmd/collaborators/list_test.go | 4 +- cmd/collaborators/remove.go | 2 +- cmd/collaborators/remove_test.go | 12 +- cmd/collaborators/update.go | 2 +- cmd/collaborators/update_test.go | 8 +- cmd/datastore/bulk_delete.go | 2 +- cmd/datastore/bulk_get.go | 2 +- cmd/datastore/bulk_put.go | 2 +- cmd/datastore/count.go | 2 +- cmd/datastore/count_test.go | 2 +- cmd/datastore/datastore_test.go | 2 +- cmd/datastore/delete.go | 2 +- cmd/datastore/get.go | 2 +- cmd/datastore/put.go | 2 +- cmd/datastore/query.go | 2 +- cmd/datastore/update.go | 2 +- cmd/env/add.go | 2 +- cmd/env/add_test.go | 4 +- cmd/env/env.go | 4 +- cmd/env/list.go | 2 +- cmd/env/list_test.go | 4 +- cmd/env/remove.go | 2 +- cmd/env/remove_test.go | 12 +- cmd/externalauth/add.go | 2 +- cmd/externalauth/add_secret.go | 2 +- cmd/externalauth/helpers_test.go | 3 +- cmd/externalauth/remove.go | 2 +- cmd/externalauth/select_auth.go | 2 +- cmd/function/access.go | 2 +- cmd/function/helpers_test.go | 3 +- cmd/manifest/info.go | 2 +- cmd/manifest/info_test.go | 6 +- cmd/manifest/manifest_test.go | 2 +- cmd/manifest/validate.go | 2 +- cmd/manifest/validate_test.go | 10 +- cmd/openformresponse/export.go | 2 +- cmd/openformresponse/export_test.go | 2 +- cmd/platform/activity.go | 2 +- cmd/platform/activity_test.go | 2 +- cmd/platform/deploy.go | 3 +- cmd/platform/deploy_test.go | 4 +- cmd/platform/run.go | 4 +- cmd/platform/run_test.go | 4 +- cmd/triggers/access.go | 2 +- cmd/triggers/access_test.go | 2 +- cmd/triggers/create.go | 2 +- cmd/triggers/create_test.go | 4 +- cmd/triggers/delete.go | 2 +- cmd/triggers/delete_test.go | 4 +- cmd/triggers/info.go | 2 +- cmd/triggers/info_test.go | 4 +- cmd/triggers/list.go | 2 +- cmd/triggers/list_test.go | 2 +- cmd/triggers/update.go | 2 +- cmd/triggers/update_test.go | 2 +- internal/app/app_client.go | 2 +- internal/prompts/app_select.go | 83 ++-------- internal/prompts/app_select_mock.go | 10 +- internal/prompts/app_select_test.go | 201 +++++------------------- 72 files changed, 193 insertions(+), 360 deletions(-) diff --git a/cmd/app/add.go b/cmd/app/add.go index 406bf37d..3ed3857d 100644 --- a/cmd/app/add.go +++ b/cmd/app/add.go @@ -36,7 +36,7 @@ import ( var runAddCommandFunc = RunAddCommand var appInstallProdAppFunc = apps.Add var appInstallDevAppFunc = apps.InstallLocalApp -var teamAppSelectPromptFunc = prompts.TeamAppSelectPrompt +var appSelectPromptFunc = prompts.AppSelectPrompt // Flags @@ -111,7 +111,7 @@ func preRunAddCommand(ctx context.Context, clients *shared.ClientFactory) error // RunAddCommand executes the workspace install command, prints output, and returns any errors. func RunAddCommand(ctx context.Context, clients *shared.ClientFactory, selection *prompts.SelectedApp, orgGrantWorkspaceID string) (context.Context, types.InstallState, types.App, error) { if selection == nil { - selected, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowAllApps) + selected, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowAllApps) if err != nil { return ctx, "", types.App{}, err } diff --git a/cmd/app/add_test.go b/cmd/app/add_test.go index 6f06597e..ab9fa903 100644 --- a/cmd/app/add_test.go +++ b/cmd/app/add_test.go @@ -140,8 +140,8 @@ func TestAppAddCommand(t *testing.T) { // Mock TeamSelector prompt to return "team1" appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{Auth: mockAuthTeam1}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{Auth: mockAuthTeam1}, nil) // Mock valid session for team1 cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ @@ -212,8 +212,8 @@ func TestAppAddCommand(t *testing.T) { // Mock TeamSelector prompt to return "team1" appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: mockAppTeam1, Auth: mockAuthTeam1}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{App: mockAppTeam1, Auth: mockAuthTeam1}, nil) // Mock valid session for team1 cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ @@ -292,8 +292,8 @@ func TestAppAddCommand(t *testing.T) { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { prepareAddMocks(t, cf, cm) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: mockAppTeam1}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{App: mockAppTeam1}, nil) }, }, "adds a new deployed app to an org with a workspace grant": { @@ -303,8 +303,8 @@ func TestAppAddCommand(t *testing.T) { prepareAddMocks(t, cf, cm) // Select workspace appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) // Mock calls cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ UserID: &mockOrgAuth.UserID, @@ -363,8 +363,8 @@ func TestAppAddCommand(t *testing.T) { prepareAddMocks(t, cf, cm) // Select workspace appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) // Mock calls cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ UserID: &mockOrgAuth.UserID, @@ -419,8 +419,8 @@ func TestAppAddCommand(t *testing.T) { prepareAddMocks(t, cf, cm) // Select workspace appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{App: types.NewApp(), Auth: mockOrgAuth}, nil) // Mock calls cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ UserID: &mockOrgAuth.UserID, diff --git a/cmd/app/delete.go b/cmd/app/delete.go index 73f74076..e272f6e9 100644 --- a/cmd/app/delete.go +++ b/cmd/app/delete.go @@ -71,7 +71,7 @@ func RunDeleteCommand(ctx context.Context, clients *shared.ClientFactory, cmd *c } // Get the app auth selection from the flag or prompt - selection, err := deleteAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAndUninstalledApps) + selection, err := deleteAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps) if err != nil { if slackerror.ToSlackError(err).Code == slackerror.ErrInstallationRequired { return types.App{}, nil diff --git a/cmd/app/delete_test.go b/cmd/app/delete_test.go index 6ce94da9..a0c8e8c6 100644 --- a/cmd/app/delete_test.go +++ b/cmd/app/delete_test.go @@ -55,7 +55,7 @@ func TestAppsDeleteCommand(t *testing.T) { // Mock App Selection appSelectMock := prompts.NewAppSelectMock() deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{ + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{ Auth: types.SlackAuth{TeamDomain: fakeDeployedApp.TeamDomain}, App: fakeDeployedApp, }, nil) @@ -89,7 +89,7 @@ func TestAppsDeleteCommand(t *testing.T) { // Mock App Selection appSelectMock := prompts.NewAppSelectMock() deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{ + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{ Auth: types.SlackAuth{TeamDomain: fakeLocalApp.TeamDomain}, App: fakeLocalApp, }, nil) @@ -122,7 +122,7 @@ func TestAppsDeleteCommand(t *testing.T) { // Mock App Selection appSelectMock := prompts.NewAppSelectMock() deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{ + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{ Auth: types.SlackAuth{TeamDomain: fakeDeployedApp.TeamDomain}, App: fakeDeployedApp, }, nil) @@ -149,7 +149,7 @@ func TestAppsDeleteCommand(t *testing.T) { cm.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{}, nil) appSelectMock := prompts.NewAppSelectMock() deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{App: fakeDeployedApp}, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: fakeDeployedApp}, nil) }, }, }, func(cf *shared.ClientFactory) *cobra.Command { diff --git a/cmd/app/settings.go b/cmd/app/settings.go index 38da92c7..f57813f9 100644 --- a/cmd/app/settings.go +++ b/cmd/app/settings.go @@ -96,7 +96,7 @@ func appSettingsCommandRunE(clients *shared.ClientFactory, cmd *cobra.Command, a ctx := cmd.Context() clients.IO.PrintTrace(ctx, slacktrace.AppSettingsStart) - app, err := settingsAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAndUninstalledApps) + app, err := settingsAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps) if err != nil { return err } diff --git a/cmd/app/settings_test.go b/cmd/app/settings_test.go index d1203589..dad286ae 100644 --- a/cmd/app/settings_test.go +++ b/cmd/app/settings_test.go @@ -96,6 +96,10 @@ func Test_App_SettingsCommand(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectMock.On( "AppSelectPrompt", + mock.Anything, + mock.Anything, + prompts.ShowAllEnvironments, + prompts.ShowInstalledAndUninstalledApps, ).Return( prompts.SelectedApp{App: types.App{AppID: "A0101010101"}}, nil, @@ -124,6 +128,10 @@ func Test_App_SettingsCommand(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectMock.On( "AppSelectPrompt", + mock.Anything, + mock.Anything, + prompts.ShowAllEnvironments, + prompts.ShowInstalledAndUninstalledApps, ).Return( prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired), @@ -147,6 +155,10 @@ func Test_App_SettingsCommand(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectMock.On( "AppSelectPrompt", + mock.Anything, + mock.Anything, + prompts.ShowAllEnvironments, + prompts.ShowInstalledAndUninstalledApps, ).Return( prompts.SelectedApp{App: types.App{AppID: "A0123456789"}}, nil, @@ -176,6 +188,10 @@ func Test_App_SettingsCommand(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectMock.On( "AppSelectPrompt", + mock.Anything, + mock.Anything, + prompts.ShowAllEnvironments, + prompts.ShowInstalledAndUninstalledApps, ).Return( prompts.SelectedApp{ App: types.App{AppID: "A0123456789"}, diff --git a/cmd/app/uninstall.go b/cmd/app/uninstall.go index fc3b0d7d..ba20af14 100644 --- a/cmd/app/uninstall.go +++ b/cmd/app/uninstall.go @@ -66,7 +66,7 @@ func RunUninstallCommand(ctx context.Context, clients *shared.ClientFactory, cmd } // Get the workspace from the flag or prompt - selection, err := uninstallAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := uninstallAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { if slackerror.ToSlackError(err).Code == slackerror.ErrInstallationRequired { return types.App{}, nil diff --git a/cmd/app/uninstall_test.go b/cmd/app/uninstall_test.go index 5819ae97..84d8d2ff 100644 --- a/cmd/app/uninstall_test.go +++ b/cmd/app/uninstall_test.go @@ -84,7 +84,7 @@ func TestAppsUninstall(t *testing.T) { prepareCommonUninstallMocks(ctx, cf, cm) appSelectMock := prompts.NewAppSelectMock() uninstallAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{App: fakeApp}, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{App: fakeApp}, nil) }, }, }, func(clients *shared.ClientFactory) *cobra.Command { @@ -99,7 +99,7 @@ func prepareCommonUninstallMocks(ctx context.Context, clients *shared.ClientFact // Mock App Selection appSelectMock := prompts.NewAppSelectMock() uninstallAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedProdApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedProdApp, nil) // Mock API calls clientsMock.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything). diff --git a/cmd/collaborators/add.go b/cmd/collaborators/add.go index 4404a02e..c0b07800 100644 --- a/cmd/collaborators/add.go +++ b/cmd/collaborators/add.go @@ -74,7 +74,7 @@ func runAddCommandFunc(ctx context.Context, clients *shared.ClientFactory, cmd * defer span.Finish() // Get the app auth selection from the flag or prompt - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) if err != nil { return err } diff --git a/cmd/collaborators/add_test.go b/cmd/collaborators/add_test.go index 26bfff0b..69139d99 100644 --- a/cmd/collaborators/add_test.go +++ b/cmd/collaborators/add_test.go @@ -35,8 +35,8 @@ func TestAddCommand(t *testing.T) { cm.AddDefaultMocks() // Mock App Selection appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) // Set experiment flag cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, "read-only-collaborators") cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug) @@ -59,8 +59,8 @@ func TestAddCommand(t *testing.T) { cm.AddDefaultMocks() // Mock App Selection appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) // Set experiment flag cm.Config.ExperimentsFlag = append(cm.Config.ExperimentsFlag, "read-only-collaborators") cm.Config.LoadExperiments(ctx, cm.IO.PrintDebug) @@ -84,8 +84,8 @@ func TestAddCommand(t *testing.T) { cm.AddDefaultMocks() // Mock App Selection appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) // Mock API call cm.API.On("AddCollaborator", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) diff --git a/cmd/collaborators/collaborators.go b/cmd/collaborators/collaborators.go index 52c85834..8a155f0e 100644 --- a/cmd/collaborators/collaborators.go +++ b/cmd/collaborators/collaborators.go @@ -26,8 +26,8 @@ import ( "github.com/spf13/cobra" ) -// teamAppSelectPromptFunc is a handle to the TeamAppSelectPrompt that can be mocked in tests -var teamAppSelectPromptFunc = prompts.TeamAppSelectPrompt +// appSelectPromptFunc is a handle to the AppSelectPrompt that can be mocked in tests +var appSelectPromptFunc = prompts.AppSelectPrompt func NewCommand(clients *shared.ClientFactory) *cobra.Command { cmd := &cobra.Command{ diff --git a/cmd/collaborators/collaborators_test.go b/cmd/collaborators/collaborators_test.go index 886a10c9..4b0bf1a5 100644 --- a/cmd/collaborators/collaborators_test.go +++ b/cmd/collaborators/collaborators_test.go @@ -102,8 +102,8 @@ func TestCollaboratorsCommand(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := slackcontext.MockContext(t.Context()) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: tt.app, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: tt.app, Auth: types.SlackAuth{}}, nil) clientsMock := shared.NewClientsMock() clientsMock.AddDefaultMocks() clientsMock.API.On("ListCollaborators", mock.Anything, mock.Anything, mock.Anything). diff --git a/cmd/collaborators/list.go b/cmd/collaborators/list.go index 0881bcfc..e12fc35d 100644 --- a/cmd/collaborators/list.go +++ b/cmd/collaborators/list.go @@ -58,7 +58,7 @@ func runListCommand(cmd *cobra.Command, clients *shared.ClientFactory) error { defer span.Finish() // Get the app auth selection from the flag or prompt - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) if err != nil { return err } diff --git a/cmd/collaborators/list_test.go b/cmd/collaborators/list_test.go index 7b08dc46..1d4e7a9d 100644 --- a/cmd/collaborators/list_test.go +++ b/cmd/collaborators/list_test.go @@ -102,8 +102,8 @@ func TestListCommand(t *testing.T) { t.Run(name, func(t *testing.T) { ctx := slackcontext.MockContext(t.Context()) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: tt.app, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: tt.app, Auth: types.SlackAuth{}}, nil) clientsMock := shared.NewClientsMock() clientsMock.AddDefaultMocks() clientsMock.API.On("ListCollaborators", mock.Anything, mock.Anything, mock.Anything). diff --git a/cmd/collaborators/remove.go b/cmd/collaborators/remove.go index bea987ca..955ac330 100644 --- a/cmd/collaborators/remove.go +++ b/cmd/collaborators/remove.go @@ -59,7 +59,7 @@ func runRemoveCommandFunc(ctx context.Context, clients *shared.ClientFactory, cm var span opentracing.Span span, ctx = opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Remove") defer span.Finish() - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) if err != nil { return err } diff --git a/cmd/collaborators/remove_test.go b/cmd/collaborators/remove_test.go index af5ee8c9..0877fe64 100644 --- a/cmd/collaborators/remove_test.go +++ b/cmd/collaborators/remove_test.go @@ -53,8 +53,8 @@ func TestRemoveCommand(t *testing.T) { CmdArgs: []string{"USLACKBOT"}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt"). + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps). Return(mockSelection, nil) cm.API.On("RemoveCollaborator", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) @@ -72,8 +72,8 @@ func TestRemoveCommand(t *testing.T) { CmdArgs: []string{}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt"). + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps). Return(mockSelection, nil) cm.API.On("RemoveCollaborator", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) @@ -93,8 +93,8 @@ func TestRemoveCommand(t *testing.T) { CmdArgs: []string{}, Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt"). + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps). Return(mockSelection, nil) cm.API.On("RemoveCollaborator", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) diff --git a/cmd/collaborators/update.go b/cmd/collaborators/update.go index f4efa674..78e056bb 100644 --- a/cmd/collaborators/update.go +++ b/cmd/collaborators/update.go @@ -97,7 +97,7 @@ func runUpdateCommand(cmd *cobra.Command, clients *shared.ClientFactory, args [] } // Get the app auth selection from the flag or prompt - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps) if err != nil { return err } diff --git a/cmd/collaborators/update_test.go b/cmd/collaborators/update_test.go index 32872edd..f2ab1ed4 100644 --- a/cmd/collaborators/update_test.go +++ b/cmd/collaborators/update_test.go @@ -35,8 +35,8 @@ func TestUpdateCommand(t *testing.T) { clientsMock.AddDefaultMocks() // Mock App Selection appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) // Set experiment flag clientsMock.Config.ExperimentsFlag = append(clientsMock.Config.ExperimentsFlag, "read-only-collaborators") clientsMock.Config.LoadExperiments(ctx, clientsMock.IO.PrintDebug) @@ -53,8 +53,8 @@ func TestUpdateCommand(t *testing.T) { clientsMock.AddDefaultMocks() // Mock App Selection appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{App: types.App{AppID: "A123"}, Auth: types.SlackAuth{}}, nil) // Set experiment flag clientsMock.Config.ExperimentsFlag = append(clientsMock.Config.ExperimentsFlag, "read-only-collaborators") clientsMock.Config.LoadExperiments(ctx, clientsMock.IO.PrintDebug) diff --git a/cmd/datastore/bulk_delete.go b/cmd/datastore/bulk_delete.go index 6a3111e0..8684dc39 100644 --- a/cmd/datastore/bulk_delete.go +++ b/cmd/datastore/bulk_delete.go @@ -73,7 +73,7 @@ func NewBulkDeleteCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the app auth selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/bulk_get.go b/cmd/datastore/bulk_get.go index 4bab47e8..e293f3ff 100644 --- a/cmd/datastore/bulk_get.go +++ b/cmd/datastore/bulk_get.go @@ -75,7 +75,7 @@ func NewBulkGetCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the app auth selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/bulk_put.go b/cmd/datastore/bulk_put.go index 16cbddbd..a86c130a 100644 --- a/cmd/datastore/bulk_put.go +++ b/cmd/datastore/bulk_put.go @@ -88,7 +88,7 @@ func NewBulkPutCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/count.go b/cmd/datastore/count.go index d8d33288..cc175897 100644 --- a/cmd/datastore/count.go +++ b/cmd/datastore/count.go @@ -113,7 +113,7 @@ func runCountCommandFunc( } // Get the app from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/count_test.go b/cmd/datastore/count_test.go index e2b85ad2..7a52b2c5 100644 --- a/cmd/datastore/count_test.go +++ b/cmd/datastore/count_test.go @@ -348,7 +348,7 @@ func TestCountCommand(t *testing.T) { } appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt"). + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly). Return(prompts.SelectedApp{App: types.App{AppID: "A001"}}, nil) return cmd }) diff --git a/cmd/datastore/datastore_test.go b/cmd/datastore/datastore_test.go index 1b5c2bbf..5d47c5d5 100644 --- a/cmd/datastore/datastore_test.go +++ b/cmd/datastore/datastore_test.go @@ -59,7 +59,7 @@ func setupDatastoreMocks() *shared.ClientsMock { // Select the same example app each time appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{ + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{ App: types.App{AppID: mockAppID}, }, nil) diff --git a/cmd/datastore/delete.go b/cmd/datastore/delete.go index a36831a3..1a13954b 100644 --- a/cmd/datastore/delete.go +++ b/cmd/datastore/delete.go @@ -75,7 +75,7 @@ func NewDeleteCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the app auth selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/get.go b/cmd/datastore/get.go index 659d9bdf..2a75f706 100644 --- a/cmd/datastore/get.go +++ b/cmd/datastore/get.go @@ -77,7 +77,7 @@ func NewGetCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the app auth selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/put.go b/cmd/datastore/put.go index 564df6f5..e3a5bce7 100644 --- a/cmd/datastore/put.go +++ b/cmd/datastore/put.go @@ -76,7 +76,7 @@ func NewPutCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/query.go b/cmd/datastore/query.go index 082d0ef2..776a93d5 100644 --- a/cmd/datastore/query.go +++ b/cmd/datastore/query.go @@ -99,7 +99,7 @@ func NewQueryCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/datastore/update.go b/cmd/datastore/update.go index 344ea012..cb2633ea 100644 --- a/cmd/datastore/update.go +++ b/cmd/datastore/update.go @@ -76,7 +76,7 @@ func NewUpdateCommand(clients *shared.ClientFactory) *cobra.Command { } // Get the selection and auth selection from the flag or prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/env/add.go b/cmd/env/add.go index efe76909..dbd0e6a3 100644 --- a/cmd/env/add.go +++ b/cmd/env/add.go @@ -88,7 +88,7 @@ func runEnvAddCommandFunc(clients *shared.ClientFactory, cmd *cobra.Command, arg ctx := cmd.Context() // Get the workspace from the flag or prompt - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/env/add_test.go b/cmd/env/add_test.go index b8caa592..9e8f8f1d 100644 --- a/cmd/env/add_test.go +++ b/cmd/env/add_test.go @@ -283,8 +283,8 @@ func setupEnvAddCommandMocks(ctx context.Context, cm *shared.ClientsMock, cf *sh _ = cf.AppClient().SaveDeployed(ctx, mockApp) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{Auth: mockAuth, App: mockApp}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{Auth: mockAuth, App: mockApp}, nil) cm.Config.Flags.String("value", "", "mock value flag") diff --git a/cmd/env/env.go b/cmd/env/env.go index 43998107..2fa27cab 100644 --- a/cmd/env/env.go +++ b/cmd/env/env.go @@ -27,8 +27,8 @@ import ( var variableNameFlag string var variableValueFlag string -// teamAppSelectPromptFunc is a handle to the TeamAppSelectPrompt that can be mocked in tests -var teamAppSelectPromptFunc = prompts.TeamAppSelectPrompt +// appSelectPromptFunc is a handle to the AppSelectPrompt that can be mocked in tests +var appSelectPromptFunc = prompts.AppSelectPrompt func NewCommand(clients *shared.ClientFactory) *cobra.Command { cmd := &cobra.Command{ diff --git a/cmd/env/list.go b/cmd/env/list.go index beb98862..faa3f2dc 100644 --- a/cmd/env/list.go +++ b/cmd/env/list.go @@ -78,7 +78,7 @@ func runEnvListCommandFunc( ) error { ctx := cmd.Context() - selection, err := teamAppSelectPromptFunc( + selection, err := appSelectPromptFunc( ctx, clients, prompts.ShowHostedOnly, diff --git a/cmd/env/list_test.go b/cmd/env/list_test.go index 803a98ee..1b64c78b 100644 --- a/cmd/env/list_test.go +++ b/cmd/env/list_test.go @@ -146,8 +146,8 @@ func Test_Env_ListCommand(t *testing.T) { nil, ) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) }, ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { cm.API.AssertCalled( diff --git a/cmd/env/remove.go b/cmd/env/remove.go index 509f9b84..17711302 100644 --- a/cmd/env/remove.go +++ b/cmd/env/remove.go @@ -85,7 +85,7 @@ func runEnvRemoveCommandFunc(clients *shared.ClientFactory, cmd *cobra.Command, var ctx = cmd.Context() // Get the workspace from the flag or prompt - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/env/remove_test.go b/cmd/env/remove_test.go index f2e79d43..57d8aac3 100644 --- a/cmd/env/remove_test.go +++ b/cmd/env/remove_test.go @@ -153,8 +153,8 @@ func Test_Env_RemoveCommand(t *testing.T) { nil, ) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) }, ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { cm.API.AssertCalled( @@ -212,8 +212,8 @@ func Test_Env_RemoveCommand(t *testing.T) { nil, ) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) }, ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { cm.API.AssertCalled( @@ -246,8 +246,8 @@ func Test_Env_RemoveCommand(t *testing.T) { nil, ) appSelectMock := prompts.NewAppSelectMock() - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) }, ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) { cm.API.AssertCalled( diff --git a/cmd/externalauth/add.go b/cmd/externalauth/add.go index 06d9e165..5be8c038 100644 --- a/cmd/externalauth/add.go +++ b/cmd/externalauth/add.go @@ -83,7 +83,7 @@ func runAddCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { // Get the app selection and accompanying auth from the prompt // Note: Only installed apps will be shown in the prompt as installation is required for this command. // This is consistent with the experience for other commands that require installation. - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/externalauth/add_secret.go b/cmd/externalauth/add_secret.go index 1fb657bf..fa339ed5 100644 --- a/cmd/externalauth/add_secret.go +++ b/cmd/externalauth/add_secret.go @@ -86,7 +86,7 @@ func runAddClientSecretCommand(clients *shared.ClientFactory, cmd *cobra.Command ctx := cmd.Context() // Get the app selection and accompanying auth from the prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/externalauth/helpers_test.go b/cmd/externalauth/helpers_test.go index 6434696f..c0db69c2 100644 --- a/cmd/externalauth/helpers_test.go +++ b/cmd/externalauth/helpers_test.go @@ -17,6 +17,7 @@ package externalauth import ( "github.com/slackapi/slack-cli/internal/prompts" "github.com/slackapi/slack-cli/internal/shared/types" + "github.com/stretchr/testify/mock" ) var fakeAppID = "A1234" @@ -38,7 +39,7 @@ func setupMockAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = appSelectPromptFunc appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { appSelectPromptFunc = originalPromptFunc } diff --git a/cmd/externalauth/remove.go b/cmd/externalauth/remove.go index 7740b776..4fece1d2 100644 --- a/cmd/externalauth/remove.go +++ b/cmd/externalauth/remove.go @@ -95,7 +95,7 @@ func runRemoveCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { ctx := cmd.Context() // Get the app selection and accompanying auth from the prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/externalauth/select_auth.go b/cmd/externalauth/select_auth.go index e44aed14..9ce1bf7b 100644 --- a/cmd/externalauth/select_auth.go +++ b/cmd/externalauth/select_auth.go @@ -86,7 +86,7 @@ func runSelectAuthCommand(clients *shared.ClientFactory, cmd *cobra.Command) err ctx := cmd.Context() // Get the app selection and accompanying auth from the prompt - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/function/access.go b/cmd/function/access.go index 1c0ba42a..4c0b1a9e 100644 --- a/cmd/function/access.go +++ b/cmd/function/access.go @@ -91,7 +91,7 @@ func runDistributeCommand(cmd *cobra.Command, clients *shared.ClientFactory) err ctx := cmd.Context() // Get the app selection and accompanying auth from the prompt - selectedApp, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selectedApp, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/function/helpers_test.go b/cmd/function/helpers_test.go index 749cbb59..44b9a627 100644 --- a/cmd/function/helpers_test.go +++ b/cmd/function/helpers_test.go @@ -17,6 +17,7 @@ package function import ( "github.com/slackapi/slack-cli/internal/prompts" "github.com/slackapi/slack-cli/internal/shared/types" + "github.com/stretchr/testify/mock" ) var ( @@ -37,7 +38,7 @@ func setupMockAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = appSelectPromptFunc appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { appSelectPromptFunc = originalPromptFunc } diff --git a/cmd/manifest/info.go b/cmd/manifest/info.go index b8e6126e..2603d076 100644 --- a/cmd/manifest/info.go +++ b/cmd/manifest/info.go @@ -129,7 +129,7 @@ func getManifestInfoProject(ctx context.Context, clients *shared.ClientFactory) // getManifestInfoRemote gathers app manifest information from app settings func getManifestInfoRemote(ctx context.Context, clients *shared.ClientFactory) (types.AppManifest, error) { - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAndUninstalledApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps) if err != nil { return types.AppManifest{}, err } diff --git a/cmd/manifest/info_test.go b/cmd/manifest/info_test.go index 0c77d100..590e2671 100644 --- a/cmd/manifest/info_test.go +++ b/cmd/manifest/info_test.go @@ -82,7 +82,7 @@ func TestInfoCommand(t *testing.T) { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return( + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return( prompts.SelectedApp{ App: types.App{AppID: "A001"}, Auth: types.SlackAuth{Token: "xapp"}}, nil) @@ -112,7 +112,7 @@ func TestInfoCommand(t *testing.T) { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return( + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return( prompts.SelectedApp{ App: types.App{AppID: "A004"}, Auth: types.SlackAuth{Token: "xapp"}}, nil) @@ -163,7 +163,7 @@ func TestInfoCommand(t *testing.T) { cf.AppClient().Manifest = manifestMock appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{ + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return(prompts.SelectedApp{ App: types.App{AppID: "A005"}, Auth: types.SlackAuth{Token: "xapp-example-005"}, }, nil) diff --git a/cmd/manifest/manifest_test.go b/cmd/manifest/manifest_test.go index 75a30488..9dda661d 100644 --- a/cmd/manifest/manifest_test.go +++ b/cmd/manifest/manifest_test.go @@ -38,7 +38,7 @@ func TestManifestCommand(t *testing.T) { Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return( + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndUninstalledApps).Return( prompts.SelectedApp{ App: types.App{AppID: "A001"}, Auth: types.SlackAuth{Token: "xapp"}, diff --git a/cmd/manifest/validate.go b/cmd/manifest/validate.go index 36af907e..4ac7634d 100644 --- a/cmd/manifest/validate.go +++ b/cmd/manifest/validate.go @@ -62,7 +62,7 @@ func NewValidateCommand(clients *shared.ClientFactory) *cobra.Command { // Get the app selection and accompanying auth of an installed app or gather // some other authentication token var token string - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { if slackerror.ToSlackError(err).Code != slackerror.ErrInstallationRequired { return err diff --git a/cmd/manifest/validate_test.go b/cmd/manifest/validate_test.go index 7c9e69cb..3f58a3c5 100644 --- a/cmd/manifest/validate_test.go +++ b/cmd/manifest/validate_test.go @@ -58,7 +58,7 @@ func TestManifestValidateCommand(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) manifestValidatePkgMock := new(ManifestValidatePkgMock) manifestValidateFunc = manifestValidatePkgMock.ManifestValidate @@ -89,7 +89,7 @@ func TestManifestValidateCommand_HandleMissingAppInstallError_ZeroUserAuth(t *te // Mock a failed AppSelectPrompt appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) // Mock zero user auths mockAuths := []types.SlackAuth{} @@ -133,7 +133,7 @@ func TestManifestValidateCommand_HandleMissingAppInstallError_OneUserAuth(t *tes // Mock a failed AppSelectPrompt appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) // Mock the manifest validate package manifestValidatePkgMock := new(ManifestValidatePkgMock) @@ -163,7 +163,7 @@ func TestManifestValidateCommand_HandleMissingAppInstallError_MoreThanOneUserAut // Mock a failed AppSelectPrompt appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired)) clientsMock.IO.On("SelectPrompt", mock.Anything, prompts.SelectTeamPrompt, mock.Anything, iostreams.MatchPromptConfig(iostreams.SelectPromptConfig{ Flag: clients.Config.Flags.Lookup("team"), })).Return(iostreams.SelectPromptResponse{ @@ -227,7 +227,7 @@ func TestManifestValidateCommand_HandleOtherErrors(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt errMsg := "Unrelated error" - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, slackerror.New(errMsg)) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, slackerror.New(errMsg)) err := cmd.ExecuteContext(ctx) require.ErrorContains(t, err, errMsg) diff --git a/cmd/openformresponse/export.go b/cmd/openformresponse/export.go index 1f6895db..25952879 100644 --- a/cmd/openformresponse/export.go +++ b/cmd/openformresponse/export.go @@ -68,7 +68,7 @@ func runExportCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { var span, _ = opentracing.StartSpanFromContext(ctx, "cmd.open-form-response.export") defer span.Finish() - selection, err := exportAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := exportAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/openformresponse/export_test.go b/cmd/openformresponse/export_test.go index 596fec45..46671a7d 100644 --- a/cmd/openformresponse/export_test.go +++ b/cmd/openformresponse/export_test.go @@ -83,7 +83,7 @@ func setupMockCreateAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = exportAppSelectPromptFunc exportAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { exportAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/platform/activity.go b/cmd/platform/activity.go index 01a67c2e..96162a2f 100644 --- a/cmd/platform/activity.go +++ b/cmd/platform/activity.go @@ -105,7 +105,7 @@ func runActivityCommand(clients *shared.ClientFactory, cmd *cobra.Command, args } // Prompt for an installed app - selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/platform/activity_test.go b/cmd/platform/activity_test.go index ba9919d9..e947e868 100644 --- a/cmd/platform/activity_test.go +++ b/cmd/platform/activity_test.go @@ -65,7 +65,7 @@ func TestActivity_Command(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() appSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, nil) err := cmd.ExecuteContext(ctx) if err != nil { diff --git a/cmd/platform/deploy.go b/cmd/platform/deploy.go index 4d2fb191..0a15b78e 100644 --- a/cmd/platform/deploy.go +++ b/cmd/platform/deploy.go @@ -41,7 +41,6 @@ import ( // Create handle to Deploy function for testing // TODO - Stopgap until we learn the correct way to structure our code for testing. var deployFunc = platform.Deploy -var teamAppSelectPromptFunc = prompts.TeamAppSelectPrompt // TODO - Same as above, but probably even worse var runAddCommandFunc = app.RunAddCommand @@ -79,7 +78,7 @@ func NewDeployCommand(clients *shared.ClientFactory) *cobra.Command { deploySpinner.Stop() }() - selection, err := teamAppSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowAllApps) + selection, err := appSelectPromptFunc(ctx, clients, prompts.ShowHostedOnly, prompts.ShowAllApps) if err != nil { return err } diff --git a/cmd/platform/deploy_test.go b/cmd/platform/deploy_test.go index e3706abc..f30451d8 100644 --- a/cmd/platform/deploy_test.go +++ b/cmd/platform/deploy_test.go @@ -84,8 +84,8 @@ func TestDeployCommand(t *testing.T) { }, nil) appSelectMock := prompts.NewAppSelectMock() - appSelectMock.On("TeamAppSelectPrompt").Return(prompts.SelectedApp{}, nil) - teamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowHostedOnly, prompts.ShowAllApps).Return(prompts.SelectedApp{}, nil) + appSelectPromptFunc = appSelectMock.AppSelectPrompt manifestMock := &app.ManifestMockObject{} manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{ diff --git a/cmd/platform/run.go b/cmd/platform/run.go index c424b037..a755a559 100644 --- a/cmd/platform/run.go +++ b/cmd/platform/run.go @@ -45,7 +45,7 @@ var runFlags runCmdFlags // TODO - Stopgap until we learn the correct way to structure our code for testing. var runFunc = platform.Run var runRunCommandFunc = RunRunCommand -var runTeamAppSelectPromptFunc = prompts.TeamAppSelectPrompt +var runAppSelectPromptFunc = prompts.AppSelectPrompt func NewRunCommand(clients *shared.ClientFactory) *cobra.Command { cmd := &cobra.Command{ @@ -99,7 +99,7 @@ func RunRunCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []str ctx := cmd.Context() // Get the workspace from the flag or prompt - selection, err := runTeamAppSelectPromptFunc(ctx, clients, prompts.ShowLocalOnly, prompts.ShowAllApps) + selection, err := runAppSelectPromptFunc(ctx, clients, prompts.ShowLocalOnly, prompts.ShowAllApps) if err != nil { switch slackerror.ToSlackError(err).Code { case slackerror.ErrDeployedAppNotSupported: diff --git a/cmd/platform/run_test.go b/cmd/platform/run_test.go index 5f867a2c..8d478df7 100644 --- a/cmd/platform/run_test.go +++ b/cmd/platform/run_test.go @@ -217,8 +217,8 @@ func TestRunCommand_Flags(t *testing.T) { }) appSelectMock := prompts.NewAppSelectMock() - appSelectMock.On("TeamAppSelectPrompt").Return(tt.selectedAppAuth, tt.selectedAppErr) - runTeamAppSelectPromptFunc = appSelectMock.TeamAppSelectPrompt + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowLocalOnly, prompts.ShowAllApps).Return(tt.selectedAppAuth, tt.selectedAppErr) + runAppSelectPromptFunc = appSelectMock.AppSelectPrompt runPkgMock := new(RunPkgMock) runFunc = runPkgMock.Run diff --git a/cmd/triggers/access.go b/cmd/triggers/access.go index 1e8f05a7..1fd0567e 100644 --- a/cmd/triggers/access.go +++ b/cmd/triggers/access.go @@ -102,7 +102,7 @@ func runAccessCommand(cmd *cobra.Command, clients *shared.ClientFactory) error { defer span.Finish() // Get the app selection and accompanying auth from the flag or prompt - selection, err := accessAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := accessAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/triggers/access_test.go b/cmd/triggers/access_test.go index c8abacb5..18a5f3fd 100644 --- a/cmd/triggers/access_test.go +++ b/cmd/triggers/access_test.go @@ -601,7 +601,7 @@ func setupMockAccessAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = accessAppSelectPromptFunc accessAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { accessAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/triggers/create.go b/cmd/triggers/create.go index e93bae83..ed78fd6b 100644 --- a/cmd/triggers/create.go +++ b/cmd/triggers/create.go @@ -94,7 +94,7 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { defer span.Finish() // Get the app selection and accompanying auth from the flag or prompt - selection, err := createAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAndNewApps) + selection, err := createAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAndNewApps) if err != nil { return err } diff --git a/cmd/triggers/create_test.go b/cmd/triggers/create_test.go index a3e389cb..cdd93aea 100644 --- a/cmd/triggers/create_test.go +++ b/cmd/triggers/create_test.go @@ -468,7 +468,7 @@ func TestTriggersCreateCommand_AppSelection(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = createAppSelectPromptFunc createAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, errors.New("selection error")) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndNewApps).Return(prompts.SelectedApp{}, errors.New("selection error")) appSelectTeardown = func() { createAppSelectPromptFunc = originalPromptFunc } @@ -673,7 +673,7 @@ func setupMockCreateAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = createAppSelectPromptFunc createAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAndNewApps).Return(selectedApp, nil) return func() { createAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/triggers/delete.go b/cmd/triggers/delete.go index 0d133316..5523cfa3 100644 --- a/cmd/triggers/delete.go +++ b/cmd/triggers/delete.go @@ -66,7 +66,7 @@ func runDeleteCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { defer span.Finish() // Get the app from the flag or prompt - selection, err := deleteAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := deleteAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/triggers/delete_test.go b/cmd/triggers/delete_test.go index 96786f94..cb70a0d3 100644 --- a/cmd/triggers/delete_test.go +++ b/cmd/triggers/delete_test.go @@ -117,7 +117,7 @@ func TestTriggersDeleteCommand_AppSelection(t *testing.T) { appSelectTeardown = setupMockDeleteAppSelection(installedProdApp) appSelectMock := prompts.NewAppSelectMock() deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, errors.New("selection error")) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, errors.New("selection error")) }, Teardown: func() { appSelectTeardown() @@ -166,7 +166,7 @@ func setupMockDeleteAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = deleteAppSelectPromptFunc deleteAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { deleteAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/triggers/info.go b/cmd/triggers/info.go index 788f75ea..5c3f1599 100644 --- a/cmd/triggers/info.go +++ b/cmd/triggers/info.go @@ -65,7 +65,7 @@ func runInfoCommand(cmd *cobra.Command, clients *shared.ClientFactory) error { defer span.Finish() // Get the app from the flag or prompt - selection, err := infoAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := infoAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/triggers/info_test.go b/cmd/triggers/info_test.go index b518a111..cc95fc71 100644 --- a/cmd/triggers/info_test.go +++ b/cmd/triggers/info_test.go @@ -181,7 +181,7 @@ func TestTriggersInfoCommand_AppSelection(t *testing.T) { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = createAppSelectPromptFunc createAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(prompts.SelectedApp{}, errors.New("error")) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(prompts.SelectedApp{}, errors.New("error")) appSelectTeardown = func() { createAppSelectPromptFunc = originalPromptFunc } @@ -233,7 +233,7 @@ func setupMockInfoAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = infoAppSelectPromptFunc infoAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { infoAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/triggers/list.go b/cmd/triggers/list.go index 1c68c2e4..0bce5aca 100644 --- a/cmd/triggers/list.go +++ b/cmd/triggers/list.go @@ -76,7 +76,7 @@ func runListCommand(cmd *cobra.Command, clients *shared.ClientFactory) error { defer span.Finish() // Get the app selection and accompanying auth from the flag or prompt - selection, err := listAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := listAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/triggers/list_test.go b/cmd/triggers/list_test.go index 6309bd79..e6680489 100644 --- a/cmd/triggers/list_test.go +++ b/cmd/triggers/list_test.go @@ -183,7 +183,7 @@ func setupMockListAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = listAppSelectPromptFunc listAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { listAppSelectPromptFunc = originalPromptFunc } diff --git a/cmd/triggers/update.go b/cmd/triggers/update.go index 535bf534..e459c20d 100644 --- a/cmd/triggers/update.go +++ b/cmd/triggers/update.go @@ -78,7 +78,7 @@ func runUpdateCommand(clients *shared.ClientFactory, cmd *cobra.Command) error { defer span.Finish() // Get the app selection and accompanying auth from the flag or prompt - selection, err := updateAppSelectPromptFunc(ctx, clients, prompts.ShowInstalledAppsOnly) + selection, err := updateAppSelectPromptFunc(ctx, clients, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly) if err != nil { return err } diff --git a/cmd/triggers/update_test.go b/cmd/triggers/update_test.go index a56420e4..9421ac28 100644 --- a/cmd/triggers/update_test.go +++ b/cmd/triggers/update_test.go @@ -509,7 +509,7 @@ func setupMockUpdateAppSelection(selectedApp prompts.SelectedApp) func() { appSelectMock := prompts.NewAppSelectMock() var originalPromptFunc = updateAppSelectPromptFunc updateAppSelectPromptFunc = appSelectMock.AppSelectPrompt - appSelectMock.On("AppSelectPrompt").Return(selectedApp, nil) + appSelectMock.On("AppSelectPrompt", mock.Anything, mock.Anything, prompts.ShowAllEnvironments, prompts.ShowInstalledAppsOnly).Return(selectedApp, nil) return func() { updateAppSelectPromptFunc = originalPromptFunc } diff --git a/internal/app/app_client.go b/internal/app/app_client.go index a0df3a36..41df7ac1 100644 --- a/internal/app/app_client.go +++ b/internal/app/app_client.go @@ -458,7 +458,7 @@ func (ac *AppClient) migrateToAppByTeamIDLocal() error { // as team domain is not guaranteed to be unique func (ac *AppClient) getDeployedAppTeamDomain(ctx context.Context) string { // Find the app team using the priority: - // 1. Team Flag (set by the user or prompts.TeamAppSelectPrompt) + // 1. Team Flag (set by the user) if ac.config.TeamFlag != "" { return ac.config.TeamFlag } diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index 2abe2628..e953dc7b 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -374,8 +374,8 @@ func showOptionsForNewAppCreation(app types.App, status AppInstallStatus) bool { return !appExists(app) && (status == ShowAllApps || status == ShowInstalledAndNewApps) } -// flatAppSelectPrompt reveals options for apps that match the install status -func flatAppSelectPrompt( +// AppSelectPrompt reveals options for apps that match the install status +func AppSelectPrompt( ctx context.Context, clients *shared.ClientFactory, environment AppEnvironmentType, @@ -388,14 +388,17 @@ func flatAppSelectPrompt( case environment.Equals(ShowAllEnvironments) && types.IsAppFlagEnvironment(clients.Config.AppFlag): switch { case types.IsAppFlagDeploy(clients.Config.AppFlag): - return flatAppSelectPrompt(ctx, clients, ShowHostedOnly, status) + return AppSelectPrompt(ctx, clients, ShowHostedOnly, status) case types.IsAppFlagLocal(clients.Config.AppFlag): - return flatAppSelectPrompt(ctx, clients, ShowLocalOnly, status) + return AppSelectPrompt(ctx, clients, ShowLocalOnly, status) } case environment.Equals(ShowLocalOnly) && types.IsAppFlagDeploy(clients.Config.AppFlag): return SelectedApp{}, slackerror.New(slackerror.ErrDeployedAppNotSupported) case environment.Equals(ShowHostedOnly) && types.IsAppFlagLocal(clients.Config.AppFlag): return SelectedApp{}, slackerror.New(slackerror.ErrLocalAppNotSupported) + case clients.Config.AppFlag != "" && !types.IsAppFlagValid(clients.Config.AppFlag): + return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag). + WithRemediation("Choose a specific app with %s", style.Highlight("--app ")) } defer func() { if err != nil { @@ -622,7 +625,7 @@ func flatAppSelectPrompt( case selection.Prompt && options[selection.Index].label != creation: return options[selection.Index].app, nil case selection.Prompt && options[selection.Index].label == creation: - team, err := flatTeamSelectPrompt(ctx, clients) + team, err := teamSelectPrompt(ctx, clients) if err != nil { return SelectedApp{}, err } @@ -648,30 +651,8 @@ func flatAppSelectPrompt( return SelectedApp{}, slackerror.New(slackerror.ErrAppNotFound) } -// AppSelectPrompt prompts the user to select a workspace then environment for the current command, -// returning the selected app. This app might require installation before use if `status == ShowAllApps`. -func AppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, status AppInstallStatus) (SelectedApp, error) { - var appFlag = clients.Config.AppFlag // e.g. 'local', 'deploy', 'deployed', A12345 - var tokenFlag = clients.Config.TokenFlag // e.g. xoxe.xoxp.xxxx - - if clients.Config.SkipLocalFs() { - clients.IO.PrintDebug(ctx, "selecting app based on token value and app id value '%s'", appFlag) - selection, err := getTokenApp(ctx, clients, tokenFlag, appFlag) - if err != nil { - return SelectedApp{}, err - } - if status == ShowInstalledAppsOnly && selection.App.InstallStatus != types.AppStatusInstalled { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - clients.Auth().SetSelectedAuth(ctx, selection.Auth, clients.Config, clients.Os) - return selection, nil - } - - return flatAppSelectPrompt(ctx, clients, ShowAllEnvironments, status) -} - -// flatTeamSelectPrompt shows choices for authenticated teams -func flatTeamSelectPrompt( +// teamSelectPrompt shows choices for authenticated teams +func teamSelectPrompt( ctx context.Context, clients *shared.ClientFactory, ) ( @@ -753,50 +734,6 @@ func flatTeamSelectPrompt( return types.SlackAuth{}, slackerror.New(slackerror.ErrTeamNotFound) } -// TeamAppSelectPrompt prompts the user to select an app from a specified team environment, -// returning the selected app. This app might require installation before use if `status == ShowAllApps`. -func TeamAppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, env AppEnvironmentType, status AppInstallStatus) (SelectedApp, error) { - var appFlag = clients.Config.AppFlag - var tokenFlag = clients.Config.TokenFlag - - // Error if an invalid or mismatched --app flag is provided - if appFlag != "" && !types.IsAppFlagValid(appFlag) { - return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag). - WithRemediation("Choose a specific app with %s", style.Highlight("--app ")) - } - if env == ShowHostedOnly && types.IsAppFlagLocal(appFlag) { - return SelectedApp{}, slackerror.New(slackerror.ErrLocalAppNotSupported) - } - if env == ShowLocalOnly && types.IsAppFlagDeploy(appFlag) { - return SelectedApp{}, slackerror.New(slackerror.ErrDeployedAppNotSupported) - } - - if clients.Config.SkipLocalFs() { - clients.IO.PrintDebug(ctx, "selecting app based on token value and app id value '%s'", appFlag) - selection, err := getTokenApp(ctx, clients, tokenFlag, appFlag) - if err != nil { - return SelectedApp{}, err - } - if status == ShowInstalledAppsOnly && selection.App.InstallStatus != types.AppStatusInstalled { - return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) - } - clients.Auth().SetSelectedAuth(ctx, selection.Auth, clients.Config, clients.Os) - // The development status of an app cannot be determined when local app files - // do not exist. This defaults to "false" for these cases. - // - // Commands such as "platform run" might allow unknown development statuses so - // we return both the selection and an error here. - if selection.App.IsDev && env == ShowHostedOnly { - return selection, slackerror.New(slackerror.ErrLocalAppNotSupported) - } else if !selection.App.IsDev && env == ShowLocalOnly { - return selection, slackerror.New(slackerror.ErrDeployedAppNotSupported) - } - return selection, nil - } - - return flatAppSelectPrompt(ctx, clients, env, status) -} - // OrgSelectWorkspacePrompt prompts the user to select a single workspace to grant app access to, or grant all workspaces within the org. func OrgSelectWorkspacePrompt(ctx context.Context, clients *shared.ClientFactory, orgDomain, token string, topOptionAllWorkspaces bool) (string, error) { teams, paginationCursor, err := clients.API().AuthTeamsList(ctx, token, api.DefaultAuthTeamsListPageSize) diff --git a/internal/prompts/app_select_mock.go b/internal/prompts/app_select_mock.go index ac72c2f0..369ff711 100644 --- a/internal/prompts/app_select_mock.go +++ b/internal/prompts/app_select_mock.go @@ -31,14 +31,8 @@ func NewAppSelectMock() *AppSelectMock { return &AppSelectMock{} } -// TeamAppSelectPrompt mocks the workspace selection prompt -func (m *AppSelectMock) TeamAppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, runEnv AppEnvironmentType, status AppInstallStatus) (SelectedApp, error) { - args := m.Called() - return args.Get(0).(SelectedApp), args.Error(1) -} - // AppSelectPrompt mocks the app selection prompt -func (m *AppSelectMock) AppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, status AppInstallStatus) (SelectedApp, error) { - args := m.Called() +func (m *AppSelectMock) AppSelectPrompt(ctx context.Context, clients *shared.ClientFactory, env AppEnvironmentType, status AppInstallStatus) (SelectedApp, error) { + args := m.Called(ctx, clients, env, status) return args.Get(0).(SelectedApp), args.Error(1) } diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index d623a9dd..ca1f5382 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -281,21 +281,22 @@ func TestGetTokenApp(t *testing.T) { func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { tests := map[string]struct { - tokenFlag string - tokenAuth types.SlackAuth - appFlag string - appStatus api.GetAppStatusResult - statusErr error - selectStatus AppInstallStatus - expectedApp SelectedApp - expectedErr error + tokenFlag string + tokenAuth types.SlackAuth + appFlag string + appStatus api.GetAppStatusResult + appStatusErr error + selectEnvironment AppEnvironmentType + selectStatus AppInstallStatus + expectedApp SelectedApp + expectedErr error }{ "error if an error occurred while collecting app info": { tokenFlag: team1Token, tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], appFlag: localTeam1UninstalledApp.AppID, appStatus: api.GetAppStatusResult{}, - statusErr: slackerror.New(slackerror.ErrAppNotFound), + appStatusErr: slackerror.New(slackerror.ErrAppNotFound), selectStatus: ShowAllApps, expectedApp: SelectedApp{}, expectedErr: slackerror.New(slackerror.ErrAppNotFound), @@ -311,7 +312,7 @@ func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { Hosted: true, }}, }, - statusErr: nil, + appStatusErr: nil, selectStatus: ShowInstalledAppsOnly, expectedApp: SelectedApp{}, expectedErr: slackerror.New(slackerror.ErrInstallationRequired), @@ -324,10 +325,10 @@ func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { Apps: []api.AppStatusResultAppInfo{{ AppID: deployedTeam1InstalledAppID, Installed: deployedTeam1AppIsInstalled, - Hosted: true, + Hosted: false, }}, }, - statusErr: nil, + appStatusErr: nil, selectStatus: ShowInstalledAppsOnly, expectedApp: SelectedApp{ Auth: fakeAuthsByTeamDomain[team1TeamDomain], @@ -335,6 +336,26 @@ func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { }, expectedErr: nil, }, + "returns app details without respect to the app environment": { + tokenFlag: team2Token, + tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], + appFlag: deployedTeam2UninstalledAppID, + appStatus: api.GetAppStatusResult{ + Apps: []api.AppStatusResultAppInfo{{ + AppID: deployedTeam2UninstalledAppID, + Installed: deployedTeam2AppIsInstalled, + Hosted: true, + }}, + }, + appStatusErr: nil, + selectEnvironment: ShowLocalOnly, + selectStatus: ShowInstalledAndUninstalledApps, + expectedApp: SelectedApp{ + Auth: fakeAuthsByTeamDomain[team2TeamDomain], + App: deployedTeam2UninstalledApp, + }, + expectedErr: nil, + }, } for name, test := range tests { @@ -344,17 +365,21 @@ func TestPrompt_AppSelectPrompt_TokenAppFlag(t *testing.T) { clientsMock.Auth.On(AuthWithToken, mock.Anything, test.tokenFlag). Return(test.tokenAuth, nil) clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(test.appStatus, test.statusErr) + Return(test.appStatus, test.appStatusErr) + clientsMock.API.On("ValidateSession", mock.Anything, mock.Anything).Return(api.AuthSession{ + TeamName: &test.tokenAuth.TeamDomain, + TeamID: &test.tokenAuth.TeamID, + }, nil) clientsMock.AddDefaultMocks() clients := shared.NewClientFactory(clientsMock.MockClientFactory()) clients.Config.TokenFlag = test.tokenFlag clients.Config.AppFlag = test.appFlag - selection, err := AppSelectPrompt(ctx, clients, test.selectStatus) + selection, err := AppSelectPrompt(ctx, clients, ShowAllEnvironments, test.selectStatus) - if test.statusErr != nil && assert.Error(t, err) { - require.Equal(t, test.statusErr, err) + if test.appStatusErr != nil && assert.Error(t, err) { + require.Equal(t, test.appStatusErr, err) } else if test.expectedErr != nil && assert.Error(t, err) { require.Equal(t, test.expectedErr, err) } else { @@ -619,7 +644,7 @@ func TestPrompt_AppSelectPrompt_GetApps(t *testing.T) { } } -func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { +func TestPrompt_AppSelectPrompt(t *testing.T) { tests := map[string]struct { mockAuths []types.SlackAuth mockAuthWithTeamIDError error @@ -1353,7 +1378,7 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { err := clients.AppClient().SaveLocal(ctx, app) require.NoError(t, err) } - selectedApp, err := flatAppSelectPrompt(ctx, clients, tt.appPromptConfigEnvironment, tt.appPromptConfigStatus) + selectedApp, err := AppSelectPrompt(ctx, clients, tt.appPromptConfigEnvironment, tt.appPromptConfigStatus) require.Equal(t, tt.expectedError, err) require.Equal(t, tt.expectedSelection, selectedApp) require.Contains(t, clientsMock.GetStdoutOutput(), tt.expectedStdout) @@ -1362,146 +1387,6 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { } } -// -// TeamAppSelectPrompt tests -// - -func TestPrompt_TeamAppSelectPrompt_TokenAppFlag(t *testing.T) { - tests := map[string]struct { - tokenFlag string - tokenAuth types.SlackAuth - appFlag string - appStatus api.GetAppStatusResult - statusErr error - saveLocal []types.App - selectEnv AppEnvironmentType - selectStatus AppInstallStatus - expectedApp SelectedApp - expectedErr error - }{ - "error if an error occurred while collecting app info": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: localTeam1UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{}, - statusErr: slackerror.New(slackerror.ErrAppNotFound), - selectEnv: ShowHostedOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrAppNotFound), - }, - "continue if a saved local app is used for a deployed only prompt": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: localTeam1UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: localTeam1UninstalledAppID, - Installed: localTeam1AppIsInstalled, - Hosted: false, - }}, - }, - statusErr: nil, - saveLocal: []types.App{localTeam1UninstalledApp}, - selectEnv: ShowHostedOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{ - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - App: localTeam1UninstalledApp, - }, - expectedErr: slackerror.New(slackerror.ErrLocalAppNotSupported), - }, - "error if a deployed app is used for a local only prompt": { - tokenFlag: team2Token, - tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], - appFlag: deployedTeam2UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam2UninstalledApp.AppID, - Installed: deployedTeam2AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowLocalOnly, - selectStatus: ShowAllApps, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrDeployedAppNotSupported), - }, - "error if an uninstalled app is used for an installed only prompt": { - tokenFlag: team2Token, - tokenAuth: fakeAuthsByTeamDomain[team2TeamDomain], - appFlag: deployedTeam2UninstalledApp.AppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam2UninstalledApp.AppID, - Installed: deployedTeam2AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowHostedOnly, - selectStatus: ShowInstalledAppsOnly, - expectedApp: SelectedApp{}, - expectedErr: slackerror.New(slackerror.ErrInstallationRequired), - }, - "returns known information about the request app": { - tokenFlag: team1Token, - tokenAuth: fakeAuthsByTeamDomain[team1TeamDomain], - appFlag: deployedTeam1InstalledAppID, - appStatus: api.GetAppStatusResult{ - Apps: []api.AppStatusResultAppInfo{{ - AppID: deployedTeam1InstalledAppID, - Installed: deployedTeam1AppIsInstalled, - Hosted: true, - }}, - }, - statusErr: nil, - selectEnv: ShowHostedOnly, - selectStatus: ShowInstalledAppsOnly, - expectedApp: SelectedApp{ - Auth: fakeAuthsByTeamDomain[team1TeamDomain], - App: deployedTeam1InstalledApp, - }, - expectedErr: nil, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - ctx := slackcontext.MockContext(t.Context()) - clientsMock := shared.NewClientsMock() - clientsMock.Auth.On(AuthWithToken, mock.Anything, test.tokenFlag). - Return(test.tokenAuth, nil) - clientsMock.API.On(GetAppStatus, mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(test.appStatus, test.statusErr) - clientsMock.AddDefaultMocks() - - clients := shared.NewClientFactory(clientsMock.MockClientFactory()) - for _, app := range test.saveLocal { - err := clients.AppClient().SaveLocal(ctx, app) - require.NoError(t, err) - } - clients.Config.TokenFlag = test.tokenFlag - clients.Config.AppFlag = test.appFlag - - selection, err := TeamAppSelectPrompt(ctx, clients, test.selectEnv, test.selectStatus) - - if test.statusErr != nil && assert.Error(t, err) { - require.Equal(t, test.statusErr, err) - } else if test.expectedErr != nil && assert.Error(t, err) { - require.Equal(t, test.expectedErr, err) - } else { - require.NoError(t, err) - assert.Equal(t, test.expectedApp.Auth, selection.Auth) - expectedApp := test.expectedApp.App - expectedApp.UserID = test.expectedApp.Auth.UserID - assert.Equal(t, expectedApp, selection.App) - } - }) - } -} - func TestSortAlphaNumeric_Sorted(t *testing.T) { items := []string{"alphabetical", "bordering"} labels := []string{"_alphabetical_ T001", "_bordering_ T1"} From 8466439834dd1d97c1cb0ef8e41ba36e0c05b6fb Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Wed, 16 Jul 2025 23:53:28 -0700 Subject: [PATCH 6/9] test: error if app id flag is not valid --- internal/prompts/app_select.go | 3 +-- internal/prompts/app_select_test.go | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index e953dc7b..9dde242a 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -397,8 +397,7 @@ func AppSelectPrompt( case environment.Equals(ShowHostedOnly) && types.IsAppFlagLocal(clients.Config.AppFlag): return SelectedApp{}, slackerror.New(slackerror.ErrLocalAppNotSupported) case clients.Config.AppFlag != "" && !types.IsAppFlagValid(clients.Config.AppFlag): - return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag). - WithRemediation("Choose a specific app with %s", style.Highlight("--app ")) + return SelectedApp{}, slackerror.New(slackerror.ErrInvalidAppFlag) } defer func() { if err != nil { diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index ca1f5382..64275dbf 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -983,6 +983,10 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, + "errors if app id flag is not valid": { + mockFlagApp: "123", + expectedError: slackerror.New(slackerror.ErrInvalidAppFlag), + }, "errors if app id flag has a team id flag that does not match": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ From 5b9f5aba68d05240051b2c9deb796d0db3c9ebbf Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Thu, 17 Jul 2025 15:08:55 -0700 Subject: [PATCH 7/9] chore: default to bolt-install experiment behavior for app selection --- internal/prompts/app_select.go | 96 +++++++++++++---------------- internal/prompts/app_select_test.go | 27 +------- 2 files changed, 46 insertions(+), 77 deletions(-) diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index 9dde242a..29f56eeb 100644 --- a/internal/prompts/app_select.go +++ b/internal/prompts/app_select.go @@ -24,8 +24,6 @@ import ( "github.com/slackapi/slack-cli/internal/api" "github.com/slackapi/slack-cli/internal/cmdutil" - "github.com/slackapi/slack-cli/internal/config" - "github.com/slackapi/slack-cli/internal/experiment" "github.com/slackapi/slack-cli/internal/iostreams" authpkg "github.com/slackapi/slack-cli/internal/pkg/auth" "github.com/slackapi/slack-cli/internal/shared" @@ -476,10 +474,6 @@ func AppSelectPrompt( filtered[id] = app } } - manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx) - if err != nil { - return SelectedApp{}, err - } type Selection struct { app SelectedApp label string @@ -524,58 +518,54 @@ func AppSelectPrompt( return SelectedApp{}, slackerror.New(slackerror.ErrInstallationRequired) } case ShowAllApps, ShowInstalledAndNewApps: - isManifestSourceLocal := manifestSource.Equals(config.ManifestSourceLocal) - isBoltInstallEnabled := clients.Config.WithExperimentOn(experiment.BoltInstall) - if isManifestSourceLocal || isBoltInstallEnabled { - option := Selection{ - label: style.Secondary("Create a new app"), - } - options = append(options, option) - switch { - case types.IsAppID(clients.Config.AppFlag): - // Skip to match the app ID later - case teamFlag != "": - // Check for an existing app ID - selections := []SelectedApp{} - for _, app := range filtered { - switch teamFlag { - case app.App.TeamID, app.App.TeamDomain: - switch { - case environment.Equals(ShowAllEnvironments): - selections = append(selections, app) - case environment.Equals(ShowHostedOnly) && !app.App.IsDev: - selections = append(selections, app) - case environment.Equals(ShowLocalOnly) && app.App.IsDev: - selections = append(selections, app) - } + option := Selection{ + label: style.Secondary("Create a new app"), + } + options = append(options, option) + switch { + case types.IsAppID(clients.Config.AppFlag): + // Skip to match the app ID later + case teamFlag != "": + // Check for an existing app ID + selections := []SelectedApp{} + for _, app := range filtered { + switch teamFlag { + case app.App.TeamID, app.App.TeamDomain: + switch { + case environment.Equals(ShowAllEnvironments): + selections = append(selections, app) + case environment.Equals(ShowHostedOnly) && !app.App.IsDev: + selections = append(selections, app) + case environment.Equals(ShowLocalOnly) && app.App.IsDev: + selections = append(selections, app) } } - switch len(selections) { - case 0: - // Skip to create a new app - case 1: - return selections[0], nil - default: - return SelectedApp{}, slackerror.New(slackerror.ErrAppFound). - WithMessage("Multiple apps exist for the provided team") - } - // Create a new app if none exists - auths, err := getAuths(ctx, clients) - if err != nil { - return SelectedApp{}, err - } - for _, auth := range auths { - switch teamFlag { - case auth.TeamID, auth.TeamDomain: - app := SelectedApp{ - App: types.NewApp(), - Auth: auth, - } - return app, nil + } + switch len(selections) { + case 0: + // Skip to create a new app + case 1: + return selections[0], nil + default: + return SelectedApp{}, slackerror.New(slackerror.ErrAppFound). + WithMessage("Multiple apps exist for the provided team") + } + // Create a new app if none exists + auths, err := getAuths(ctx, clients) + if err != nil { + return SelectedApp{}, err + } + for _, auth := range auths { + switch teamFlag { + case auth.TeamID, auth.TeamDomain: + app := SelectedApp{ + App: types.NewApp(), + Auth: auth, } + return app, nil } - return SelectedApp{}, slackerror.New(slackerror.ErrTeamNotFound) } + return SelectedApp{}, slackerror.New(slackerror.ErrTeamNotFound) } } labels := []string{} diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 64275dbf..4a6f2b4a 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -20,7 +20,6 @@ import ( "time" "github.com/slackapi/slack-cli/internal/api" - "github.com/slackapi/slack-cli/internal/config" "github.com/slackapi/slack-cli/internal/hooks" "github.com/slackapi/slack-cli/internal/iostreams" "github.com/slackapi/slack-cli/internal/shared" @@ -655,7 +654,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockFlagApp string mockFlagTeam string mockFlagToken string - mockManifestSource config.ManifestSource appPromptConfigEnvironment AppEnvironmentType appPromptConfigOptions []string appPromptConfigStatus AppInstallStatus @@ -702,7 +700,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { UserID: team2UserID, }, }, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowAllEnvironments, appPromptConfigOptions: []string{ "A1 team1 T1", @@ -727,7 +724,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { "returns new application if selected": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{}, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigOptions: []string{ "Create a new app", @@ -746,7 +742,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { "errors if installation required and no apps saved": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{}, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowInstalledAppsOnly, expectedError: slackerror.New(slackerror.ErrInstallationRequired), @@ -772,7 +767,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { appPromptResponseOption: "Create a new app", teamPromptResponseFlag: true, teamPromptResponseOption: team1TeamDomain, - mockManifestSource: config.ManifestSourceLocal, expectedError: slackerror.New(slackerror.ErrAppExists). WithDetails(slackerror.ErrorDetails{{ Message: `The app "A1" already exists for team "team1" (T1)`, @@ -783,7 +777,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockAuths: fakeAuthsByTeamDomainSlice, mockFlagApp: "deployed", mockFlagTeam: team1TeamID, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowInstalledAndNewApps, expectedSelection: SelectedApp{ @@ -791,7 +784,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, - "selects existing application for app environment flag and team id flag if app saved": { + "returns existing application for app environment flag and team id flag if app saved": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ { @@ -802,7 +795,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { }, mockFlagApp: "deployed", mockFlagTeam: team2TeamID, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowAllApps, expectedSelection: SelectedApp{ @@ -851,6 +843,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { "A1 team1 T1", "A2 team2 T2", }, + appPromptConfigStatus: ShowInstalledAndUninstalledApps, appPromptResponsePrompt: true, appPromptResponseIndex: 1, expectedSelection: SelectedApp{ @@ -1049,7 +1042,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockFlagTeam: "TNOTFOUND", appPromptConfigEnvironment: ShowAllEnvironments, appPromptConfigStatus: ShowAllApps, - expectedError: slackerror.New(slackerror.ErrAppNotFound), + expectedError: slackerror.New(slackerror.ErrTeamNotFound), }, "errors if deployed app environment flag for local app prompt": { mockFlagApp: "deploy", @@ -1064,7 +1057,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { "errors if deployed app environment flag and team id flag for local app prompt": { mockFlagApp: "deployed", mockFlagTeam: team1TeamID, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowLocalOnly, appPromptConfigStatus: ShowInstalledAndNewApps, expectedError: slackerror.New(slackerror.ErrDeployedAppNotSupported), @@ -1072,14 +1064,12 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { "errors if local app environment flag and team id flag for hosted app prompt": { mockFlagApp: "local", mockFlagTeam: team1TeamID, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowInstalledAndNewApps, expectedError: slackerror.New(slackerror.ErrLocalAppNotSupported), }, "errors if team id flag does not have authorization": { mockFlagTeam: team1TeamID, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigEnvironment: ShowHostedOnly, appPromptConfigStatus: ShowInstalledAndNewApps, expectedError: slackerror.New(slackerror.ErrTeamNotFound), @@ -1128,7 +1118,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockAuthWithTeamIDTeamID: team1TeamID, mockFlagTeam: team1TeamID, mockFlagToken: fakeAuthsByTeamDomain[team1TeamDomain].Token, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigStatus: ShowInstalledAndNewApps, appPromptConfigEnvironment: ShowHostedOnly, expectedSelection: SelectedApp{ @@ -1145,7 +1134,6 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockAuthWithTeamIDTeamID: team1TeamID, mockFlagTeam: team1TeamID, mockFlagToken: fakeAuthsByTeamDomain[team1TeamDomain].Token, - mockManifestSource: config.ManifestSourceLocal, appPromptConfigStatus: ShowAllApps, appPromptConfigEnvironment: ShowLocalOnly, expectedSelection: SelectedApp{ @@ -1361,16 +1349,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { nil, ) clientsMock.AddDefaultMocks() - projectConfigMock := config.NewProjectConfigMock() - projectConfigMock.On( - "GetManifestSource", - mock.Anything, - ).Return( - tt.mockManifestSource, - nil, - ) clientsMock.Config.AppFlag = tt.mockFlagApp - clientsMock.Config.ProjectConfig = projectConfigMock clientsMock.Config.TeamFlag = tt.mockFlagTeam clientsMock.Config.TokenFlag = tt.mockFlagToken clients := shared.NewClientFactory(clientsMock.MockClientFactory()) From f9b74e0086fcb202435e92edf8f572d357037e89 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Thu, 17 Jul 2025 19:01:01 -0700 Subject: [PATCH 8/9] test: confirm installation requirements or single choices are selected --- internal/prompts/app_select_test.go | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 4a6f2b4a..1dc10d63 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -807,6 +807,27 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team2TeamDomain], }, }, + "returns installed application for app environment flag and team id flag if app saved": { + mockAuths: fakeAuthsByTeamDomainSlice, + mockAppsLocal: []types.App{ + localTeam2InstalledApp, + }, + mockFlagApp: "local", + mockFlagTeam: team2TeamID, + appPromptConfigEnvironment: ShowAllEnvironments, + appPromptConfigStatus: ShowInstalledAppsOnly, + expectedSelection: SelectedApp{ + App: types.App{ + AppID: localTeam2InstalledAppID, + TeamDomain: team2TeamDomain, + TeamID: team2TeamID, + UserID: team2UserID, + IsDev: true, + InstallStatus: types.AppStatusInstalled, + }, + Auth: fakeAuthsByTeamDomain[team2TeamDomain], + }, + }, "returns filtered deployed apps for app environment flag before selection": { mockAuths: fakeAuthsByTeamDomainSlice, mockAppsDeployed: []types.App{ @@ -944,6 +965,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockFlagApp: deployedTeam1InstalledAppID, mockFlagTeam: team1TeamID, appPromptConfigEnvironment: ShowHostedOnly, + appPromptConfigStatus: ShowAllApps, expectedSelection: SelectedApp{ App: types.App{ AppID: deployedTeam1InstalledAppID, @@ -966,6 +988,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockFlagApp: deployedTeam1InstalledAppID, mockFlagTeam: team1TeamDomain, appPromptConfigEnvironment: ShowAllEnvironments, + appPromptConfigStatus: ShowAllApps, expectedSelection: SelectedApp{ App: types.App{ AppID: deployedTeam1InstalledAppID, @@ -1015,6 +1038,7 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { mockFlagApp: "local", mockFlagTeam: team1TeamID, appPromptConfigEnvironment: ShowAllEnvironments, + appPromptConfigStatus: ShowAllApps, expectedSelection: SelectedApp{ App: types.App{ AppID: localTeam1UninstalledAppID, @@ -1141,6 +1165,20 @@ func TestPrompt_AppSelectPrompt(t *testing.T) { Auth: fakeAuthsByTeamDomain[team1TeamDomain], }, }, + "returns selection for token flag if one app saved": { + mockAppsLocal: []types.App{ + localTeam1UninstalledApp, + localTeam2InstalledApp, + }, + mockAuthWithToken: fakeAuthsByTeamDomain[team2TeamDomain], + mockAuthWithTeamIDError: slackerror.New(slackerror.ErrCredentialsNotFound), + mockAuthWithTeamIDTeamID: mock.Anything, + mockFlagToken: fakeAuthsByTeamDomain[team2TeamDomain].Token, + expectedSelection: SelectedApp{ + App: localTeam2InstalledApp, + Auth: fakeAuthsByTeamDomain[team2TeamDomain], + }, + }, "errors if token flag and team id flag do not match": { mockFlagTeam: team1TeamID, mockFlagToken: fakeAuthsByTeamDomain[team2TeamDomain].Token, From ae0d6025bb6f10a2a99b70333ffab90f354ff171 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Thu, 17 Jul 2025 20:38:21 -0700 Subject: [PATCH 9/9] style: remove a comment about removed mocks --- internal/prompts/app_select_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/prompts/app_select_test.go b/internal/prompts/app_select_test.go index 1dc10d63..c171e420 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -1616,8 +1616,6 @@ func Test_ValidateGetOrgWorkspaceGrant(t *testing.T) { // Test_ValidateAuth tests edge cases of the reauthentication logic for certain // errors -// -// Successful reauthentication in other functions might use default mocks below func Test_ValidateAuth(t *testing.T) { apiHostDev := "https://dev.slack.com" tests := map[string]struct {