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