github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+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/cloudcontroller/ccerror" 11 "code.cloudfoundry.org/cli/api/uaa" 12 "code.cloudfoundry.org/cli/api/uaa/constant" 13 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 14 "code.cloudfoundry.org/cli/command/commandfakes" 15 "code.cloudfoundry.org/cli/command/translatableerror" 16 . "code.cloudfoundry.org/cli/command/v6" 17 "code.cloudfoundry.org/cli/command/v6/v6fakes" 18 "code.cloudfoundry.org/cli/util/configv3" 19 "code.cloudfoundry.org/cli/util/ui" 20 . "github.com/onsi/ginkgo" 21 . "github.com/onsi/gomega" 22 . "github.com/onsi/gomega/gbytes" 23 ) 24 25 var _ = Describe("login Command", func() { 26 var ( 27 binaryName string 28 cmd LoginCommand 29 testUI *ui.UI 30 fakeActor *v6fakes.FakeLoginActor 31 fakeV2Actor *v6fakes.FakeV2LoginActor 32 fakeConfig *commandfakes.FakeConfig 33 fakeActorMaker *v6fakes.FakeActorMaker 34 fakeV2ActorMaker *v6fakes.FakeV2ActorMaker 35 executeErr error 36 input *Buffer 37 ) 38 39 BeforeEach(func() { 40 input = NewBuffer() 41 testUI = ui.NewTestUI(input, NewBuffer(), NewBuffer()) 42 fakeConfig = new(commandfakes.FakeConfig) 43 fakeActor = new(v6fakes.FakeLoginActor) 44 fakeActorMaker = new(v6fakes.FakeActorMaker) 45 fakeActorMaker.NewActorReturns(fakeActor, nil) 46 47 fakeV2Actor = new(v6fakes.FakeV2LoginActor) 48 fakeV2ActorMaker = new(v6fakes.FakeV2ActorMaker) 49 fakeV2ActorMaker.NewV2ActorReturns(fakeV2Actor, nil) 50 binaryName = "some-executable" 51 fakeConfig.BinaryNameReturns(binaryName) 52 53 fakeConfig.UAAOAuthClientReturns("cf") 54 55 cmd = LoginCommand{ 56 UI: testUI, 57 Actor: fakeActor, 58 ActorMaker: fakeActorMaker, 59 Config: fakeConfig, 60 V2ActorMaker: fakeV2ActorMaker, 61 } 62 cmd.APIEndpoint = "" 63 }) 64 65 JustBeforeEach(func() { 66 executeErr = cmd.Execute(nil) 67 }) 68 69 Describe("API Endpoint", func() { 70 BeforeEach(func() { 71 fakeConfig.APIVersionReturns("3.4.5") 72 }) 73 74 When("user provides the api endpoint using the -a flag", func() { 75 BeforeEach(func() { 76 fakeActor.SetTargetReturns(v3action.Warnings{"some-warning-1", "some-warning-2"}, nil) 77 cmd.APIEndpoint = "api.example.com" 78 }) 79 80 When("the user specifies --skip-ssl-validation", func() { 81 BeforeEach(func() { 82 cmd.SkipSSLValidation = true 83 }) 84 It("targets the provided api endpoint", func() { 85 Expect(executeErr).ToNot(HaveOccurred()) 86 Expect(testUI.Out).To(Say("API endpoint: api.example.com\n\n")) 87 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 88 actualSettings := fakeActor.SetTargetArgsForCall(0) 89 Expect(actualSettings.URL).To(Equal("https://api.example.com")) 90 Expect(actualSettings.SkipSSLValidation).To(Equal(true)) 91 }) 92 }) 93 94 When("the user does not specify --skip-ssl-validation", func() { 95 It("targets the provided api endpoint", func() { 96 Expect(executeErr).ToNot(HaveOccurred()) 97 Expect(testUI.Out).To(Say("API endpoint: api.example.com\n\n")) 98 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 99 actualSettings := fakeActor.SetTargetArgsForCall(0) 100 Expect(actualSettings.URL).To(Equal("https://api.example.com")) 101 Expect(actualSettings.SkipSSLValidation).To(Equal(false)) 102 }) 103 }) 104 105 It("prints all warnings", func() { 106 Expect(testUI.Err).To(Say("some-warning-1")) 107 Expect(testUI.Err).To(Say("some-warning-2")) 108 }) 109 110 When("targeting the API fails", func() { 111 BeforeEach(func() { 112 fakeActor.SetTargetReturns( 113 v3action.Warnings{"some-warning-1", "some-warning-2"}, 114 errors.New("some error")) 115 }) 116 117 It("errors", func() { 118 Expect(executeErr).To(MatchError("some error")) 119 }) 120 121 It("prints all warnings", func() { 122 Expect(testUI.Err).To(Say("some-warning-1")) 123 Expect(testUI.Err).To(Say("some-warning-2")) 124 }) 125 }) 126 }) 127 128 When("user does not provide the api endpoint using the -a flag", func() { 129 When("config has API endpoint already set", func() { 130 BeforeEach(func() { 131 fakeConfig.TargetReturns("api.fake.com") 132 }) 133 134 It("does not prompt the user for an API endpoint", func() { 135 Expect(executeErr).ToNot(HaveOccurred()) 136 Expect(testUI.Out).To(Say(`API endpoint:\s+api\.fake\.com \(API version: 3\.4\.5\)`)) 137 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 138 }) 139 140 When("the config has SkipSSLValidation false and the --skip-ssl-validation flag is passed", func() { 141 BeforeEach(func() { 142 fakeConfig.SkipSSLValidationReturns(false) 143 cmd.SkipSSLValidation = true 144 }) 145 146 It("sets the target with SkipSSLValidation is true", func() { 147 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 148 targetSettings := fakeActor.SetTargetArgsForCall(0) 149 Expect(targetSettings.SkipSSLValidation).To(BeTrue()) 150 }) 151 152 It("does not error", func() { 153 Expect(executeErr).ToNot(HaveOccurred()) 154 }) 155 }) 156 }) 157 158 When("config does not have an API endpoint set and the user enters the endpoint at the prompt", func() { 159 BeforeEach(func() { 160 cmd.APIEndpoint = "" 161 input.Write([]byte("api.example.com\n")) 162 fakeConfig.TargetReturnsOnCall(0, "") 163 fakeConfig.TargetReturnsOnCall(1, "https://api.example.com") 164 }) 165 166 When("the user specifies --skip-ssl-validation", func() { 167 BeforeEach(func() { 168 cmd.SkipSSLValidation = true 169 }) 170 171 It("targets the API that the user inputted", func() { 172 Expect(executeErr).ToNot(HaveOccurred()) 173 174 Expect(testUI.Out).To(Say("API endpoint:")) 175 Expect(testUI.Out).To(Say("api.example.com\n")) 176 Expect(testUI.Out).To(Say(`API endpoint:\s+https://api\.example\.com \(API version: 3\.4\.5\)`)) 177 178 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 179 actualSettings := fakeActor.SetTargetArgsForCall(0) 180 Expect(actualSettings.URL).To(Equal("https://api.example.com")) 181 Expect(actualSettings.SkipSSLValidation).To(Equal(true)) 182 183 Expect(fakeConfig.TargetCallCount()).To(Equal(2)) 184 }) 185 }) 186 187 When("the user does not specify --skip-ssl-validation", func() { 188 BeforeEach(func() { 189 cmd.SkipSSLValidation = false 190 }) 191 192 It("targets the API that the user inputted", func() { 193 Expect(executeErr).ToNot(HaveOccurred()) 194 195 Expect(testUI.Out).To(Say("API endpoint:")) 196 Expect(testUI.Out).To(Say("api.example.com\n")) 197 Expect(testUI.Out).To(Say(`API endpoint:\s+https://api\.example\.com \(API version: 3\.4\.5\)`)) 198 199 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 200 actualSettings := fakeActor.SetTargetArgsForCall(0) 201 Expect(actualSettings.URL).To(Equal("https://api.example.com")) 202 Expect(actualSettings.SkipSSLValidation).To(Equal(false)) 203 204 Expect(fakeConfig.TargetCallCount()).To(Equal(2)) 205 }) 206 }) 207 }) 208 209 When("the user inputs an empty API", func() { 210 BeforeEach(func() { 211 cmd.APIEndpoint = "" 212 input.Write([]byte("\n\napi.example.com\n")) 213 fakeConfig.TargetReturnsOnCall(0, "") 214 fakeConfig.TargetReturnsOnCall(1, "https://api.example.com") 215 }) 216 217 It("reprompts for the API", func() { 218 Expect(executeErr).ToNot(HaveOccurred()) 219 Expect(testUI.Out).To(Say("API endpoint:")) 220 Expect(testUI.Out).To(Say("API endpoint:")) 221 Expect(testUI.Out).To(Say("API endpoint:")) 222 Expect(testUI.Out).To(Say("api.example.com\n")) 223 Expect(testUI.Out).To(Say(`API endpoint:\s+https://api\.example\.com \(API version: 3\.4\.5\)`)) 224 Expect(fakeConfig.TargetCallCount()).To(Equal(2)) 225 }) 226 }) 227 }) 228 229 When("the endpoint has trailing slashes", func() { 230 BeforeEach(func() { 231 cmd.APIEndpoint = "api.example.com////" 232 fakeConfig.TargetReturns("https://api.example.com///") 233 }) 234 235 It("strips the backslashes before using the endpoint", func() { 236 Expect(executeErr).ToNot(HaveOccurred()) 237 Expect(fakeActor.SetTargetCallCount()).To(Equal(1)) 238 actualSettings := fakeActor.SetTargetArgsForCall(0) 239 Expect(actualSettings.URL).To(Equal("https://api.example.com")) 240 241 Expect(testUI.Out).To(Say(`API endpoint:\s+https://api\.example\.com \(API version: 3\.4\.5\)`)) 242 Expect(fakeConfig.TargetCallCount()).To(Equal(1)) 243 }) 244 }) 245 246 When("the endpoint is well-formed", func() { 247 BeforeEach(func() { 248 cmd.APIEndpoint = "api.example.com" 249 }) 250 251 When("targeting the API fails due to an invalid certificate", func() { 252 BeforeEach(func() { 253 fakeActor.SetTargetReturns(nil, ccerror.UnverifiedServerError{URL: "https://api.example.com"}) 254 }) 255 It("returns an error mentioning the login command", func() { 256 Expect(executeErr).To(MatchError( 257 translatableerror.InvalidSSLCertError{URL: "https://api.example.com", SuggestedCommand: "login"})) 258 }) 259 }) 260 261 When("reloading the actor fails due to an invalid certificate", func() { 262 BeforeEach(func() { 263 fakeActorMaker.NewActorReturns(nil, ccerror.UnverifiedServerError{URL: "https://api.example.com"}) 264 }) 265 It("returns an error mentioning the login command", func() { 266 Expect(executeErr).To(MatchError( 267 translatableerror.InvalidSSLCertError{URL: "https://api.example.com", SuggestedCommand: "login"})) 268 }) 269 }) 270 }) 271 }) 272 273 Describe("Minimum CLI version", func() { 274 var fakeAPI string 275 BeforeEach(func() { 276 fakeAPI = "whatever.com" 277 fakeConfig.TargetReturns(fakeAPI) 278 279 fakeV2Actor.MinCLIVersionReturns("9000.0.0") 280 }) 281 282 It("sets the minimum CLI version in the config", func() { 283 Expect(executeErr).NotTo(HaveOccurred()) 284 Expect(fakeConfig.SetMinCLIVersionCallCount()).To(Equal(1)) 285 Expect(fakeConfig.SetMinCLIVersionArgsForCall(0)).To(Equal("9000.0.0")) 286 }) 287 288 When("The current version is below the minimum supported", func() { 289 BeforeEach(func() { 290 fakeV2Actor.CloudControllerAPIVersionReturns("2.123.0") 291 fakeConfig.BinaryVersionReturns("1.2.3") 292 fakeConfig.MinCLIVersionReturns("9000.0.0") 293 }) 294 295 It("displays a warning", func() { 296 Expect(executeErr).NotTo(HaveOccurred()) 297 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")) 298 }) 299 300 Context("ordering of output", func() { 301 BeforeEach(func() { 302 outAndErr := NewBuffer() 303 testUI.Out = outAndErr 304 testUI.Err = outAndErr 305 }) 306 307 It("displays the warning after the API targeting but before the summary ", func() { 308 Expect(executeErr).NotTo(HaveOccurred()) 309 Expect(testUI.Out).To(Say(`API endpoint: `)) 310 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")) 311 Expect(testUI.Out).To(Say(`Authenticating...`)) 312 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, fakeAPI)) 313 Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' or '%s login --sso' to log in.`, binaryName, binaryName)) 314 }) 315 }) 316 }) 317 }) 318 319 Describe("username and password", func() { 320 BeforeEach(func() { 321 fakeConfig.TargetReturns("https://some.random.endpoint") 322 }) 323 324 When("the current grant type is client credentials", func() { 325 BeforeEach(func() { 326 fakeConfig.UAAGrantTypeReturns(string(constant.GrantTypeClientCredentials)) 327 }) 328 329 It("returns an error", func() { 330 Expect(executeErr).To(MatchError("Service account currently logged in. Use 'cf logout' to log out service account and try again.")) 331 }) 332 333 It("the returned error is translatable", func() { 334 Expect(executeErr).To(MatchError(translatableerror.PasswordGrantTypeLogoutRequiredError{})) 335 }) 336 337 When("client secret in the configuration is present", func() { 338 It("should not display a warning", func() { 339 Expect(testUI.Err).NotTo(Say("Deprecation warning:")) 340 }) 341 }) 342 }) 343 344 When("the current grant type is password", func() { 345 BeforeEach(func() { 346 fakeConfig.UAAGrantTypeReturns(string(constant.GrantTypePassword)) 347 }) 348 349 It("fetches prompts from the UAA", func() { 350 Expect(executeErr).ToNot(HaveOccurred()) 351 Expect(fakeActor.GetLoginPromptsCallCount()).To(Equal(1)) 352 }) 353 354 When("fetching prompts succeeds", func() { 355 When("one of the prompts has a username key and is text type", func() { 356 BeforeEach(func() { 357 fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{ 358 "username": { 359 DisplayName: "Username", 360 Type: coreconfig.AuthPromptTypeText, 361 }, 362 }) 363 }) 364 365 When("the username flag is set", func() { 366 BeforeEach(func() { 367 cmd.Username = "potatoface" 368 }) 369 370 It("uses the provided value and does not prompt for the username", func() { 371 Expect(executeErr).ToNot(HaveOccurred()) 372 Expect(testUI.Out).NotTo(Say("Username:")) 373 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 374 credentials, _, _ := fakeActor.AuthenticateArgsForCall(0) 375 Expect(credentials["username"]).To(Equal("potatoface")) 376 }) 377 378 When("the --origin flag is set", func() { 379 BeforeEach(func() { 380 cmd.Origin = "picklebike" 381 }) 382 It("authenticates with it", func() { 383 Expect(executeErr).NotTo(HaveOccurred()) 384 Expect(testUI.Out).NotTo(Say("Username:")) 385 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 386 credentials, origin, _ := fakeActor.AuthenticateArgsForCall(0) 387 Expect(credentials["username"]).To(Equal("potatoface")) 388 Expect(origin).To(Equal("picklebike")) 389 }) 390 391 }) 392 }) 393 }) 394 395 When("one of the prompts has password key and is password type", func() { 396 BeforeEach(func() { 397 fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{ 398 "password": { 399 DisplayName: "Your Password", 400 Type: coreconfig.AuthPromptTypePassword, 401 }, 402 }) 403 }) 404 405 When("the password flag is set", func() { 406 BeforeEach(func() { 407 cmd.Password = "noprompto" 408 }) 409 410 It("uses the provided value and does not prompt for the password", func() { 411 Expect(executeErr).ToNot(HaveOccurred()) 412 Expect(testUI.Out).NotTo(Say("Your Password:")) 413 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 414 credentials, _, _ := fakeActor.AuthenticateArgsForCall(0) 415 Expect(credentials["password"]).To(Equal("noprompto")) 416 }) 417 418 When("the password is incorrect", func() { 419 BeforeEach(func() { 420 input.Write([]byte("other-password\n")) 421 fakeActor.AuthenticateReturns(errors.New("bad creds")) 422 }) 423 424 It("does not reuse the flag value for subsequent attempts", func() { 425 credentials, _, _ := fakeActor.AuthenticateArgsForCall(1) 426 Expect(credentials["password"]).To(Equal("other-password")) 427 }) 428 }) 429 430 When("there have been too many failed login attempts", func() { 431 BeforeEach(func() { 432 input.Write([]byte("other-password\n")) 433 fakeActor.AuthenticateReturns( 434 uaa.AccountLockedError{ 435 Message: "Your account has been locked because of too many failed attempts to login.", 436 }, 437 ) 438 }) 439 440 It("does not reuse the flag value for subsequent attempts", func() { 441 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1), "called Authenticate again after lockout") 442 Expect(testUI.Err).To(Say("Your account has been locked because of too many failed attempts to login.")) 443 }) 444 }) 445 }) 446 }) 447 448 When("UAA prompts for the SSO passcode during non-SSO flow", func() { 449 BeforeEach(func() { 450 cmd.SSO = false 451 cmd.Password = "some-password" 452 fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{ 453 "password": { 454 DisplayName: "Your Password", 455 Type: coreconfig.AuthPromptTypePassword, 456 }, 457 "passcode": { 458 DisplayName: "gimme your passcode", 459 Type: coreconfig.AuthPromptTypePassword, 460 }, 461 }) 462 }) 463 464 It("does not prompt for the passcode", func() { 465 Expect(executeErr).ToNot(HaveOccurred()) 466 Expect(testUI.Out).NotTo(Say("gimme your passcode")) 467 }) 468 469 It("does not send the passcode", func() { 470 Expect(executeErr).ToNot(HaveOccurred()) 471 credentials, _, _ := fakeActor.AuthenticateArgsForCall(0) 472 Expect(credentials).To(HaveKeyWithValue("password", "some-password")) 473 Expect(credentials).NotTo(HaveKey("passcode")) 474 }) 475 }) 476 477 When("multiple prompts of text and password type are returned", func() { 478 BeforeEach(func() { 479 fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{ 480 "account_number": { 481 DisplayName: "Account Number", 482 Type: coreconfig.AuthPromptTypeText, 483 }, 484 "username": { 485 DisplayName: "Username", 486 Type: coreconfig.AuthPromptTypeText, 487 }, 488 "passcode": { 489 DisplayName: "It's a passcode, what you want it to be???", 490 Type: coreconfig.AuthPromptTypePassword, 491 }, 492 "password": { 493 DisplayName: "Your Password", 494 Type: coreconfig.AuthPromptTypePassword, 495 }, 496 "supersecret": { 497 DisplayName: "MFA Code", 498 Type: coreconfig.AuthPromptTypePassword, 499 }, 500 }) 501 }) 502 503 When("no authentication flags are set", func() { 504 BeforeEach(func() { 505 input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\n")) 506 }) 507 508 It("displays text prompts, starting with username, then password prompts, starting with password", func() { 509 Expect(executeErr).ToNot(HaveOccurred()) 510 511 Expect(testUI.Out).To(Say("\n\n")) 512 Expect(testUI.Out).To(Say("Username:")) 513 Expect(testUI.Out).To(Say("faker")) 514 515 Expect(testUI.Out).To(Say("\n\n")) 516 Expect(testUI.Out).To(Say("Account Number:")) 517 Expect(testUI.Out).To(Say("someaccount")) 518 519 Expect(testUI.Out).To(Say("\n\n")) 520 Expect(testUI.Out).To(Say("Your Password:")) 521 Expect(testUI.Out).NotTo(Say("somepassword")) 522 523 Expect(testUI.Out).To(Say("\n\n")) 524 Expect(testUI.Out).To(Say("MFA Code:")) 525 Expect(testUI.Out).NotTo(Say("garbage")) 526 }) 527 528 It("authenticates with the responses", func() { 529 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 530 credentials, _, grantType := fakeActor.AuthenticateArgsForCall(0) 531 Expect(credentials["username"]).To(Equal("faker")) 532 Expect(credentials["password"]).To(Equal("somepassword")) 533 Expect(credentials["supersecret"]).To(Equal("garbage")) 534 Expect(grantType).To(Equal(constant.GrantTypePassword)) 535 }) 536 }) 537 538 When("an error occurs prompting for the username", func() { 539 var fakeUI *commandfakes.FakeUI 540 541 BeforeEach(func() { 542 fakeUI = new(commandfakes.FakeUI) 543 fakeUI.DisplayTextPromptReturns("", errors.New("some-error")) 544 cmd = LoginCommand{ 545 UI: fakeUI, 546 Actor: fakeActor, 547 ActorMaker: fakeActorMaker, 548 Config: fakeConfig, 549 V2ActorMaker: fakeV2ActorMaker, 550 } 551 }) 552 553 It("stops prompting after the first prompt", func() { 554 Expect(fakeUI.DisplayTextPromptCallCount()).To(Equal(1)) 555 }) 556 557 It("errors", func() { 558 Expect(executeErr).To(MatchError("Unable to authenticate.")) 559 }) 560 }) 561 562 When("an error occurs in an additional text prompt after username", func() { 563 var fakeUI *commandfakes.FakeUI 564 565 BeforeEach(func() { 566 fakeUI = new(commandfakes.FakeUI) 567 fakeUI.DisplayTextPromptReturnsOnCall(0, "some-name", nil) 568 fakeUI.DisplayTextPromptReturnsOnCall(1, "", errors.New("some-error")) 569 cmd = LoginCommand{ 570 UI: fakeUI, 571 Actor: fakeActor, 572 ActorMaker: fakeActorMaker, 573 Config: fakeConfig, 574 V2ActorMaker: fakeV2ActorMaker, 575 } 576 }) 577 578 It("returns the error", func() { 579 Expect(executeErr).To(MatchError("Unable to authenticate.")) 580 }) 581 }) 582 583 When("an error occurs prompting for the password", func() { 584 var fakeUI *commandfakes.FakeUI 585 586 BeforeEach(func() { 587 fakeUI = new(commandfakes.FakeUI) 588 fakeUI.DisplayPasswordPromptReturns("", errors.New("some-error")) 589 cmd = LoginCommand{ 590 UI: fakeUI, 591 Actor: fakeActor, 592 ActorMaker: fakeActorMaker, 593 Config: fakeConfig, 594 V2ActorMaker: fakeV2ActorMaker, 595 } 596 }) 597 598 It("stops prompting after the first prompt", func() { 599 Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(1)) 600 }) 601 602 It("errors", func() { 603 Expect(executeErr).To(MatchError("Unable to authenticate.")) 604 }) 605 }) 606 607 When("an error occurs prompting for prompts of type password that are not the 'password'", func() { 608 var fakeUI *commandfakes.FakeUI 609 610 BeforeEach(func() { 611 fakeUI = new(commandfakes.FakeUI) 612 fakeUI.DisplayPasswordPromptReturnsOnCall(0, "some-password", nil) 613 fakeUI.DisplayPasswordPromptReturnsOnCall(1, "", errors.New("some-error")) 614 615 cmd = LoginCommand{ 616 UI: fakeUI, 617 Actor: fakeActor, 618 ActorMaker: fakeActorMaker, 619 Config: fakeConfig, 620 V2ActorMaker: fakeV2ActorMaker, 621 } 622 }) 623 624 It("stops prompting after the second prompt", func() { 625 Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(2)) 626 }) 627 628 It("errors", func() { 629 Expect(executeErr).To(MatchError("Unable to authenticate.")) 630 }) 631 }) 632 633 When("authenticating succeeds", func() { 634 BeforeEach(func() { 635 fakeConfig.CurrentUserNameReturns("potatoface", nil) 636 input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\n")) 637 }) 638 639 It("displays OK and a status summary", func() { 640 Expect(executeErr).ToNot(HaveOccurred()) 641 Expect(testUI.Out).To(Say("OK")) 642 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint)) 643 Expect(testUI.Out).To(Say(`User:\s+potatoface`)) 644 645 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 646 }) 647 }) 648 649 When("authenticating fails", func() { 650 BeforeEach(func() { 651 fakeActor.AuthenticateReturns(errors.New("something died")) 652 input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\n")) 653 }) 654 655 It("prints the error message three times", func() { 656 Expect(testUI.Out).To(Say("Your Password:")) 657 Expect(testUI.Out).To(Say("MFA Code:")) 658 Expect(testUI.Err).To(Say("something died")) 659 Expect(testUI.Out).To(Say("Your Password:")) 660 Expect(testUI.Out).To(Say("MFA Code:")) 661 Expect(testUI.Err).To(Say("something died")) 662 Expect(testUI.Out).To(Say("Your Password:")) 663 Expect(testUI.Out).To(Say("MFA Code:")) 664 Expect(testUI.Err).To(Say("something died")) 665 }) 666 667 It("returns an error indicating that it could not authenticate", func() { 668 Expect(executeErr).To(MatchError("Unable to authenticate.")) 669 }) 670 671 It("displays a status summary", func() { 672 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, cmd.APIEndpoint)) 673 Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' or '%s login --sso' to log in.`, cmd.Config.BinaryName(), cmd.Config.BinaryName())) 674 }) 675 676 }) 677 678 When("authenticating fails with a bad credentials error", func() { 679 BeforeEach(func() { 680 fakeActor.AuthenticateReturns(uaa.UnauthorizedError{Message: "Bad credentials"}) 681 input.Write([]byte("faker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\nfaker\nsomeaccount\nsomepassword\ngarbage\n")) 682 }) 683 684 It("converts the error before printing it", func() { 685 Expect(testUI.Out).To(Say("Your Password:")) 686 Expect(testUI.Out).To(Say("MFA Code:")) 687 Expect(testUI.Err).To(Say("Credentials were rejected, please try again.")) 688 Expect(testUI.Out).To(Say("Your Password:")) 689 Expect(testUI.Out).To(Say("MFA Code:")) 690 Expect(testUI.Err).To(Say("Credentials were rejected, please try again.")) 691 Expect(testUI.Out).To(Say("Your Password:")) 692 Expect(testUI.Out).To(Say("MFA Code:")) 693 Expect(testUI.Err).To(Say("Credentials were rejected, please try again.")) 694 }) 695 }) 696 }) 697 698 When("custom client ID and client secret are set in the config file", func() { 699 BeforeEach(func() { 700 fakeConfig.UAAOAuthClientReturns("some-other-client-id") 701 fakeConfig.UAAOAuthClientSecretReturns("some-secret") 702 }) 703 704 It("prints a deprecation warning", func() { 705 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." 706 Expect(testUI.Err).To(Say(deprecationMessage)) 707 }) 708 709 It("still attempts to log in", func() { 710 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 711 }) 712 }) 713 }) 714 }) 715 }) 716 717 Describe("SSO Passcode", func() { 718 fakeAPI := "whatever.com" 719 BeforeEach(func() { 720 fakeConfig.TargetReturns(fakeAPI) 721 722 input.Write([]byte("some-passcode\n")) 723 fakeActor.GetLoginPromptsReturns(map[string]coreconfig.AuthPrompt{ 724 "passcode": { 725 DisplayName: "some-sso-prompt", 726 Type: coreconfig.AuthPromptTypePassword, 727 }, 728 }) 729 730 fakeConfig.CurrentUserNameReturns("potatoface", nil) 731 }) 732 733 When("--sso flag is set", func() { 734 BeforeEach(func() { 735 cmd.SSO = true 736 }) 737 738 It("prompts the user for SSO passcode", func() { 739 Expect(executeErr).NotTo(HaveOccurred()) 740 Expect(fakeActor.GetLoginPromptsCallCount()).To(Equal(1)) 741 Expect(testUI.Out).To(Say("some-sso-prompt:")) 742 }) 743 744 It("authenticates with the inputted code", func() { 745 Expect(testUI.Out).To(Say("OK")) 746 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, fakeAPI)) 747 Expect(testUI.Out).To(Say(`User:\s+potatoface`)) 748 749 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 750 credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0) 751 Expect(credentials["passcode"]).To(Equal("some-passcode")) 752 Expect(origin).To(BeEmpty()) 753 Expect(grantType).To(Equal(constant.GrantTypePassword)) 754 }) 755 756 When("an error occurs prompting for the code", func() { 757 var fakeUI *commandfakes.FakeUI 758 759 BeforeEach(func() { 760 fakeUI = new(commandfakes.FakeUI) 761 fakeUI.DisplayPasswordPromptReturns("", errors.New("some-error")) 762 cmd = LoginCommand{ 763 UI: fakeUI, 764 Actor: fakeActor, 765 ActorMaker: fakeActorMaker, 766 Config: fakeConfig, 767 V2ActorMaker: fakeV2ActorMaker, 768 SSO: true, 769 } 770 }) 771 772 It("errors", func() { 773 Expect(fakeUI.DisplayPasswordPromptCallCount()).To(Equal(1)) 774 Expect(executeErr).To(MatchError("Unable to authenticate.")) 775 }) 776 }) 777 }) 778 779 When("the --sso-passcode flag is set", func() { 780 BeforeEach(func() { 781 cmd.SSOPasscode = "a-passcode" 782 }) 783 784 It("does not prompt the user for SSO passcode", func() { 785 Expect(executeErr).NotTo(HaveOccurred()) 786 Expect(testUI.Out).ToNot(Say("some-sso-prompt:")) 787 }) 788 789 It("uses the flag value to authenticate", func() { 790 Expect(executeErr).NotTo(HaveOccurred()) 791 Expect(fakeActor.AuthenticateCallCount()).To(Equal(1)) 792 credentials, origin, grantType := fakeActor.AuthenticateArgsForCall(0) 793 Expect(credentials["passcode"]).To(Equal("a-passcode")) 794 Expect(origin).To(BeEmpty()) 795 Expect(grantType).To(Equal(constant.GrantTypePassword)) 796 }) 797 798 It("displays a summary with user information", func() { 799 Expect(executeErr).NotTo(HaveOccurred()) 800 Expect(testUI.Out).To(Say("OK")) 801 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, fakeAPI)) 802 Expect(testUI.Out).To(Say(`User:\s+potatoface`)) 803 }) 804 805 When("an incorrect passcode is inputted", func() { 806 BeforeEach(func() { 807 cmd.SSOPasscode = "some-garbage" 808 fakeActor.AuthenticateReturns(uaa.UnauthorizedError{ 809 Message: "Bad credentials", 810 }) 811 fakeConfig.CurrentUserNameReturns("", nil) 812 input.Write([]byte("some-passcode\n")) 813 }) 814 815 It("re-prompts two more times", func() { 816 Expect(testUI.Out).To(Say("some-sso-prompt:")) 817 Expect(testUI.Out).To(Say(`Authenticating\.\.\.`)) 818 Expect(testUI.Err).To(Say("Credentials were rejected, please try again.")) 819 Expect(testUI.Out).To(Say("some-sso-prompt:")) 820 Expect(testUI.Out).To(Say(`Authenticating\.\.\.`)) 821 Expect(testUI.Err).To(Say("Credentials were rejected, please try again.")) 822 }) 823 824 It("returns an error message", func() { 825 Expect(executeErr).To(MatchError("Unable to authenticate.")) 826 }) 827 828 It("does not include user information in the summary", func() { 829 Expect(testUI.Out).To(Say(`API endpoint:\s+%s`, fakeAPI)) 830 Expect(testUI.Out).To(Say(`Not logged in. Use '%s login' or '%s login --sso' to log in.`, cmd.Config.BinaryName(), cmd.Config.BinaryName())) 831 }) 832 }) 833 }) 834 835 When("both --sso and --sso-passcode flags are set", func() { 836 BeforeEach(func() { 837 cmd.SSO = true 838 cmd.SSOPasscode = "a-passcode" 839 }) 840 841 It("returns an error message", func() { 842 Expect(fakeActor.AuthenticateCallCount()).To(Equal(0)) 843 Expect(executeErr).To(MatchError(translatableerror.ArgumentCombinationError{Args: []string{"--sso-passcode", "--sso"}})) 844 }) 845 }) 846 }) 847 848 Describe("Config", func() { 849 When("a user has successfully authenticated", func() { 850 BeforeEach(func() { 851 cmd.APIEndpoint = "example.com" 852 cmd.Username = "some-user" 853 cmd.Password = "some-password" 854 fakeConfig.APIVersionReturns("3.4.5") 855 fakeConfig.CurrentUserNameReturns("some-user", nil) 856 }) 857 858 It("writes to the config", func() { 859 Expect(executeErr).ToNot(HaveOccurred()) 860 Expect(fakeConfig.WriteConfigCallCount()).To(Equal(1)) 861 }) 862 863 When("GetOrganizations fails", func() { 864 BeforeEach(func() { 865 fakeActor.GetOrganizationsReturns(nil, nil, errors.New("Org Failure")) 866 }) 867 It("writes to the config", func() { 868 Expect(executeErr).To(HaveOccurred()) 869 Expect(fakeConfig.WriteConfigCallCount()).To(Equal(1)) 870 }) 871 }) 872 873 When("WriteConfig returns an error", func() { 874 BeforeEach(func() { 875 fakeConfig.WriteConfigReturns(errors.New("Config Failure")) 876 }) 877 It("throws that error", func() { 878 Expect(executeErr).To(MatchError("Error writing config: Config Failure")) 879 }) 880 }) 881 }) 882 }) 883 884 Describe("Targeting Org", func() { 885 BeforeEach(func() { 886 cmd.APIEndpoint = "example.com" 887 cmd.Username = "some-user" 888 cmd.Password = "some-password" 889 fakeConfig.APIVersionReturns("3.4.5") 890 fakeConfig.CurrentUserNameReturns("some-user", nil) 891 }) 892 893 When("-o was passed", func() { 894 BeforeEach(func() { 895 cmd.Organization = "some-org" 896 }) 897 898 It("fetches the specified organization", func() { 899 Expect(fakeActor.GetOrganizationByNameCallCount()).To(Equal(1)) 900 Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(0)) 901 Expect(fakeActor.GetOrganizationByNameArgsForCall(0)).To(Equal("some-org")) 902 }) 903 904 When("fetching the organization succeeds", func() { 905 BeforeEach(func() { 906 fakeActor.GetOrganizationByNameReturns( 907 v3action.Organization{Name: "some-org", GUID: "some-guid"}, 908 v3action.Warnings{"some-warning-1", "some-warning-2"}, 909 nil) 910 fakeConfig.TargetedOrganizationNameReturns("some-org") 911 fakeConfig.TargetReturns("https://example.com") 912 }) 913 914 It("prints all warnings", func() { 915 Expect(testUI.Err).To(Say("some-warning-1")) 916 Expect(testUI.Err).To(Say("some-warning-2")) 917 }) 918 919 It("sets the targeted organization in the config", func() { 920 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1)) 921 orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0) 922 Expect(orgGUID).To(Equal("some-guid")) 923 Expect(orgName).To(Equal("some-org")) 924 }) 925 926 It("reports to the user that the org is targeted", func() { 927 Expect(testUI.Out).To(Say(`API endpoint:\s+https://example.com \(API version: 3.4.5\)`)) 928 Expect(testUI.Out).To(Say("User: some-user")) 929 Expect(testUI.Out).To(Say("Org: some-org")) 930 }) 931 }) 932 933 When("fetching the organization fails", func() { 934 BeforeEach(func() { 935 fakeActor.GetOrganizationByNameReturns( 936 v3action.Organization{}, 937 v3action.Warnings{"some-warning-1", "some-warning-2"}, 938 errors.New("org-not-found"), 939 ) 940 }) 941 942 It("prints all warnings", func() { 943 Expect(testUI.Err).To(Say("some-warning-1")) 944 Expect(testUI.Err).To(Say("some-warning-2")) 945 }) 946 947 It("does not set the targeted org", func() { 948 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0)) 949 }) 950 }) 951 }) 952 953 When("-o was not passed", func() { 954 BeforeEach(func() { 955 cmd.APIEndpoint = "example.com" 956 cmd.Username = "some-user" 957 cmd.Password = "some-password" 958 fakeActor.GetOrganizationsReturns( 959 []v3action.Organization{}, 960 v3action.Warnings{"some-org-warning-1", "some-org-warning-2"}, 961 nil, 962 ) 963 }) 964 965 It("fetches the available organizations", func() { 966 Expect(executeErr).ToNot(HaveOccurred()) 967 Expect(fakeActor.GetOrganizationsCallCount()).To(Equal(1)) 968 }) 969 970 It("prints all warnings", func() { 971 Expect(testUI.Err).To(Say("some-org-warning-1")) 972 Expect(testUI.Err).To(Say("some-org-warning-2")) 973 }) 974 975 When("fetching the organizations succeeds", func() { 976 BeforeEach(func() { 977 fakeConfig.CurrentUserNameReturns("some-user", nil) 978 fakeConfig.TargetReturns("https://example.com") 979 }) 980 981 When("no org exists", func() { 982 It("does not prompt the user to select an org", func() { 983 Expect(executeErr).ToNot(HaveOccurred()) 984 Expect(testUI.Out).ToNot(Say("Select an org:")) 985 Expect(testUI.Out).ToNot(Say(`Org \(enter to skip\):`)) 986 }) 987 988 It("displays how to target an org and space", func() { 989 Expect(executeErr).ToNot(HaveOccurred()) 990 991 Expect(testUI.Out).To(Say(`API endpoint:\s+https://example.com \(API version: 3.4.5\)`)) 992 Expect(testUI.Out).To(Say(`User:\s+some-user`)) 993 Expect(testUI.Out).To(Say("No org or space targeted, use '%s target -o ORG -s SPACE'", binaryName)) 994 }) 995 }) 996 997 When("only one org exists", func() { 998 BeforeEach(func() { 999 fakeActor.GetOrganizationsReturns( 1000 []v3action.Organization{v3action.Organization{ 1001 GUID: "some-org-guid", 1002 Name: "some-org-name", 1003 }}, 1004 v3action.Warnings{"some-org-warning-1", "some-org-warning-2"}, 1005 nil, 1006 ) 1007 }) 1008 1009 It("targets that org", func() { 1010 Expect(executeErr).ToNot(HaveOccurred()) 1011 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1)) 1012 orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0) 1013 Expect(orgGUID).To(Equal("some-org-guid")) 1014 Expect(orgName).To(Equal("some-org-name")) 1015 }) 1016 }) 1017 1018 When("more than one but fewer than 50 orgs exists", func() { 1019 BeforeEach(func() { 1020 fakeActor.GetOrganizationsReturns( 1021 []v3action.Organization{ 1022 v3action.Organization{ 1023 GUID: "some-org-guid3", 1024 Name: "1234", 1025 }, 1026 v3action.Organization{ 1027 GUID: "some-org-guid1", 1028 Name: "some-org-name1", 1029 }, 1030 v3action.Organization{ 1031 GUID: "some-org-guid2", 1032 Name: "some-org-name2", 1033 }, 1034 }, 1035 v3action.Warnings{"some-org-warning-1", "some-org-warning-2"}, 1036 nil, 1037 ) 1038 }) 1039 1040 When("the user selects an org by list position", func() { 1041 When("the position is valid", func() { 1042 BeforeEach(func() { 1043 fakeConfig.TargetedOrganizationReturns(configv3.Organization{ 1044 GUID: "targeted-org-guid1"}) 1045 fakeConfig.TargetedOrganizationNameReturns("targeted-org-name") 1046 input.Write([]byte("2\n")) 1047 }) 1048 1049 It("prompts the user to select an org", func() { 1050 Expect(testUI.Out).To(Say("Select an org:")) 1051 Expect(testUI.Out).To(Say("1. 1234")) 1052 Expect(testUI.Out).To(Say("2. some-org-name1")) 1053 Expect(testUI.Out).To(Say("3. some-org-name2")) 1054 Expect(testUI.Out).To(Say("\n\n")) 1055 Expect(testUI.Out).To(Say(`Org \(enter to skip\):`)) 1056 Expect(executeErr).ToNot(HaveOccurred()) 1057 }) 1058 1059 It("targets that org", func() { 1060 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1)) 1061 orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0) 1062 Expect(orgGUID).To(Equal("some-org-guid1")) 1063 Expect(orgName).To(Equal("some-org-name1")) 1064 }) 1065 1066 It("outputs targeted org", func() { 1067 Expect(testUI.Out).To(Say("Targeted org targeted-org-name")) 1068 }) 1069 }) 1070 1071 When("the position is invalid", func() { 1072 BeforeEach(func() { 1073 input.Write([]byte("4\n")) 1074 }) 1075 1076 It("reprompts the user", func() { 1077 Expect(testUI.Out).To(Say("Select an org:")) 1078 Expect(testUI.Out).To(Say("1. 1234")) 1079 Expect(testUI.Out).To(Say("2. some-org-name1")) 1080 Expect(testUI.Out).To(Say("3. some-org-name2")) 1081 Expect(testUI.Out).To(Say(`Org \(enter to skip\):`)) 1082 Expect(testUI.Out).To(Say("Select an org:")) 1083 Expect(testUI.Out).To(Say("1. 1234")) 1084 Expect(testUI.Out).To(Say("2. some-org-name1")) 1085 Expect(testUI.Out).To(Say("3. some-org-name2")) 1086 Expect(testUI.Out).To(Say(`Org \(enter to skip\):`)) 1087 }) 1088 }) 1089 }) 1090 1091 When("the user selects an org by name", func() { 1092 When("the list contains that org", func() { 1093 BeforeEach(func() { 1094 input.Write([]byte("some-org-name2\n")) 1095 }) 1096 1097 It("prompts the user to select an org", func() { 1098 Expect(testUI.Out).To(Say("Select an org:")) 1099 Expect(testUI.Out).To(Say("1. 1234")) 1100 Expect(testUI.Out).To(Say("2. some-org-name1")) 1101 Expect(testUI.Out).To(Say("3. some-org-name2")) 1102 Expect(testUI.Out).To(Say(`Org \(enter to skip\):`)) 1103 Expect(executeErr).ToNot(HaveOccurred()) 1104 }) 1105 1106 It("targets that org", func() { 1107 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1)) 1108 orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0) 1109 Expect(orgGUID).To(Equal("some-org-guid2")) 1110 Expect(orgName).To(Equal("some-org-name2")) 1111 }) 1112 }) 1113 1114 When("the org is not in the list", func() { 1115 BeforeEach(func() { 1116 input.Write([]byte("invalid-org\n")) 1117 }) 1118 1119 It("returns an error", func() { 1120 Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"})) 1121 }) 1122 1123 It("does not target the org", func() { 1124 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0)) 1125 }) 1126 }) 1127 }) 1128 1129 When("the user exits the prompt early", func() { 1130 var fakeUI *commandfakes.FakeUI 1131 1132 BeforeEach(func() { 1133 fakeUI = new(commandfakes.FakeUI) 1134 cmd.UI = fakeUI 1135 }) 1136 1137 When("the prompt returns with an EOF", func() { 1138 BeforeEach(func() { 1139 fakeUI.DisplayTextMenuReturns("", io.EOF) 1140 }) 1141 1142 It("selects no org and returns no error", func() { 1143 Expect(executeErr).ToNot(HaveOccurred()) 1144 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0)) 1145 }) 1146 }) 1147 }) 1148 1149 }) 1150 1151 When("more than 50 orgs exist", func() { 1152 BeforeEach(func() { 1153 orgs := make([]v3action.Organization, 51) 1154 for i := range orgs { 1155 orgs[i].Name = fmt.Sprintf("org%d", i+1) 1156 orgs[i].GUID = fmt.Sprintf("org-guid%d", i+1) 1157 } 1158 1159 fakeActor.GetOrganizationsReturns( 1160 orgs, 1161 v3action.Warnings{"some-org-warning-1", "some-org-warning-2"}, 1162 nil, 1163 ) 1164 }) 1165 1166 When("the user selects an org by name", func() { 1167 When("the list contains that org", func() { 1168 BeforeEach(func() { 1169 input.Write([]byte("org37\n")) 1170 }) 1171 1172 It("prompts the user to select an org", func() { 1173 Expect(testUI.Out).To(Say("There are too many options to display; please type in the name.")) 1174 Expect(testUI.Out).To(Say(`Org \(enter to skip\):`)) 1175 Expect(executeErr).ToNot(HaveOccurred()) 1176 }) 1177 1178 It("targets that org", func() { 1179 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(1)) 1180 orgGUID, orgName := fakeConfig.SetOrganizationInformationArgsForCall(0) 1181 Expect(orgGUID).To(Equal("org-guid37")) 1182 Expect(orgName).To(Equal("org37")) 1183 }) 1184 }) 1185 1186 When("the org is not in the list", func() { 1187 BeforeEach(func() { 1188 input.Write([]byte("invalid-org\n")) 1189 }) 1190 1191 It("returns an error", func() { 1192 Expect(executeErr).To(MatchError(translatableerror.OrganizationNotFoundError{Name: "invalid-org"})) 1193 }) 1194 1195 It("does not target the org", func() { 1196 Expect(fakeConfig.SetOrganizationInformationCallCount()).To(Equal(0)) 1197 }) 1198 }) 1199 }) 1200 1201 }) 1202 }) 1203 1204 When("fetching the organizations fails", func() { 1205 BeforeEach(func() { 1206 fakeActor.GetOrganizationsReturns( 1207 []v3action.Organization{}, 1208 v3action.Warnings{"some-warning-1", "some-warning-2"}, 1209 errors.New("api call failed"), 1210 ) 1211 }) 1212 1213 It("returns the error", func() { 1214 Expect(executeErr).To(MatchError("api call failed")) 1215 }) 1216 1217 It("prints all warnings", func() { 1218 Expect(testUI.Err).To(Say("some-warning-1")) 1219 Expect(testUI.Err).To(Say("some-warning-2")) 1220 }) 1221 }) 1222 }) 1223 }) 1224 1225 Describe("Targeting Space", func() { 1226 BeforeEach(func() { 1227 cmd.APIEndpoint = "example.com" 1228 cmd.Username = "some-user" 1229 cmd.Password = "some-password" 1230 fakeConfig.APIVersionReturns("3.4.5") 1231 fakeConfig.CurrentUserNameReturns("some-user", nil) 1232 }) 1233 1234 When("an org has been successfully targeted", func() { 1235 BeforeEach(func() { 1236 fakeConfig.TargetedOrganizationReturns(configv3.Organization{ 1237 GUID: "targeted-org-guid", 1238 Name: "targeted-org-name"}, 1239 ) 1240 fakeConfig.TargetedOrganizationNameReturns("targeted-org-name") 1241 }) 1242 1243 When("-s was passed", func() { 1244 BeforeEach(func() { 1245 cmd.Space = "some-space" 1246 }) 1247 1248 When("the specified space exists", func() { 1249 BeforeEach(func() { 1250 fakeActor.GetSpaceByNameAndOrganizationReturns( 1251 v3action.Space{ 1252 Name: "some-space", 1253 GUID: "some-space-guid", 1254 }, 1255 v3action.Warnings{"some-warning-1", "some-warning-2"}, 1256 nil, 1257 ) 1258 }) 1259 1260 It("targets that space", func() { 1261 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1262 spaceGUID, spaceName, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0) 1263 Expect(spaceGUID).To(Equal("some-space-guid")) 1264 Expect(spaceName).To(Equal("some-space")) 1265 Expect(allowSSH).To(BeTrue()) 1266 }) 1267 1268 It("prints all warnings", func() { 1269 Expect(testUI.Err).To(Say("some-warning-1")) 1270 Expect(testUI.Err).To(Say("some-warning-2")) 1271 }) 1272 1273 When("the space has been successfully targeted", func() { 1274 BeforeEach(func() { 1275 fakeConfig.TargetedSpaceReturns(configv3.Space{Name: "some-space"}) 1276 }) 1277 1278 It("displays that the spacce has been targeted", func() { 1279 Expect(testUI.Out).To(Say(`Space:\s+some-space`)) 1280 }) 1281 }) 1282 }) 1283 1284 When("the specified space does not exist or does not belong to the targeted org", func() { 1285 BeforeEach(func() { 1286 fakeActor.GetSpaceByNameAndOrganizationReturns( 1287 v3action.Space{}, 1288 v3action.Warnings{"some-warning-1", "some-warning-2"}, 1289 actionerror.SpaceNotFoundError{Name: "some-space"}, 1290 ) 1291 }) 1292 1293 It("returns an error", func() { 1294 Expect(executeErr).To(MatchError(actionerror.SpaceNotFoundError{Name: "some-space"})) 1295 }) 1296 1297 It("prints all warnings", func() { 1298 Expect(testUI.Err).To(Say("some-warning-1")) 1299 Expect(testUI.Err).To(Say("some-warning-2")) 1300 }) 1301 1302 It("reports that no space is targeted", func() { 1303 Expect(testUI.Out).To(Say(`Space:\s+No space targeted, use 'some-executable target -s SPACE'`)) 1304 }) 1305 }) 1306 }) 1307 1308 When("-s was not passed", func() { 1309 When("fetching the spaces for an organization succeeds", func() { 1310 When("no space exists", func() { 1311 BeforeEach(func() { 1312 fakeActor.GetOrganizationSpacesReturns( 1313 []v3action.Space{}, 1314 v3action.Warnings{}, 1315 nil, 1316 ) 1317 fakeConfig.TargetReturns("https://example.com") 1318 }) 1319 It("does not prompt the user to select a space", func() { 1320 Expect(executeErr).ToNot(HaveOccurred()) 1321 Expect(testUI.Out).ToNot(Say("Select a space:")) 1322 Expect(testUI.Out).ToNot(Say(`Space \(enter to skip\):`)) 1323 }) 1324 1325 It("displays how to target a space", func() { 1326 Expect(executeErr).ToNot(HaveOccurred()) 1327 Expect(testUI.Out).To(Say(`API endpoint:\s+https://example.com \(API version: 3.4.5\)`)) 1328 Expect(testUI.Out).To(Say(`User:\s+some-user`)) 1329 Expect(testUI.Out).To(Say("No space targeted, use '%s target -s SPACE'", binaryName)) 1330 }) 1331 }) 1332 1333 When("only one space is available", func() { 1334 BeforeEach(func() { 1335 spaces := []v3action.Space{ 1336 { 1337 GUID: "some-space-guid", 1338 Name: "some-space-name", 1339 OrganizationGUID: "targeted-org-guid", 1340 }, 1341 } 1342 1343 fakeActor.GetOrganizationSpacesReturns( 1344 spaces, 1345 v3action.Warnings{}, 1346 nil, 1347 ) 1348 1349 fakeConfig.TargetedSpaceReturns(configv3.Space{ 1350 GUID: "some-space-guid", 1351 Name: "some-space-name", 1352 }) 1353 }) 1354 1355 It("targets this space", func() { 1356 Expect(executeErr).NotTo(HaveOccurred()) 1357 1358 Expect(fakeActor.GetOrganizationSpacesCallCount()).To(Equal(1)) 1359 Expect(fakeActor.GetOrganizationSpacesArgsForCall(0)).To(Equal("targeted-org-guid")) 1360 1361 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1362 1363 firstArg, secondArg, _ := fakeConfig.SetSpaceInformationArgsForCall(0) 1364 Expect(firstArg).To(Equal("some-space-guid")) 1365 Expect(secondArg).To(Equal("some-space-name")) 1366 1367 Expect(testUI.Out).To(Say(`Targeted space some-space-name`)) 1368 Expect(testUI.Out).To(Say(`Space:\s+some-space-name`)) 1369 Expect(testUI.Out).NotTo(Say(`Space:\s+No space targeted, use 'some-executable target -s SPACE`)) 1370 }) 1371 }) 1372 1373 When("more than one space is available", func() { 1374 BeforeEach(func() { 1375 spaces := []v3action.Space{ 1376 { 1377 GUID: "some-space-guid", 1378 Name: "some-space-name", 1379 OrganizationGUID: "targeted-org-guid", 1380 }, 1381 { 1382 GUID: "some-space-guid1", 1383 Name: "some-space-name1", 1384 OrganizationGUID: "targeted-org-guid1", 1385 }, 1386 { 1387 GUID: "some-space-guid2", 1388 Name: "some-space-name2", 1389 OrganizationGUID: "targeted-org-guid2", 1390 }, 1391 { 1392 GUID: "some-space-guid3", 1393 Name: "3", 1394 OrganizationGUID: "targeted-org-guid3", 1395 }, 1396 { 1397 GUID: "some-space-guid3", 1398 Name: "100", 1399 OrganizationGUID: "targeted-org-guid3", 1400 }, 1401 } 1402 1403 fakeActor.GetOrganizationSpacesReturns( 1404 spaces, 1405 v3action.Warnings{}, 1406 nil, 1407 ) 1408 }) 1409 1410 It("displays a numbered list of spaces", func() { 1411 Expect(testUI.Out).To(Say("Select a space:")) 1412 Expect(testUI.Out).To(Say("1. some-space-name")) 1413 Expect(testUI.Out).To(Say("2. some-space-name1")) 1414 Expect(testUI.Out).To(Say("3. some-space-name2")) 1415 Expect(testUI.Out).To(Say("4. 3")) 1416 Expect(testUI.Out).To(Say("5. 100")) 1417 Expect(testUI.Out).To(Say("\n\n")) 1418 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1419 }) 1420 1421 When("the user selects a space by list position", func() { 1422 When("the position is valid", func() { 1423 BeforeEach(func() { 1424 input.Write([]byte("2\n")) 1425 }) 1426 1427 It("targets that space", func() { 1428 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1429 guid, name, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0) 1430 Expect(guid).To(Equal("some-space-guid1")) 1431 Expect(name).To(Equal("some-space-name1")) 1432 Expect(allowSSH).To(BeTrue()) 1433 Expect(executeErr).NotTo(HaveOccurred()) 1434 }) 1435 }) 1436 1437 When("the position is invalid", func() { 1438 BeforeEach(func() { 1439 input.Write([]byte("-1\n")) 1440 }) 1441 1442 It("reprompts the user", func() { 1443 Expect(testUI.Out).To(Say("Select a space:")) 1444 Expect(testUI.Out).To(Say("1. some-space-name")) 1445 Expect(testUI.Out).To(Say("2. some-space-name1")) 1446 Expect(testUI.Out).To(Say("3. some-space-name2")) 1447 Expect(testUI.Out).To(Say("4. 3")) 1448 Expect(testUI.Out).To(Say("5. 100")) 1449 Expect(testUI.Out).To(Say("\n\n")) 1450 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1451 Expect(testUI.Out).To(Say("Select a space:")) 1452 Expect(testUI.Out).To(Say("1. some-space-name")) 1453 Expect(testUI.Out).To(Say("2. some-space-name1")) 1454 Expect(testUI.Out).To(Say("3. some-space-name2")) 1455 Expect(testUI.Out).To(Say("4. 3")) 1456 Expect(testUI.Out).To(Say("5. 100")) 1457 Expect(testUI.Out).To(Say("\n\n")) 1458 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1459 }) 1460 }) 1461 }) 1462 1463 When("the user selects a space by name", func() { 1464 When("the list contains that space", func() { 1465 BeforeEach(func() { 1466 input.Write([]byte("some-space-name2\n")) 1467 }) 1468 1469 It("targets that space", func() { 1470 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1471 guid, name, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0) 1472 Expect(guid).To(Equal("some-space-guid2")) 1473 Expect(name).To(Equal("some-space-name2")) 1474 Expect(allowSSH).To(BeTrue()) 1475 Expect(executeErr).NotTo(HaveOccurred()) 1476 }) 1477 }) 1478 1479 When("the space is not in the list", func() { 1480 BeforeEach(func() { 1481 input.Write([]byte("invalid-space\n")) 1482 }) 1483 1484 It("returns an error", func() { 1485 Expect(executeErr).To(MatchError(translatableerror.SpaceNotFoundError{Name: "invalid-space"})) 1486 }) 1487 1488 It("does not target the space", func() { 1489 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(0)) 1490 }) 1491 }) 1492 1493 When("the user exits the prompt early", func() { 1494 var fakeUI *commandfakes.FakeUI 1495 1496 BeforeEach(func() { 1497 fakeUI = new(commandfakes.FakeUI) 1498 cmd.UI = fakeUI 1499 }) 1500 1501 When("the prompt returns with an EOF", func() { 1502 BeforeEach(func() { 1503 fakeUI.DisplayTextMenuReturns("", io.EOF) 1504 }) 1505 It("selects no space and returns no error", func() { 1506 Expect(executeErr).ToNot(HaveOccurred()) 1507 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(0)) 1508 }) 1509 }) 1510 1511 }) 1512 1513 }) 1514 1515 When("the user enters text which is both a space name and a digit", func() { 1516 When("the entry is a valid position", func() { 1517 BeforeEach(func() { 1518 input.Write([]byte("3\n")) 1519 }) 1520 1521 It("targets the space at the index specified", func() { 1522 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1523 guid, name, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0) 1524 Expect(guid).To(Equal("some-space-guid2")) 1525 Expect(name).To(Equal("some-space-name2")) 1526 Expect(allowSSH).To(BeTrue()) 1527 Expect(executeErr).NotTo(HaveOccurred()) 1528 }) 1529 }) 1530 1531 When("the entry is an invalid position", func() { 1532 BeforeEach(func() { 1533 input.Write([]byte("100\n")) 1534 }) 1535 1536 It("reprompts the user", func() { 1537 Expect(testUI.Out).To(Say("Select a space:")) 1538 Expect(testUI.Out).To(Say("1. some-space-name")) 1539 Expect(testUI.Out).To(Say("2. some-space-name1")) 1540 Expect(testUI.Out).To(Say("3. some-space-name2")) 1541 Expect(testUI.Out).To(Say("4. 3")) 1542 Expect(testUI.Out).To(Say("5. 100")) 1543 Expect(testUI.Out).To(Say("\n\n")) 1544 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1545 Expect(testUI.Out).To(Say("1. some-space-name")) 1546 Expect(testUI.Out).To(Say("2. some-space-name1")) 1547 Expect(testUI.Out).To(Say("3. some-space-name2")) 1548 Expect(testUI.Out).To(Say("4. 3")) 1549 Expect(testUI.Out).To(Say("5. 100")) 1550 Expect(testUI.Out).To(Say("\n\n")) 1551 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1552 }) 1553 }) 1554 }) 1555 }) 1556 1557 When("more than 50 spaces exist", func() { 1558 BeforeEach(func() { 1559 spaces := make([]v3action.Space, 51) 1560 for i := range spaces { 1561 spaces[i].Name = fmt.Sprintf("space-%d", i+1) 1562 spaces[i].GUID = fmt.Sprintf("space-guid-%d", i+1) 1563 } 1564 1565 fakeActor.GetOrganizationSpacesReturns( 1566 spaces, 1567 v3action.Warnings{}, 1568 nil, 1569 ) 1570 }) 1571 1572 It("prompts the user to select an space", func() { 1573 Expect(testUI.Out).To(Say("There are too many options to display; please type in the name.")) 1574 Expect(testUI.Out).To(Say("\n\n")) 1575 Expect(testUI.Out).To(Say(`Space \(enter to skip\):`)) 1576 }) 1577 1578 When("the user selects an space by name", func() { 1579 When("the list contains that space", func() { 1580 BeforeEach(func() { 1581 input.Write([]byte("space-37\n")) 1582 }) 1583 1584 It("targets that space", func() { 1585 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(1)) 1586 spaceGUID, spaceName, allowSSH := fakeConfig.SetSpaceInformationArgsForCall(0) 1587 Expect(spaceGUID).To(Equal("space-guid-37")) 1588 Expect(spaceName).To(Equal("space-37")) 1589 Expect(allowSSH).To(BeTrue()) 1590 }) 1591 }) 1592 1593 When("the name is a valid list position, but it does not match a space name", func() { 1594 BeforeEach(func() { 1595 input.Write([]byte("31\n")) 1596 }) 1597 1598 It("returns an error", func() { 1599 Expect(executeErr).To(MatchError(translatableerror.SpaceNotFoundError{Name: "31"})) 1600 }) 1601 1602 It("does not target the space", func() { 1603 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(0)) 1604 }) 1605 1606 }) 1607 1608 When("the space is not in the list", func() { 1609 BeforeEach(func() { 1610 input.Write([]byte("invalid-space\n")) 1611 }) 1612 1613 It("returns an error", func() { 1614 Expect(executeErr).To(MatchError(translatableerror.SpaceNotFoundError{Name: "invalid-space"})) 1615 }) 1616 1617 It("does not target the space", func() { 1618 Expect(fakeConfig.SetSpaceInformationCallCount()).To(Equal(0)) 1619 }) 1620 }) 1621 }) 1622 1623 }) 1624 }) 1625 1626 When("fetching the spaces for an organization fails", func() { 1627 BeforeEach(func() { 1628 fakeActor.GetOrganizationSpacesReturns( 1629 []v3action.Space{}, 1630 v3action.Warnings{"some-warning-1", "some-warning-2"}, 1631 errors.New("fetching spaces failed"), 1632 ) 1633 }) 1634 1635 It("returns an error", func() { 1636 Expect(executeErr).To(MatchError("fetching spaces failed")) 1637 }) 1638 1639 It("returns all warnings", func() { 1640 Expect(testUI.Err).To(Say("some-warning-1")) 1641 Expect(testUI.Err).To(Say("some-warning-2")) 1642 }) 1643 }) 1644 }) 1645 }) 1646 }) 1647 1648 Describe("Targeting Org and Space", func() { 1649 When("the user selects an org and space by list positions", func() { 1650 BeforeEach(func() { 1651 fakeConfig.TargetedOrganizationReturns(configv3.Organization{ 1652 GUID: "targeted-org-guid1"}) 1653 fakeConfig.TargetedOrganizationNameReturns("targeted-org-name") 1654 1655 spaces := []v3action.Space{ 1656 { 1657 GUID: "some-space-guid", 1658 Name: "some-space-name", 1659 OrganizationGUID: "targeted-org-guid", 1660 }, 1661 { 1662 GUID: "some-space-guid1", 1663 Name: "some-space-name1", 1664 OrganizationGUID: "targeted-org-guid1", 1665 }, 1666 } 1667 1668 fakeActor.GetOrganizationSpacesReturns( 1669 spaces, 1670 v3action.Warnings{}, 1671 nil, 1672 ) 1673 1674 input.Write([]byte("2\n")) 1675 }) 1676 1677 It("outputs targeted org", func() { 1678 Expect(testUI.Out).To(Say("Targeted org targeted-org-name\n\nSelect a space"), 1679 "Expect an empty line between 'Targeted org' and 'Select a space' messages") 1680 }) 1681 }) 1682 }) 1683 })