diff --git a/internal/prompts/app_select.go b/internal/prompts/app_select.go index a5a26a82..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,75 +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{}) -} - -// 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. -// -// * 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{ @@ -177,7 +92,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 @@ -343,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) @@ -577,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) @@ -655,175 +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) -} - -// 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 +374,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 +651,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 +667,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 +756,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 +794,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..d623a9dd 100644 --- a/internal/prompts/app_select_test.go +++ b/internal/prompts/app_select_test.go @@ -15,8 +15,6 @@ package prompts import ( - "context" - "errors" "fmt" "testing" "time" @@ -170,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 // @@ -400,237 +275,10 @@ 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 // -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,833 +368,43 @@ 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, + "returns deployed and local apps with matching auths": { + mockAuths: fakeAuthsByTeamDomainSlice, + mockAppsSavedDeployed: []types.App{ + deployedTeam1InstalledApp, + deployedTeam2UninstalledApp, }, mockAppsSavedLocal: []types.App{ localTeam1UninstalledApp, @@ -1789,7 +647,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 +699,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 +754,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 +790,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 +838,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{ { @@ -2235,7 +1093,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 token flag and team id flag if app not saved": { mockAuthWithToken: fakeAuthsByTeamDomain[team1TeamDomain], mockAuthWithTeamIDError: slackerror.New(slackerror.ErrCredentialsNotFound), mockAuthWithTeamIDTeamID: team1TeamID, @@ -2455,2328 +1313,192 @@ func TestPrompt_AppSelectPrompt_FlatAppSelectPrompt(t *testing.T) { nil, ) clientsMock.IO.On( - SelectPrompt, - mock.Anything, - "Select an app", - tt.appPromptConfigOptions, - iostreams.MatchPromptConfig( - iostreams.SelectPromptConfig{ - Required: true, - }, - ), - ).Return( - iostreams.SelectPromptResponse{ - 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) + SelectPrompt, + mock.Anything, + "Select an app", + tt.appPromptConfigOptions, + iostreams.MatchPromptConfig( + iostreams.SelectPromptConfig{ + Required: true, + }, + ), + ).Return( + iostreams.SelectPromptResponse{ + 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) + }) } } -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 +1903,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() -} 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") - } -}