github.com/arunkumar7540/cli@v6.45.0+incompatible/command/v6/login_command_test.go (about)

     1  package v6_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/actor/v3action"
    10  	"code.cloudfoundry.org/cli/api/uaa"
    11  	"code.cloudfoundry.org/cli/api/uaa/constant"
    12  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    13  	"code.cloudfoundry.org/cli/command/commandfakes"
    14  	"code.cloudfoundry.org/cli/command/translatableerror"
    15  	. "code.cloudfoundry.org/cli/command/v6"
    16  	"code.cloudfoundry.org/cli/command/v6/v6fakes"
    17  	"code.cloudfoundry.org/cli/util/configv3"
    18  	"code.cloudfoundry.org/cli/util/ui"
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/gomega"
    21  	. "github.com/onsi/gomega/gbytes"
    22  )
    23  
    24  var _ = Describe("login Command", func() {
    25  	var (
    26  		binaryName       string
    27  		cmd              LoginCommand
    28  		testUI           *ui.UI
    29  		fakeActor        *v6fakes.FakeLoginActor
    30  		fakeChecker      *v6fakes.FakeVersionChecker
    31  		fakeConfig       *commandfakes.FakeConfig
    32  		fakeActorMaker   *v6fakes.FakeActorMaker
    33  		fakeCheckerMaker *v6fakes.FakeCheckerMaker
    34  		executeErr       error
    35  		input            *Buffer
    36  	)
    37  
    38  	BeforeEach(func() {
    39  		input = NewBuffer()
    40  		testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer())
    41  		fakeConfig = new(commandfakes.FakeConfig)
    42  		fakeActor = new(v6fakes.FakeLoginActor)
    43  		fakeActorMaker = new(v6fakes.FakeActorMaker)
    44  		fakeActorMaker.NewActorReturns(fakeActor, nil)
    45  
    46  		fakeChecker = new(v6fakes.FakeVersionChecker)
    47  		fakeCheckerMaker = new(v6fakes.FakeCheckerMaker)
    48  		fakeCheckerMaker.NewVersionCheckerReturns(fakeChecker, nil)
    49  		binaryName = "some-executable"
    50  		fakeConfig.BinaryNameReturns(binaryName)
    51  
    52  		cmd = LoginCommand{
    53  			UI:           testUI,
    54  			Actor:        fakeActor,
    55  			ActorMaker:   fakeActorMaker,
    56  			Config:       fakeConfig,
    57  			CheckerMaker: fakeCheckerMaker,
    58  		}
    59  		cmd.APIEndpoint = ""
    60  	})
    61  
    62  	JustBeforeEach(func() {
    63  		executeErr = cmd.Execute(nil)
    64  	})
    65  
    66  	When("the experimental login flag is not set", func() {
    67  		It("returns an UnrefactoredCommandError", func() {
    68  			Expect(executeErr).To(MatchError(translatableerror.UnrefactoredCommandError{}))
    69  		})
    70  	})
    71  
    72  	When("the experimental login flag is set", func() {
    73  		BeforeEach(func() {
    74  			fakeConfig.ExperimentalLoginReturns(true)
    75  		})
    76  
    77  		It("displays a helpful warning", func() {
    78  			Expect(testUI.Err).To(Say("Using experimental login command, some behavior may be different"))
    79  		})
    80  
    81  		// When("the entire flow succeeds", func() {
    82  		// 	It("displays all warnings", func() {
    83  		// 		Expect(false).To(BeTrue())
    84  		// 	})
    85  		// })
    86  
    87  		Describe("API Endpoint", func() {
    88  			BeforeEach(func() {
    89  				fakeConfig.APIVersionReturns("3.4.5")
    90  			})
    91  
    92  			When("user provides the api endpoint using the -a flag", func() {
    93  				BeforeEach(func() {
    94  					cmd.APIEndpoint = "api.boshlite.com"
    95  				})
    96  
    97  				It("target the provided api endpoint", func() {
    98  					Expect(executeErr).ToNot(HaveOccurred())
    99  					Expect(testUI.Out).To(Say("API endpoint: api.boshlite.com\n\n"))
   100  					Expect(fakeActor.SetTargetCallCount()).To(Equal(1))
   101  					actualSettings := fakeActor.SetTargetArgsForCall(0)
   102  					Expect(actualSettings.URL).To(Equal("https://api.boshlite.com"))
   103  				})
   104  			})
   105  
   106  			When("user does not provide the api endpoint using the -a flag", func() {
   107  				When("config has API endpoint already set", func() {
   108  					BeforeEach(func() {
   109  						fakeConfig.TargetReturns("api.fake.com")
   110  					})
   111  
   112  					It("does not prompt the user for an API endpoint", func() {
   113  						Expect(executeErr).ToNot(HaveOccurred())
   114  						Expect(testUI.Out).To(Say(`API endpoint:\s+api\.fake\.com \(API version: 3\.4\.5\)`))
   115  						Expect(fakeActor.SetTargetCallCount()).To(Equal(1))
   116  					})
   117  				})
   118  
   119  				When("the user enters something at the prompt", func() {
   120  					BeforeEach(func() {
   121  						input.Write([]byte("api.boshlite.com\n"))
   122  						cmd.APIEndpoint = ""
   123  					})
   124  
   125  					It("targets the API that the user inputted", func() {
   126  						Expect(executeErr).ToNot(HaveOccurred())
   127  						Expect(testUI.Out).To(Say("API endpoint:"))
   128  						Expect(testUI.Out).To(Say("api.boshlite.com\n"))
   129  						Expect(testUI.Out).To(Say(`API endpoint:\s+api\.boshlite\.com \(API version: 3\.4\.5\)`))
   130  
   131  						Expect(fakeActor.SetTargetCallCount()).To(Equal(1))
   132  						actualSettings := fakeActor.SetTargetArgsForCall(0)
   133  						Expect(actualSettings.URL).To(Equal("https://api.boshlite.com"))
   134  					})
   135  				})
   136  
   137  				When("the user inputs an empty API", func() {
   138  					BeforeEach(func() {
   139  						cmd.APIEndpoint = ""
   140  						input.Write([]byte("\n\napi.boshlite.com\n"))
   141  					})
   142  
   143  					It("reprompts for the API", func() {
   144  						Expect(executeErr).ToNot(HaveOccurred())
   145  						Expect(testUI.Out).To(Say("API endpoint:"))
   146  						Expect(testUI.Out).To(Say("API endpoint:"))
   147  						Expect(testUI.Out).To(Say("API endpoint:"))
   148  						Expect(testUI.Out).To(Say("api.boshlite.com\n"))
   149  						Expect(testUI.Out).To(Say(`API endpoint:\s+api\.boshlite\.com \(API version: 3\.4\.5\)`))
   150  					})
   151  				})
   152  			})
   153  
   154  			When("the endpoint has trailing slashes", func() {
   155  				BeforeEach(func() {
   156  					cmd.APIEndpoint = "api.boshlite.com////"
   157  				})
   158  
   159  				It("strips the backslashes before using the endpoint", func() {
   160  					Expect(executeErr).ToNot(HaveOccurred())
   161  					Expect(fakeActor.SetTargetCallCount()).To(Equal(1))
   162  					actualSettings := fakeActor.SetTargetArgsForCall(0)
   163  					Expect(actualSettings.URL).To(Equal("https://api.boshlite.com"))
   164  
   165  					Expect(testUI.Out).To(Say(`API endpoint:\s+api\.boshlite\.com \(API version: 3\.4\.5\)`))
   166  				})
   167  			})
   168  		})
   169  
   170  		Describe("username and password", func() {
   171  			BeforeEach(func() {
   172  				fakeConfig.TargetReturns("https://some.random.endpoint")
   173  			})
   174  
   175  			When("the current grant type is client credentials", func() {
   176  				BeforeEach(func() {
   177  					fakeConfig.UAAGrantTypeReturns(string(constant.GrantTypeClientCredentials))
   178  				})
   179  
   180  				It("returns an error", func() {
   181  					Expect(executeErr).To(MatchError("Service account currently logged in. Use 'cf logout' to log out service account and try again."))
   182  				})
   183  			})
   184  
   185  			When("the current grant type is password", func() {
   186  				BeforeEach(func() {
   187  					fakeConfig.UAAGrantTypeReturns(string(constant.GrantTypePassword))
   188  				})
   189  
   190  				It("fetches prompts from the UAA", func() {
   191  					Expect(executeErr).ToNot(HaveOccurred())
   192  					Expect(fakeActor.GetLoginPromptsCallCount()).To(Equal(1))
   193  				})
   194  
   195  				When("fetching prompts succeeds", func() {
   196  					When("one of the prompts has a username key and is text type", func() {
   197  						BeforeEach(func() {
   198  							fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   199  								"username": {
   200  									DisplayName: "Username",
   201  									Type:        coreconfig.AuthPromptTypeText,
   202  								},
   203  							})
   204  						})
   205  
   206  						When("the username flag is set", func() {
   207  							BeforeEach(func() {
   208  								cmd.Username = "potatoface"
   209  							})
   210  
   211  							It("uses the provided value and does not prompt for the username", func() {
   212  								Expect(executeErr).ToNot(HaveOccurred())
   213  								Expect(testUI.Out).NotTo(Say("Username:"))
   214  								Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   215  								credentials, _, _ := fakeActor.AuthenticateArgsForCall(0)
   216  								Expect(credentials["username"]).To(Equal("potatoface"))
   217  							})
   218  						})
   219  					})
   220  
   221  					When("one of the prompts has password key and is password type", func() {
   222  						BeforeEach(func() {
   223  							fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   224  								"password": {
   225  									DisplayName: "Your Password",
   226  									Type:        coreconfig.AuthPromptTypePassword,
   227  								},
   228  							})
   229  						})
   230  
   231  						When("the password flag is set", func() {
   232  							BeforeEach(func() {
   233  								cmd.Password = "noprompto"
   234  							})
   235  
   236  							It("uses the provided value and does not prompt for the password", func() {
   237  								Expect(executeErr).ToNot(HaveOccurred())
   238  								Expect(testUI.Out).NotTo(Say("Your Password:"))
   239  								Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   240  								credentials, _, _ := fakeActor.AuthenticateArgsForCall(0)
   241  								Expect(credentials["password"]).To(Equal("noprompto"))
   242  							})
   243  
   244  							When("the password is incorrect", func() {
   245  								BeforeEach(func() {
   246  									input.Write([]byte("other-password\n"))
   247  									fakeActor.AuthenticateReturns(errors.New("bad creds"))
   248  								})
   249  
   250  								It("does not reuse the flag value for subsequent attempts", func() {
   251  									credentials, _, _ := fakeActor.AuthenticateArgsForCall(1)
   252  									Expect(credentials["password"]).To(Equal("other-password"))
   253  								})
   254  							})
   255  
   256  							When("there have been too many failed login attempts", func() {
   257  								BeforeEach(func() {
   258  									input.Write([]byte("other-password\n"))
   259  									fakeActor.AuthenticateReturns(
   260  										uaa.AccountLockedError{
   261  											Message: "Your account has been locked because of too many failed attempts to login.",
   262  										},
   263  									)
   264  								})
   265  
   266  								It("does not reuse the flag value for subsequent attempts", func() {
   267  									Expect(fakeActor.AuthenticateCallCount()).To(Equal(1), "called Authenticate again after lockout")
   268  									Expect(testUI.Err).To(Say("Your account has been locked because of too many failed attempts to login."))
   269  								})
   270  							})
   271  						})
   272  					})
   273  
   274  					When("UAA prompts for the SSO passcode during non-SSO flow", func() {
   275  						BeforeEach(func() {
   276  							cmd.SSO = false
   277  							cmd.Password = "some-password"
   278  							fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   279  								"password": {
   280  									DisplayName: "Your Password",
   281  									Type:        coreconfig.AuthPromptTypePassword,
   282  								},
   283  								"passcode": {
   284  									DisplayName: "gimme your passcode",
   285  									Type:        coreconfig.AuthPromptTypePassword,
   286  								},
   287  							})
   288  						})
   289  
   290  						It("does not prompt for the passcode", func() {
   291  							Expect(executeErr).ToNot(HaveOccurred())
   292  							Expect(testUI.Out).NotTo(Say("gimme your passcode"))
   293  						})
   294  
   295  						It("does not send the passcode", func() {
   296  							Expect(executeErr).ToNot(HaveOccurred())
   297  							credentials, _, _ := fakeActor.AuthenticateArgsForCall(0)
   298  							Expect(credentials).To(HaveKeyWithValue("password", "some-password"))
   299  							Expect(credentials).NotTo(HaveKey("passcode"))
   300  						})
   301  					})
   302  
   303  					When("multiple prompts of text and password type are returned", func() {
   304  						BeforeEach(func() {
   305  							fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   306  								"account_number": {
   307  									DisplayName: "Account Number",
   308  									Type:        coreconfig.AuthPromptTypeText,
   309  								},
   310  								"username": {
   311  									DisplayName: "Username",
   312  									Type:        coreconfig.AuthPromptTypeText,
   313  								},
   314  								"passcode": {
   315  									DisplayName: "It's a passcode, what you want it to be???",
   316  									Type:        coreconfig.AuthPromptTypePassword,
   317  								},
   318  								"password": {
   319  									DisplayName: "Your Password",
   320  									Type:        coreconfig.AuthPromptTypePassword,
   321  								},
   322  								"supersecret": {
   323  									DisplayName: "MFA Code",
   324  									Type:        coreconfig.AuthPromptTypePassword,
   325  								},
   326  							})
   327  						})
   328  
   329  						When("no authentication flags are set", func() {
   330  							BeforeEach(func() {
   331  								input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\n"))
   332  							})
   333  
   334  							It("displays text prompts, starting with username, then password prompts, starting with password", func() {
   335  								Expect(executeErr).ToNot(HaveOccurred())
   336  
   337  								Expect(testUI.Out).To(Say("\n\n"))
   338  								Expect(testUI.Out).To(Say("Username:"))
   339  								Expect(testUI.Out).To(Say("faker"))
   340  
   341  								Expect(testUI.Out).To(Say("\n\n"))
   342  								Expect(testUI.Out).To(Say("Account Number:"))
   343  								Expect(testUI.Out).To(Say("someaccount"))
   344  
   345  								Expect(testUI.Out).To(Say("\n\n"))
   346  								Expect(testUI.Out).To(Say("Your Password:"))
   347  								Expect(testUI.Out).NotTo(Say("somepassword"))
   348  
   349  								Expect(testUI.Out).To(Say("\n\n"))
   350  								Expect(testUI.Out).To(Say("MFA Code:"))
   351  								Expect(testUI.Out).NotTo(Say("garbage"))
   352  							})
   353  
   354  							It("authenticates with the responses", func() {
   355  								Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   356  								credentials, _, grantType := fakeActor.AuthenticateArgsForCall(0)
   357  								Expect(credentials["username"]).To(Equal("faker"))
   358  								Expect(credentials["password"]).To(Equal("somepassword"))
   359  								Expect(credentials["supersecret"]).To(Equal("garbage"))
   360  								Expect(grantType).To(Equal(constant.GrantTypePassword))
   361  							})
   362  						})
   363  
   364  						When("an error occurs prompting for the username", func() {
   365  							var fakeUI *commandfakes.FakeUI
   366  
   367  							BeforeEach(func() {
   368  								fakeUI = new(commandfakes.FakeUI)
   369  								fakeUI.DisplayTextPromptReturns("", errors.New("some-error"))
   370  								cmd = LoginCommand{
   371  									UI:           fakeUI,
   372  									Actor:        fakeActor,
   373  									ActorMaker:   fakeActorMaker,
   374  									Config:       fakeConfig,
   375  									CheckerMaker: fakeCheckerMaker,
   376  								}
   377  							})
   378  
   379  							It("stops prompting after the first prompt", func() {
   380  								Expect(fakeUI.DisplayTextPromptCallCount()).To(Equal(1))
   381  							})
   382  
   383  							It("errors", func() {
   384  								Expect(executeErr).To(MatchError("Unable to authenticate."))
   385  							})
   386  						})
   387  
   388  						When("an error occurs in an additional text prompt after username", func() {
   389  							var fakeUI *commandfakes.FakeUI
   390  
   391  							BeforeEach(func() {
   392  								fakeUI = new(commandfakes.FakeUI)
   393  								fakeUI.DisplayTextPromptReturnsOnCall(0, "some-name", nil)
   394  								fakeUI.DisplayTextPromptReturnsOnCall(1, "", errors.New("some-error"))
   395  								cmd = LoginCommand{
   396  									UI:           fakeUI,
   397  									Actor:        fakeActor,
   398  									ActorMaker:   fakeActorMaker,
   399  									Config:       fakeConfig,
   400  									CheckerMaker: fakeCheckerMaker,
   401  								}
   402  							})
   403  
   404  							It("returns the error", func() {
   405  								Expect(executeErr).To(MatchError("Unable to authenticate."))
   406  							})
   407  						})
   408  
   409  						When("an error occurs prompting for the password", func() {
   410  							var fakeUI *commandfakes.FakeUI
   411  
   412  							BeforeEach(func() {
   413  								fakeUI = new(commandfakes.FakeUI)
   414  								fakeUI.DisplayPasswordPromptReturns("", errors.New("some-error"))
   415  								cmd = LoginCommand{
   416  									UI:           fakeUI,
   417  									Actor:        fakeActor,
   418  									ActorMaker:   fakeActorMaker,
   419  									Config:       fakeConfig,
   420  									CheckerMaker: fakeCheckerMaker,
   421  								}
   422  							})
   423  
   424  							It("stops prompting after the first prompt", func() {
   425  								Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(1))
   426  							})
   427  
   428  							It("errors", func() {
   429  								Expect(executeErr).To(MatchError("Unable to authenticate."))
   430  							})
   431  						})
   432  
   433  						When("an error occurs prompting for prompts of type password that are not the 'password'", func() {
   434  							var fakeUI *commandfakes.FakeUI
   435  
   436  							BeforeEach(func() {
   437  								fakeUI = new(commandfakes.FakeUI)
   438  								fakeUI.DisplayPasswordPromptReturnsOnCall(0, "some-password", nil)
   439  								fakeUI.DisplayPasswordPromptReturnsOnCall(1, "", errors.New("some-error"))
   440  
   441  								cmd = LoginCommand{
   442  									UI:           fakeUI,
   443  									Actor:        fakeActor,
   444  									ActorMaker:   fakeActorMaker,
   445  									Config:       fakeConfig,
   446  									CheckerMaker: fakeCheckerMaker,
   447  								}
   448  							})
   449  
   450  							It("stops prompting after the second prompt", func() {
   451  								Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(2))
   452  							})
   453  
   454  							It("errors", func() {
   455  								Expect(executeErr).To(MatchError("Unable to authenticate."))
   456  							})
   457  						})
   458  
   459  						When("authenticating succeeds", func() {
   460  							BeforeEach(func() {
   461  								fakeConfig.CurrentUserNameReturns("potatoface", nil)
   462  								input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\n"))
   463  							})
   464  
   465  							It("displays OK and a status summary", func() {
   466  								Expect(executeErr).ToNot(HaveOccurred())
   467  								Expect(testUI.Out).To(Say("OK"))
   468  								Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   469  								Expect(testUI.Out).To(Say(`User:\s+potatoface`))
   470  
   471  								Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   472  							})
   473  						})
   474  
   475  						When("authenticating fails", func() {
   476  							BeforeEach(func() {
   477  								fakeActor.AuthenticateReturns(errors.New("something died"))
   478  								input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\n"))
   479  							})
   480  
   481  							It("prints the error message three times", func() {
   482  								Expect(testUI.Out).To(Say("Your Password:"))
   483  								Expect(testUI.Out).To(Say("MFA Code:"))
   484  								Expect(testUI.Err).To(Say("something died"))
   485  								Expect(testUI.Out).To(Say("Your Password:"))
   486  								Expect(testUI.Out).To(Say("MFA Code:"))
   487  								Expect(testUI.Err).To(Say("something died"))
   488  								Expect(testUI.Out).To(Say("Your Password:"))
   489  								Expect(testUI.Out).To(Say("MFA Code:"))
   490  								Expect(testUI.Err).To(Say("something died"))
   491  							})
   492  
   493  							It("returns an error indicating that it could not authenticate", func() {
   494  								Expect(executeErr).To(MatchError("Unable to authenticate."))
   495  							})
   496  
   497  							It("displays a status summary", func() {
   498  								Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   499  								Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' to log in.`, cmd.Config.BinaryName()))
   500  							})
   501  
   502  						})
   503  
   504  						When("authenticating fails with a bad credentials error", func() {
   505  							BeforeEach(func() {
   506  								fakeActor.AuthenticateReturns(uaa.UnauthorizedError{Message: "Bad credentials"})
   507  								input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\n"))
   508  							})
   509  
   510  							It("converts the error before printing it", func() {
   511  								Expect(testUI.Out).To(Say("Your Password:"))
   512  								Expect(testUI.Out).To(Say("MFA Code:"))
   513  								Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   514  								Expect(testUI.Out).To(Say("Your Password:"))
   515  								Expect(testUI.Out).To(Say("MFA Code:"))
   516  								Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   517  								Expect(testUI.Out).To(Say("Your Password:"))
   518  								Expect(testUI.Out).To(Say("MFA Code:"))
   519  								Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   520  							})
   521  						})
   522  					})
   523  
   524  					When("custom client ID and client secret are set in the config file", func() {
   525  						BeforeEach(func() {
   526  							fakeConfig.UAAOAuthClientReturns("some-other-client-id")
   527  							fakeConfig.UAAOAuthClientSecretReturns("some-secret")
   528  						})
   529  
   530  						It("prints a deprecation warning", func() {
   531  							deprecationMessage := "Deprecation warning: Manually writing your client credentials to the config.json is deprecated and will be removed in the future. For similar functionality, please use the `cf auth --client-credentials` command instead."
   532  							Expect(testUI.Err).To(Say(deprecationMessage))
   533  						})
   534  
   535  						It("still attempts to log in", func() {
   536  							Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   537  						})
   538  					})
   539  				})
   540  			})
   541  		})
   542  
   543  		Describe("SSO Passcode", func() {
   544  			BeforeEach(func() {
   545  				fakeConfig.TargetReturns("whatever.com")
   546  
   547  				input.Write([]byte("some-passcode\n"))
   548  				fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   549  					"passcode": {
   550  						DisplayName: "some-sso-prompt",
   551  						Type:        coreconfig.AuthPromptTypePassword,
   552  					},
   553  				})
   554  
   555  				fakeConfig.CurrentUserNameReturns("potatoface", nil)
   556  			})
   557  
   558  			When("--sso flag is set", func() {
   559  				BeforeEach(func() {
   560  					cmd.SSO = true
   561  				})
   562  
   563  				It("prompts the user for SSO passcode", func() {
   564  					Expect(executeErr).NotTo(HaveOccurred())
   565  					Expect(fakeActor.GetLoginPromptsCallCount()).To(Equal(1))
   566  					Expect(testUI.Out).To(Say("some-sso-prompt:"))
   567  				})
   568  
   569  				It("authenticates with the inputted code", func() {
   570  					Expect(testUI.Out).To(Say("OK"))
   571  					Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   572  					Expect(testUI.Out).To(Say(`User:\s+potatoface`))
   573  
   574  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   575  					credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
   576  					Expect(credentials["passcode"]).To(Equal("some-passcode"))
   577  					Expect(origin).To(BeEmpty())
   578  					Expect(grantType).To(Equal(constant.GrantTypePassword))
   579  				})
   580  
   581  				When("an error occurs prompting for the code", func() {
   582  					var fakeUI *commandfakes.FakeUI
   583  
   584  					BeforeEach(func() {
   585  						fakeUI = new(commandfakes.FakeUI)
   586  						fakeUI.DisplayPasswordPromptReturns("", errors.New("some-error"))
   587  						cmd = LoginCommand{
   588  							UI:           fakeUI,
   589  							Actor:        fakeActor,
   590  							ActorMaker:   fakeActorMaker,
   591  							Config:       fakeConfig,
   592  							CheckerMaker: fakeCheckerMaker,
   593  							SSO:          true,
   594  						}
   595  					})
   596  
   597  					It("errors", func() {
   598  						Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(1))
   599  						Expect(executeErr).To(MatchError("Unable to authenticate."))
   600  					})
   601  				})
   602  			})
   603  
   604  			When("the --sso-passcode flag is set", func() {
   605  				BeforeEach(func() {
   606  					cmd.SSOPasscode = "a-passcode"
   607  				})
   608  
   609  				It("does not prompt the user for SSO passcode", func() {
   610  					Expect(executeErr).NotTo(HaveOccurred())
   611  					Expect(testUI.Out).ToNot(Say("some-sso-prompt:"))
   612  				})
   613  
   614  				It("uses the flag value to authenticate", func() {
   615  					Expect(executeErr).NotTo(HaveOccurred())
   616  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   617  					credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
   618  					Expect(credentials["passcode"]).To(Equal("a-passcode"))
   619  					Expect(origin).To(BeEmpty())
   620  					Expect(grantType).To(Equal(constant.GrantTypePassword))
   621  				})
   622  
   623  				It("displays a summary with user information", func() {
   624  					Expect(executeErr).NotTo(HaveOccurred())
   625  					Expect(testUI.Out).To(Say("OK"))
   626  					Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   627  					Expect(testUI.Out).To(Say(`User:\s+potatoface`))
   628  				})
   629  
   630  				When("an incorrect passcode is inputted", func() {
   631  					BeforeEach(func() {
   632  						cmd.SSOPasscode = "some-garbage"
   633  						fakeActor.AuthenticateReturns(uaa.UnauthorizedError{
   634  							Message: "Bad credentials",
   635  						})
   636  						fakeConfig.CurrentUserNameReturns("", nil)
   637  						input.Write([]byte("some-passcode\n"))
   638  					})
   639  
   640  					It("re-prompts two more times", func() {
   641  						Expect(testUI.Out).To(Say("some-sso-prompt:"))
   642  						Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
   643  						Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   644  						Expect(testUI.Out).To(Say("some-sso-prompt:"))
   645  						Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
   646  						Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   647  					})
   648  
   649  					It("returns an error message", func() {
   650  						Expect(executeErr).To(MatchError("Unable to authenticate."))
   651  					})
   652  
   653  					It("does not include user information in the summary", func() {
   654  						Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   655  						Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' to log in.`, cmd.Config.BinaryName()))
   656  					})
   657  				})
   658  			})
   659  
   660  			When("both --sso and --sso-passcode flags are set", func() {
   661  				BeforeEach(func() {
   662  					cmd.SSO = true
   663  					cmd.SSOPasscode = "a-passcode"
   664  				})
   665  
   666  				It("returns an error message", func() {
   667  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(0))
   668  					Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{Args: []string{"--sso-passcode", "--sso"}}))
   669  				})
   670  			})
   671  		})
   672  
   673  		Describe("Minimum CLI version ", func() {
   674  			BeforeEach(func() {
   675  				fakeConfig.TargetReturns("whatever.com")
   676  
   677  				fakeChecker.MinCLIVersionReturns("9000.0.0")
   678  			})
   679  
   680  			It("sets the minimum CLI version in the config", func() {
   681  				Expect(executeErr).NotTo(HaveOccurred())
   682  				Expect(fakeConfig.SetMinCLIVersionCallCount()).To(Equal(1))
   683  				Expect(fakeConfig.SetMinCLIVersionArgsForCall(0)).To(Equal("9000.0.0"))
   684  			})
   685  
   686  			When("The current version is below the minimum supported", func() {
   687  				BeforeEach(func() {
   688  					fakeChecker.CloudControllerAPIVersionReturns("2.123.0")
   689  					fakeConfig.BinaryVersionReturns("1.2.3")
   690  					fakeConfig.MinCLIVersionReturns("9000.0.0")
   691  				})
   692  
   693  				It("displays a warning", func() {
   694  					Expect(executeErr).NotTo(HaveOccurred())
   695  					Expect(testUI.Err).To(Say("Cloud Foundry API version 2.123.0 requires CLI version 9000.0.0. You are currently on version 1.2.3. To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads"))
   696  				})
   697  
   698  				Context("ordering of output", func() {
   699  					BeforeEach(func() {
   700  						outAndErr := NewBuffer()
   701  						testUI.Out = outAndErr
   702  						testUI.Err = outAndErr
   703  					})
   704  
   705  					It("displays the warning after all prompts but before the summary ", func() {
   706  						Expect(executeErr).NotTo(HaveOccurred())
   707  						Expect(testUI.Out).To(Say(`Authenticating...`))
   708  						Expect(testUI.Err).To(Say("Cloud Foundry API version 2.123.0 requires CLI version 9000.0.0. You are currently on version 1.2.3. To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads"))
   709  						Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   710  						Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' to log in.`, binaryName))
   711  					})
   712  				})
   713  			})
   714  		})
   715  
   716  		Describe("Targeting Org and Space", func() {
   717  			BeforeEach(func() {
   718  				cmd.APIEndpoint = "example.com"
   719  				cmd.Username = "some-user"
   720  				cmd.Password = "some-password"
   721  				fakeConfig.APIVersionReturns("3.4.5")
   722  				fakeConfig.CurrentUserNameReturns("some-user", nil)
   723  			})
   724  
   725  			When("-o was passed", func() {
   726  				BeforeEach(func() {
   727  					cmd.Organization = "some-org"
   728  				})
   729  
   730  				It("fetches the specified organization", func() {
   731  					Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1))
   732  					Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(0))
   733  					Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal("some-org"))
   734  				})
   735  
   736  				When("fetching the organization succeeds", func() {
   737  					BeforeEach(func() {
   738  						fakeActor.GetOrganizationByNameReturns(
   739  							v3action.Organization{Name: "some-org", GUID: "some-guid"},
   740  							v3action.Warnings{"some-warning-1", "some-warning-2"},
   741  							nil)
   742  						fakeConfig.TargetedOrganizationNameReturns("some-org")
   743  					})
   744  
   745  					It("prints all warnings", func() {
   746  						Expect(testUI.Err).To(Say("some-warning-1"))
   747  						Expect(testUI.Err).To(Say("some-warning-2"))
   748  					})
   749  
   750  					It("sets the targeted organization in the config", func() {
   751  						Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   752  						orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   753  						Expect(orgGUID).To(Equal("some-guid"))
   754  						Expect(orgName).To(Equal("some-org"))
   755  					})
   756  
   757  					It("reports to the user that the org is targeted", func() {
   758  						Expect(testUI.Out).To(Say("API endpoint:   example.com \\(API version: 3.4.5\\)"))
   759  						Expect(testUI.Out).To(Say("User:           some-user"))
   760  						Expect(testUI.Out).To(Say("Org:            some-org"))
   761  					})
   762  				})
   763  
   764  				When("fetching  the organization fails", func() {
   765  					BeforeEach(func() {
   766  						fakeActor.GetOrganizationByNameReturns(
   767  							v3action.Organization{},
   768  							v3action.Warnings{"some-warning-1", "some-warning-2"},
   769  							errors.New("org-not-found"),
   770  						)
   771  					})
   772  
   773  					It("prints all warnings", func() {
   774  						Expect(testUI.Err).To(Say("some-warning-1"))
   775  						Expect(testUI.Err).To(Say("some-warning-2"))
   776  					})
   777  
   778  					It("does not set the targeted org", func() {
   779  						Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   780  					})
   781  				})
   782  			})
   783  
   784  			When("-o was not passed", func() {
   785  				BeforeEach(func() {
   786  					cmd.APIEndpoint = "example.com"
   787  					cmd.Username = "some-user"
   788  					cmd.Password = "some-password"
   789  					fakeActor.GetOrganizationsReturns(
   790  						[]v3action.Organization{},
   791  						v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   792  						nil,
   793  					)
   794  				})
   795  
   796  				It("fetches the available organizations", func() {
   797  					Expect(executeErr).ToNot(HaveOccurred())
   798  					Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(1))
   799  				})
   800  
   801  				It("prints all warnings", func() {
   802  					Expect(testUI.Err).To(Say("some-org-warning-1"))
   803  					Expect(testUI.Err).To(Say("some-org-warning-2"))
   804  				})
   805  
   806  				When("fetching the organizations succeeds", func() {
   807  					BeforeEach(func() {
   808  						fakeConfig.CurrentUserNameReturns("some-user", nil)
   809  					})
   810  
   811  					When("no org exists", func() {
   812  						It("displays how to target an org and space", func() {
   813  							Expect(executeErr).ToNot(HaveOccurred())
   814  
   815  							Expect(testUI.Out).To(Say("API endpoint:   example.com \\(API version: 3.4.5\\)"))
   816  							Expect(testUI.Out).To(Say("User:           some-user"))
   817  							Expect(testUI.Out).To(Say("No org or space targeted, use '%s target -o ORG -s SPACE'", binaryName))
   818  						})
   819  					})
   820  
   821  					When("only one org exists", func() {
   822  						BeforeEach(func() {
   823  							fakeActor.GetOrganizationsReturns(
   824  								[]v3action.Organization{v3action.Organization{
   825  									GUID: "some-org-guid",
   826  									Name: "some-org-name",
   827  								}},
   828  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   829  								nil,
   830  							)
   831  						})
   832  
   833  						It("targets that org", func() {
   834  							Expect(executeErr).ToNot(HaveOccurred())
   835  							Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   836  							orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   837  							Expect(orgGUID).To(Equal("some-org-guid"))
   838  							Expect(orgName).To(Equal("some-org-name"))
   839  						})
   840  					})
   841  
   842  					When("more than one but fewer than 50 orgs exists", func() {
   843  						BeforeEach(func() {
   844  							fakeActor.GetOrganizationsReturns(
   845  								[]v3action.Organization{
   846  									v3action.Organization{
   847  										GUID: "some-org-guid3",
   848  										Name: "1234",
   849  									},
   850  									v3action.Organization{
   851  										GUID: "some-org-guid1",
   852  										Name: "some-org-name1",
   853  									},
   854  									v3action.Organization{
   855  										GUID: "some-org-guid2",
   856  										Name: "some-org-name2",
   857  									},
   858  								},
   859  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   860  								nil,
   861  							)
   862  						})
   863  
   864  						When("the user selects an org by list position", func() {
   865  							When("the position is valid", func() {
   866  								BeforeEach(func() {
   867  									input.Write([]byte("2\n"))
   868  								})
   869  
   870  								It("prompts the user to select an org", func() {
   871  									Expect(testUI.Out).To(Say("Select an org:"))
   872  									Expect(testUI.Out).To(Say("1. 1234"))
   873  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   874  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   875  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   876  									Expect(executeErr).ToNot(HaveOccurred())
   877  								})
   878  
   879  								It("targets that org", func() {
   880  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   881  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   882  									Expect(orgGUID).To(Equal("some-org-guid1"))
   883  									Expect(orgName).To(Equal("some-org-name1"))
   884  								})
   885  							})
   886  
   887  							When("the position is invalid", func() {
   888  								BeforeEach(func() {
   889  									input.Write([]byte("4\n"))
   890  								})
   891  
   892  								It("reprompts the user", func() {
   893  									Expect(testUI.Out).To(Say("Select an org:"))
   894  									Expect(testUI.Out).To(Say("1. 1234"))
   895  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   896  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   897  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   898  									Expect(testUI.Out).To(Say("Select an org:"))
   899  									Expect(testUI.Out).To(Say("1. 1234"))
   900  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   901  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   902  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   903  								})
   904  							})
   905  						})
   906  
   907  						When("the user selects an org by name", func() {
   908  							When("the list contains that org", func() {
   909  								BeforeEach(func() {
   910  									input.Write([]byte("some-org-name2\n"))
   911  								})
   912  
   913  								It("prompts the user to select an org", func() {
   914  									Expect(testUI.Out).To(Say("Select an org:"))
   915  									Expect(testUI.Out).To(Say("1. 1234"))
   916  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   917  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   918  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   919  									Expect(executeErr).ToNot(HaveOccurred())
   920  								})
   921  
   922  								It("targets that org", func() {
   923  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   924  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   925  									Expect(orgGUID).To(Equal("some-org-guid2"))
   926  									Expect(orgName).To(Equal("some-org-name2"))
   927  								})
   928  							})
   929  
   930  							When("the org is not in the list", func() {
   931  								BeforeEach(func() {
   932  									input.Write([]byte("invalid-org\n"))
   933  								})
   934  
   935  								It("returns an error", func() {
   936  									Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"}))
   937  								})
   938  
   939  								It("does not target the org", func() {
   940  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   941  								})
   942  							})
   943  						})
   944  
   945  						When("the user exits the prompt early", func() {
   946  							var fakeUI *commandfakes.FakeUI
   947  
   948  							BeforeEach(func() {
   949  								fakeUI = new(commandfakes.FakeUI)
   950  								cmd.UI = fakeUI
   951  							})
   952  
   953  							When("the prompt returns with an EOF", func() {
   954  								BeforeEach(func() {
   955  									fakeUI.DisplayTextMenuReturns("", io.EOF)
   956  								})
   957  
   958  								It("selects no org and returns no error", func() {
   959  									Expect(executeErr).ToNot(HaveOccurred())
   960  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   961  								})
   962  							})
   963  						})
   964  
   965  					})
   966  
   967  					When("more than 50 orgs exist", func() {
   968  						BeforeEach(func() {
   969  							orgs := make([]v3action.Organization, 51)
   970  							for i := range orgs {
   971  								orgs[i].Name = fmt.Sprintf("org%d", i+1)
   972  								orgs[i].GUID = fmt.Sprintf("org-guid%d", i+1)
   973  							}
   974  
   975  							fakeActor.GetOrganizationsReturns(
   976  								orgs,
   977  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   978  								nil,
   979  							)
   980  						})
   981  
   982  						When("the user selects an org by name", func() {
   983  							When("the list contains that org", func() {
   984  								BeforeEach(func() {
   985  									input.Write([]byte("org37\n"))
   986  								})
   987  
   988  								It("prompts the user to select an org", func() {
   989  									Expect(testUI.Out).To(Say("There are too many options to display; please type in the name."))
   990  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   991  									Expect(executeErr).ToNot(HaveOccurred())
   992  								})
   993  
   994  								It("targets that org", func() {
   995  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   996  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   997  									Expect(orgGUID).To(Equal("org-guid37"))
   998  									Expect(orgName).To(Equal("org37"))
   999  								})
  1000  							})
  1001  
  1002  							When("the org is not in the list", func() {
  1003  								BeforeEach(func() {
  1004  									input.Write([]byte("invalid-org\n"))
  1005  								})
  1006  
  1007  								It("returns an error", func() {
  1008  									Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"}))
  1009  								})
  1010  
  1011  								It("does not target the org", func() {
  1012  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
  1013  								})
  1014  							})
  1015  						})
  1016  
  1017  					})
  1018  				})
  1019  
  1020  				When("fetching the organizations fails", func() {
  1021  					BeforeEach(func() {
  1022  						fakeActor.GetOrganizationsReturns(
  1023  							[]v3action.Organization{},
  1024  							v3action.Warnings{"some-warning-1", "some-warning-2"},
  1025  							errors.New("api call failed"),
  1026  						)
  1027  					})
  1028  
  1029  					It("returns the error", func() {
  1030  						Expect(executeErr).To(MatchError("api call failed"))
  1031  					})
  1032  
  1033  					It("prints all warnings", func() {
  1034  						Expect(testUI.Err).To(Say("some-warning-1"))
  1035  						Expect(testUI.Err).To(Say("some-warning-2"))
  1036  					})
  1037  				})
  1038  			})
  1039  
  1040  			When("an org has been successfully targeted", func() {
  1041  				BeforeEach(func() {
  1042  					fakeConfig.TargetedOrganizationReturns(configv3.Organization{
  1043  						GUID: "targeted-org-guid",
  1044  						Name: "targeted-org-name"},
  1045  					)
  1046  					fakeConfig.TargetedOrganizationNameReturns("targeted-org-name")
  1047  				})
  1048  
  1049  				When("-s was passed", func() {
  1050  					BeforeEach(func() {
  1051  						cmd.Space = "some-space"
  1052  					})
  1053  
  1054  					When("the specified space exists", func() {
  1055  						BeforeEach(func() {
  1056  							fakeActor.GetSpaceByNameAndOrganizationReturns(
  1057  								v3action.Space{
  1058  									Name: "some-space",
  1059  									GUID: "some-space-guid",
  1060  								},
  1061  								v3action.Warnings{"some-warning-1", "some-warning-2"},
  1062  								nil,
  1063  							)
  1064  						})
  1065  
  1066  						It("targets that space", func() {
  1067  							Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1))
  1068  							spaceGUID, spaceName, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0)
  1069  							Expect(spaceGUID).To(Equal("some-space-guid"))
  1070  							Expect(spaceName).To(Equal("some-space"))
  1071  							Expect(allowSSH).To(BeTrue())
  1072  						})
  1073  
  1074  						It("prints all warnings", func() {
  1075  							Expect(testUI.Err).To(Say("some-warning-1"))
  1076  							Expect(testUI.Err).To(Say("some-warning-2"))
  1077  						})
  1078  
  1079  						When("the space has been successfully targeted", func() {
  1080  							BeforeEach(func() {
  1081  								fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space"})
  1082  							})
  1083  
  1084  							It("displays that the spacce has been targeted", func() {
  1085  								Expect(testUI.Out).To(Say(`Space:\s+some-space`))
  1086  							})
  1087  						})
  1088  					})
  1089  
  1090  					When("the specified space does not exist or does not belong to the targeted org", func() {
  1091  						BeforeEach(func() {
  1092  							fakeActor.GetSpaceByNameAndOrganizationReturns(
  1093  								v3action.Space{},
  1094  								v3action.Warnings{"some-warning-1", "some-warning-2"},
  1095  								actionerror.SpaceNotFoundError{Name: "some-space"},
  1096  							)
  1097  						})
  1098  
  1099  						It("returns an error", func() {
  1100  							Expect(executeErr).To(MatchError(actionerror.SpaceNotFoundError{Name: "some-space"}))
  1101  						})
  1102  
  1103  						It("prints all warnings", func() {
  1104  							Expect(testUI.Err).To(Say("some-warning-1"))
  1105  							Expect(testUI.Err).To(Say("some-warning-2"))
  1106  						})
  1107  
  1108  						It("reports that no space is targeted", func() {
  1109  							Expect(testUI.Out).To(Say(`Space:\s+No space targeted, use 'some-executable target -s SPACE'`))
  1110  						})
  1111  					})
  1112  				})
  1113  			})
  1114  		})
  1115  	})
  1116  })