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 })