github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+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  			})
   525  		})
   526  
   527  		Describe("SSO Passcode", func() {
   528  			BeforeEach(func() {
   529  				fakeConfig.TargetReturns("whatever.com")
   530  
   531  				input.Write([]byte("some-passcode\n"))
   532  				fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{
   533  					"passcode": {
   534  						DisplayName: "some-sso-prompt",
   535  						Type:        coreconfig.AuthPromptTypePassword,
   536  					},
   537  				})
   538  
   539  				fakeConfig.CurrentUserNameReturns("potatoface", nil)
   540  			})
   541  
   542  			When("--sso flag is set", func() {
   543  				BeforeEach(func() {
   544  					cmd.SSO = true
   545  				})
   546  
   547  				It("prompts the user for SSO passcode", func() {
   548  					Expect(executeErr).NotTo(HaveOccurred())
   549  					Expect(fakeActor.GetLoginPromptsCallCount()).To(Equal(1))
   550  					Expect(testUI.Out).To(Say("some-sso-prompt:"))
   551  				})
   552  
   553  				It("authenticates with the inputted code", func() {
   554  					Expect(testUI.Out).To(Say("OK"))
   555  					Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   556  					Expect(testUI.Out).To(Say(`User:\s+potatoface`))
   557  
   558  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   559  					credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
   560  					Expect(credentials["passcode"]).To(Equal("some-passcode"))
   561  					Expect(origin).To(BeEmpty())
   562  					Expect(grantType).To(Equal(constant.GrantTypePassword))
   563  				})
   564  
   565  				When("an error occurs prompting for the code", func() {
   566  					var fakeUI *commandfakes.FakeUI
   567  
   568  					BeforeEach(func() {
   569  						fakeUI = new(commandfakes.FakeUI)
   570  						fakeUI.DisplayPasswordPromptReturns("", errors.New("some-error"))
   571  						cmd = LoginCommand{
   572  							UI:           fakeUI,
   573  							Actor:        fakeActor,
   574  							ActorMaker:   fakeActorMaker,
   575  							Config:       fakeConfig,
   576  							CheckerMaker: fakeCheckerMaker,
   577  							SSO:          true,
   578  						}
   579  					})
   580  
   581  					It("errors", func() {
   582  						Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(1))
   583  						Expect(executeErr).To(MatchError("Unable to authenticate."))
   584  					})
   585  				})
   586  			})
   587  
   588  			When("the --sso-passcode flag is set", func() {
   589  				BeforeEach(func() {
   590  					cmd.SSOPasscode = "a-passcode"
   591  				})
   592  
   593  				It("does not prompt the user for SSO passcode", func() {
   594  					Expect(executeErr).NotTo(HaveOccurred())
   595  					Expect(testUI.Out).ToNot(Say("some-sso-prompt:"))
   596  				})
   597  
   598  				It("uses the flag value to authenticate", func() {
   599  					Expect(executeErr).NotTo(HaveOccurred())
   600  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(1))
   601  					credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0)
   602  					Expect(credentials["passcode"]).To(Equal("a-passcode"))
   603  					Expect(origin).To(BeEmpty())
   604  					Expect(grantType).To(Equal(constant.GrantTypePassword))
   605  				})
   606  
   607  				It("displays a summary with user information", func() {
   608  					Expect(executeErr).NotTo(HaveOccurred())
   609  					Expect(testUI.Out).To(Say("OK"))
   610  					Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   611  					Expect(testUI.Out).To(Say(`User:\s+potatoface`))
   612  				})
   613  
   614  				When("an incorrect passcode is inputted", func() {
   615  					BeforeEach(func() {
   616  						cmd.SSOPasscode = "some-garbage"
   617  						fakeActor.AuthenticateReturns(uaa.UnauthorizedError{
   618  							Message: "Bad credentials",
   619  						})
   620  						fakeConfig.CurrentUserNameReturns("", nil)
   621  						input.Write([]byte("some-passcode\n"))
   622  					})
   623  
   624  					It("re-prompts two more times", func() {
   625  						Expect(testUI.Out).To(Say("some-sso-prompt:"))
   626  						Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
   627  						Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   628  						Expect(testUI.Out).To(Say("some-sso-prompt:"))
   629  						Expect(testUI.Out).To(Say(`Authenticating\.\.\.`))
   630  						Expect(testUI.Err).To(Say("Credentials were rejected, please try again."))
   631  					})
   632  
   633  					It("returns an error message", func() {
   634  						Expect(executeErr).To(MatchError("Unable to authenticate."))
   635  					})
   636  
   637  					It("does not include user information in the summary", func() {
   638  						Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   639  						Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' to log in.`, cmd.Config.BinaryName()))
   640  					})
   641  				})
   642  			})
   643  
   644  			When("both --sso and --sso-passcode flags are set", func() {
   645  				BeforeEach(func() {
   646  					cmd.SSO = true
   647  					cmd.SSOPasscode = "a-passcode"
   648  				})
   649  
   650  				It("returns an error message", func() {
   651  					Expect(fakeActor.AuthenticateCallCount()).To(Equal(0))
   652  					Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{Args: []string{"--sso-passcode", "--sso"}}))
   653  				})
   654  			})
   655  		})
   656  
   657  		Describe("Minimum CLI version ", func() {
   658  			BeforeEach(func() {
   659  				fakeConfig.TargetReturns("whatever.com")
   660  
   661  				fakeChecker.MinCLIVersionReturns("9000.0.0")
   662  			})
   663  
   664  			It("sets the minimum CLI version in the config", func() {
   665  				Expect(executeErr).NotTo(HaveOccurred())
   666  				Expect(fakeConfig.SetMinCLIVersionCallCount()).To(Equal(1))
   667  				Expect(fakeConfig.SetMinCLIVersionArgsForCall(0)).To(Equal("9000.0.0"))
   668  			})
   669  
   670  			When("The current version is below the minimum supported", func() {
   671  				BeforeEach(func() {
   672  					fakeChecker.CloudControllerAPIVersionReturns("2.123.0")
   673  					fakeConfig.BinaryVersionReturns("1.2.3")
   674  					fakeConfig.MinCLIVersionReturns("9000.0.0")
   675  				})
   676  
   677  				It("displays a warning", func() {
   678  					Expect(executeErr).NotTo(HaveOccurred())
   679  					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"))
   680  				})
   681  
   682  				Context("ordering of output", func() {
   683  					BeforeEach(func() {
   684  						outAndErr := NewBuffer()
   685  						testUI.Out = outAndErr
   686  						testUI.Err = outAndErr
   687  					})
   688  
   689  					It("displays the warning after all prompts but before the summary ", func() {
   690  						Expect(executeErr).NotTo(HaveOccurred())
   691  						Expect(testUI.Out).To(Say(`Authenticating...`))
   692  						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"))
   693  						Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint))
   694  						Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' to log in.`, binaryName))
   695  					})
   696  				})
   697  			})
   698  		})
   699  
   700  		Describe("Targeting Org and Space", func() {
   701  			BeforeEach(func() {
   702  				cmd.APIEndpoint = "example.com"
   703  				cmd.Username = "some-user"
   704  				cmd.Password = "some-password"
   705  				fakeConfig.APIVersionReturns("3.4.5")
   706  				fakeConfig.CurrentUserNameReturns("some-user", nil)
   707  			})
   708  
   709  			When("-o was passed", func() {
   710  				BeforeEach(func() {
   711  					cmd.Organization = "some-org"
   712  				})
   713  
   714  				It("fetches the specified organization", func() {
   715  					Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1))
   716  					Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(0))
   717  					Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal("some-org"))
   718  				})
   719  
   720  				When("fetching the organization succeeds", func() {
   721  					BeforeEach(func() {
   722  						fakeActor.GetOrganizationByNameReturns(
   723  							v3action.Organization{Name: "some-org", GUID: "some-guid"},
   724  							v3action.Warnings{"some-warning-1", "some-warning-2"},
   725  							nil)
   726  						fakeConfig.TargetedOrganizationNameReturns("some-org")
   727  					})
   728  
   729  					It("prints all warnings", func() {
   730  						Expect(testUI.Err).To(Say("some-warning-1"))
   731  						Expect(testUI.Err).To(Say("some-warning-2"))
   732  					})
   733  
   734  					It("sets the targeted organization in the config", func() {
   735  						Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   736  						orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   737  						Expect(orgGUID).To(Equal("some-guid"))
   738  						Expect(orgName).To(Equal("some-org"))
   739  					})
   740  
   741  					It("reports to the user that the org is targeted", func() {
   742  						Expect(testUI.Out).To(Say("API endpoint:   example.com \\(API version: 3.4.5\\)"))
   743  						Expect(testUI.Out).To(Say("User:           some-user"))
   744  						Expect(testUI.Out).To(Say("Org:            some-org"))
   745  					})
   746  				})
   747  
   748  				When("fetching  the organization fails", func() {
   749  					BeforeEach(func() {
   750  						fakeActor.GetOrganizationByNameReturns(
   751  							v3action.Organization{},
   752  							v3action.Warnings{"some-warning-1", "some-warning-2"},
   753  							errors.New("org-not-found"),
   754  						)
   755  					})
   756  
   757  					It("prints all warnings", func() {
   758  						Expect(testUI.Err).To(Say("some-warning-1"))
   759  						Expect(testUI.Err).To(Say("some-warning-2"))
   760  					})
   761  
   762  					It("does not set the targeted org", func() {
   763  						Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   764  					})
   765  				})
   766  			})
   767  
   768  			When("-o was not passed", func() {
   769  				BeforeEach(func() {
   770  					cmd.APIEndpoint = "example.com"
   771  					cmd.Username = "some-user"
   772  					cmd.Password = "some-password"
   773  					fakeActor.GetOrganizationsReturns(
   774  						[]v3action.Organization{},
   775  						v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   776  						nil,
   777  					)
   778  				})
   779  
   780  				It("fetches the available organizations", func() {
   781  					Expect(executeErr).ToNot(HaveOccurred())
   782  					Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(1))
   783  				})
   784  
   785  				It("prints all warnings", func() {
   786  					Expect(testUI.Err).To(Say("some-org-warning-1"))
   787  					Expect(testUI.Err).To(Say("some-org-warning-2"))
   788  				})
   789  
   790  				When("fetching the organizations succeeds", func() {
   791  					BeforeEach(func() {
   792  						fakeConfig.CurrentUserNameReturns("some-user", nil)
   793  					})
   794  
   795  					When("no org exists", func() {
   796  						It("displays how to target an org and space", func() {
   797  							Expect(executeErr).ToNot(HaveOccurred())
   798  
   799  							Expect(testUI.Out).To(Say("API endpoint:   example.com \\(API version: 3.4.5\\)"))
   800  							Expect(testUI.Out).To(Say("User:           some-user"))
   801  							Expect(testUI.Out).To(Say("No org or space targeted, use '%s target -o ORG -s SPACE'", binaryName))
   802  						})
   803  					})
   804  
   805  					When("only one org exists", func() {
   806  						BeforeEach(func() {
   807  							fakeActor.GetOrganizationsReturns(
   808  								[]v3action.Organization{v3action.Organization{
   809  									GUID: "some-org-guid",
   810  									Name: "some-org-name",
   811  								}},
   812  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   813  								nil,
   814  							)
   815  						})
   816  
   817  						It("targets that org", func() {
   818  							Expect(executeErr).ToNot(HaveOccurred())
   819  							Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   820  							orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   821  							Expect(orgGUID).To(Equal("some-org-guid"))
   822  							Expect(orgName).To(Equal("some-org-name"))
   823  						})
   824  					})
   825  
   826  					When("more than one but fewer than 50 orgs exists", func() {
   827  						BeforeEach(func() {
   828  							fakeActor.GetOrganizationsReturns(
   829  								[]v3action.Organization{
   830  									v3action.Organization{
   831  										GUID: "some-org-guid3",
   832  										Name: "1234",
   833  									},
   834  									v3action.Organization{
   835  										GUID: "some-org-guid1",
   836  										Name: "some-org-name1",
   837  									},
   838  									v3action.Organization{
   839  										GUID: "some-org-guid2",
   840  										Name: "some-org-name2",
   841  									},
   842  								},
   843  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   844  								nil,
   845  							)
   846  						})
   847  
   848  						When("the user selects an org by list position", func() {
   849  							When("the position is valid", func() {
   850  								BeforeEach(func() {
   851  									input.Write([]byte("2\n"))
   852  								})
   853  
   854  								It("prompts the user to select an org", func() {
   855  									Expect(testUI.Out).To(Say("Select an org:"))
   856  									Expect(testUI.Out).To(Say("1. 1234"))
   857  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   858  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   859  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   860  									Expect(executeErr).ToNot(HaveOccurred())
   861  								})
   862  
   863  								It("targets that org", func() {
   864  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   865  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   866  									Expect(orgGUID).To(Equal("some-org-guid1"))
   867  									Expect(orgName).To(Equal("some-org-name1"))
   868  								})
   869  							})
   870  
   871  							When("the position is invalid", func() {
   872  								BeforeEach(func() {
   873  									input.Write([]byte("4\n"))
   874  								})
   875  
   876  								It("reprompts the user", func() {
   877  									Expect(testUI.Out).To(Say("Select an org:"))
   878  									Expect(testUI.Out).To(Say("1. 1234"))
   879  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   880  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   881  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   882  									Expect(testUI.Out).To(Say("Select an org:"))
   883  									Expect(testUI.Out).To(Say("1. 1234"))
   884  									Expect(testUI.Out).To(Say("2. some-org-name1"))
   885  									Expect(testUI.Out).To(Say("3. some-org-name2"))
   886  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   887  								})
   888  							})
   889  						})
   890  
   891  						When("the user selects an org by name", func() {
   892  							When("the list contains that org", func() {
   893  								BeforeEach(func() {
   894  									input.Write([]byte("some-org-name2\n"))
   895  								})
   896  
   897  								It("prompts the user to select an org", func() {
   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  									Expect(executeErr).ToNot(HaveOccurred())
   904  								})
   905  
   906  								It("targets that org", func() {
   907  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   908  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   909  									Expect(orgGUID).To(Equal("some-org-guid2"))
   910  									Expect(orgName).To(Equal("some-org-name2"))
   911  								})
   912  							})
   913  
   914  							When("the org is not in the list", func() {
   915  								BeforeEach(func() {
   916  									input.Write([]byte("invalid-org\n"))
   917  								})
   918  
   919  								It("returns an error", func() {
   920  									Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"}))
   921  								})
   922  
   923  								It("does not target the org", func() {
   924  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   925  								})
   926  							})
   927  						})
   928  
   929  						When("the user exits the prompt early", func() {
   930  							var fakeUI *commandfakes.FakeUI
   931  
   932  							BeforeEach(func() {
   933  								fakeUI = new(commandfakes.FakeUI)
   934  								cmd.UI = fakeUI
   935  							})
   936  
   937  							When("the prompt returns with an EOF", func() {
   938  								BeforeEach(func() {
   939  									fakeUI.DisplayTextMenuReturns("", io.EOF)
   940  								})
   941  
   942  								It("selects no org and returns no error", func() {
   943  									Expect(executeErr).ToNot(HaveOccurred())
   944  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   945  								})
   946  							})
   947  						})
   948  
   949  					})
   950  
   951  					When("more than 50 orgs exist", func() {
   952  						BeforeEach(func() {
   953  							orgs := make([]v3action.Organization, 51)
   954  							for i := range orgs {
   955  								orgs[i].Name = fmt.Sprintf("org%d", i+1)
   956  								orgs[i].GUID = fmt.Sprintf("org-guid%d", i+1)
   957  							}
   958  
   959  							fakeActor.GetOrganizationsReturns(
   960  								orgs,
   961  								v3action.Warnings{"some-org-warning-1", "some-org-warning-2"},
   962  								nil,
   963  							)
   964  						})
   965  
   966  						When("the user selects an org by name", func() {
   967  							When("the list contains that org", func() {
   968  								BeforeEach(func() {
   969  									input.Write([]byte("org37\n"))
   970  								})
   971  
   972  								It("prompts the user to select an org", func() {
   973  									Expect(testUI.Out).To(Say("There are too many options to display; please type in the name."))
   974  									Expect(testUI.Out).To(Say(`Org \(enter to skip\):`))
   975  									Expect(executeErr).ToNot(HaveOccurred())
   976  								})
   977  
   978  								It("targets that org", func() {
   979  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1))
   980  									orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0)
   981  									Expect(orgGUID).To(Equal("org-guid37"))
   982  									Expect(orgName).To(Equal("org37"))
   983  								})
   984  							})
   985  
   986  							When("the org is not in the list", func() {
   987  								BeforeEach(func() {
   988  									input.Write([]byte("invalid-org\n"))
   989  								})
   990  
   991  								It("returns an error", func() {
   992  									Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"}))
   993  								})
   994  
   995  								It("does not target the org", func() {
   996  									Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0))
   997  								})
   998  							})
   999  						})
  1000  
  1001  					})
  1002  				})
  1003  
  1004  				When("fetching the organizations fails", func() {
  1005  					BeforeEach(func() {
  1006  						fakeActor.GetOrganizationsReturns(
  1007  							[]v3action.Organization{},
  1008  							v3action.Warnings{"some-warning-1", "some-warning-2"},
  1009  							errors.New("api call failed"),
  1010  						)
  1011  					})
  1012  
  1013  					It("returns the error", func() {
  1014  						Expect(executeErr).To(MatchError("api call failed"))
  1015  					})
  1016  
  1017  					It("prints all warnings", func() {
  1018  						Expect(testUI.Err).To(Say("some-warning-1"))
  1019  						Expect(testUI.Err).To(Say("some-warning-2"))
  1020  					})
  1021  				})
  1022  			})
  1023  
  1024  			When("an org has been successfully targeted", func() {
  1025  				BeforeEach(func() {
  1026  					fakeConfig.TargetedOrganizationReturns(configv3.Organization{
  1027  						GUID: "targeted-org-guid",
  1028  						Name: "targeted-org-name"},
  1029  					)
  1030  					fakeConfig.TargetedOrganizationNameReturns("targeted-org-name")
  1031  				})
  1032  
  1033  				When("-s was passed", func() {
  1034  					BeforeEach(func() {
  1035  						cmd.Space = "some-space"
  1036  					})
  1037  
  1038  					When("the specified space exists", func() {
  1039  						BeforeEach(func() {
  1040  							fakeActor.GetSpaceByNameAndOrganizationReturns(
  1041  								v3action.Space{
  1042  									Name: "some-space",
  1043  									GUID: "some-space-guid",
  1044  								},
  1045  								v3action.Warnings{"some-warning-1", "some-warning-2"},
  1046  								nil,
  1047  							)
  1048  						})
  1049  
  1050  						It("targets that space", func() {
  1051  							Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1))
  1052  							spaceGUID, spaceName, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0)
  1053  							Expect(spaceGUID).To(Equal("some-space-guid"))
  1054  							Expect(spaceName).To(Equal("some-space"))
  1055  							Expect(allowSSH).To(BeTrue())
  1056  						})
  1057  
  1058  						It("prints all warnings", func() {
  1059  							Expect(testUI.Err).To(Say("some-warning-1"))
  1060  							Expect(testUI.Err).To(Say("some-warning-2"))
  1061  						})
  1062  
  1063  						When("the space has been successfully targeted", func() {
  1064  							BeforeEach(func() {
  1065  								fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space"})
  1066  							})
  1067  
  1068  							It("displays that the spacce has been targeted", func() {
  1069  								Expect(testUI.Out).To(Say(`Space:\s+some-space`))
  1070  							})
  1071  						})
  1072  					})
  1073  
  1074  					When("the specified space does not exist or does not belong to the targeted org", func() {
  1075  						BeforeEach(func() {
  1076  							fakeActor.GetSpaceByNameAndOrganizationReturns(
  1077  								v3action.Space{},
  1078  								v3action.Warnings{"some-warning-1", "some-warning-2"},
  1079  								actionerror.SpaceNotFoundError{Name: "some-space"},
  1080  							)
  1081  						})
  1082  
  1083  						It("returns an error", func() {
  1084  							Expect(executeErr).To(MatchError(actionerror.SpaceNotFoundError{Name: "some-space"}))
  1085  						})
  1086  
  1087  						It("prints all warnings", func() {
  1088  							Expect(testUI.Err).To(Say("some-warning-1"))
  1089  							Expect(testUI.Err).To(Say("some-warning-2"))
  1090  						})
  1091  
  1092  						It("reports that no space is targeted", func() {
  1093  							Expect(testUI.Out).To(Say(`Space:\s+No space targeted, use 'some-executable target -s SPACE'`))
  1094  						})
  1095  					})
  1096  				})
  1097  			})
  1098  		})
  1099  	})
  1100  })