github.com/liamawhite/cli-with-i18n@v6.32.1-0.20171122084555-dede0a5c3448+incompatible/command/v2/restage_command_test.go (about) 1 package v2_test 2 3 import ( 4 "errors" 5 "time" 6 7 "github.com/cloudfoundry/bytefmt" 8 "github.com/liamawhite/cli-with-i18n/actor/actionerror" 9 "github.com/liamawhite/cli-with-i18n/actor/sharedaction" 10 "github.com/liamawhite/cli-with-i18n/actor/v2action" 11 "github.com/liamawhite/cli-with-i18n/api/cloudcontroller/ccv2" 12 "github.com/liamawhite/cli-with-i18n/command/commandfakes" 13 "github.com/liamawhite/cli-with-i18n/command/translatableerror" 14 . "github.com/liamawhite/cli-with-i18n/command/v2" 15 "github.com/liamawhite/cli-with-i18n/command/v2/v2fakes" 16 "github.com/liamawhite/cli-with-i18n/types" 17 "github.com/liamawhite/cli-with-i18n/util/configv3" 18 "github.com/liamawhite/cli-with-i18n/util/ui" 19 . "github.com/onsi/ginkgo" 20 . "github.com/onsi/gomega" 21 . "github.com/onsi/gomega/gbytes" 22 ) 23 24 var _ = Describe("Restage Command", func() { 25 var ( 26 cmd RestageCommand 27 testUI *ui.UI 28 fakeConfig *commandfakes.FakeConfig 29 fakeSharedActor *commandfakes.FakeSharedActor 30 fakeActor *v2fakes.FakeRestageActor 31 binaryName string 32 executeErr error 33 ) 34 35 BeforeEach(func() { 36 testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) 37 fakeConfig = new(commandfakes.FakeConfig) 38 fakeSharedActor = new(commandfakes.FakeSharedActor) 39 fakeActor = new(v2fakes.FakeRestageActor) 40 41 cmd = RestageCommand{ 42 UI: testUI, 43 Config: fakeConfig, 44 SharedActor: fakeSharedActor, 45 Actor: fakeActor, 46 } 47 48 cmd.RequiredArgs.AppName = "some-app" 49 50 binaryName = "faceman" 51 fakeConfig.BinaryNameReturns(binaryName) 52 53 var err error 54 testUI.TimezoneLocation, err = time.LoadLocation("America/Los_Angeles") 55 Expect(err).NotTo(HaveOccurred()) 56 57 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 58 messages := make(chan *v2action.LogMessage) 59 logErrs := make(chan error) 60 appState := make(chan v2action.ApplicationStateChange) 61 warnings := make(chan string) 62 errs := make(chan error) 63 64 go func() { 65 appState <- v2action.ApplicationStateStaging 66 appState <- v2action.ApplicationStateStarting 67 close(messages) 68 close(logErrs) 69 close(appState) 70 close(warnings) 71 close(errs) 72 }() 73 74 return messages, logErrs, appState, warnings, errs 75 } 76 }) 77 78 JustBeforeEach(func() { 79 executeErr = cmd.Execute(nil) 80 }) 81 82 Context("when checking target fails", func() { 83 BeforeEach(func() { 84 fakeSharedActor.CheckTargetReturns(sharedaction.NotLoggedInError{BinaryName: binaryName}) 85 }) 86 87 It("returns an error if the check fails", func() { 88 Expect(executeErr).To(MatchError(translatableerror.NotLoggedInError{BinaryName: "faceman"})) 89 90 Expect(fakeSharedActor.CheckTargetCallCount()).To(Equal(1)) 91 _, checkTargetedOrg, checkTargetedSpace := fakeSharedActor.CheckTargetArgsForCall(0) 92 Expect(checkTargetedOrg).To(BeTrue()) 93 Expect(checkTargetedSpace).To(BeTrue()) 94 }) 95 }) 96 97 Context("when the user is logged in, and org and space are targeted", func() { 98 BeforeEach(func() { 99 fakeConfig.HasTargetedOrganizationReturns(true) 100 fakeConfig.TargetedOrganizationReturns(configv3.Organization{Name: "some-org"}) 101 fakeConfig.HasTargetedSpaceReturns(true) 102 fakeConfig.TargetedSpaceReturns(configv3.Space{ 103 GUID: "some-space-guid", 104 Name: "some-space"}) 105 fakeConfig.CurrentUserReturns( 106 configv3.User{Name: "some-user"}, 107 nil) 108 }) 109 110 Context("when getting the current user returns an error", func() { 111 var expectedErr error 112 113 BeforeEach(func() { 114 expectedErr = errors.New("getting current user error") 115 fakeConfig.CurrentUserReturns( 116 configv3.User{}, 117 expectedErr) 118 }) 119 120 It("returns the error", func() { 121 Expect(executeErr).To(MatchError(expectedErr)) 122 }) 123 }) 124 125 It("displays flavor text", func() { 126 Expect(testUI.Out).To(Say("Restaging app some-app in org some-org / space some-space as some-user...")) 127 }) 128 129 Context("when the app does *not* exists", func() { 130 BeforeEach(func() { 131 fakeActor.GetApplicationByNameAndSpaceReturns( 132 v2action.Application{}, 133 v2action.Warnings{"warning-1", "warning-2"}, 134 actionerror.ApplicationNotFoundError{Name: "some-app"}, 135 ) 136 }) 137 138 It("returns back an error", func() { 139 Expect(executeErr).To(MatchError(translatableerror.ApplicationNotFoundError{Name: "some-app"})) 140 141 Expect(testUI.Err).To(Say("warning-1")) 142 Expect(testUI.Err).To(Say("warning-2")) 143 }) 144 }) 145 146 Context("when the app exists", func() { 147 BeforeEach(func() { 148 fakeActor.GetApplicationByNameAndSpaceReturns( 149 v2action.Application{GUID: "app-guid"}, 150 v2action.Warnings{"warning-1", "warning-2"}, 151 nil, 152 ) 153 }) 154 155 It("stages the app", func() { 156 Expect(executeErr).ToNot(HaveOccurred()) 157 158 Expect(testUI.Err).To(Say("warning-1")) 159 Expect(testUI.Err).To(Say("warning-2")) 160 161 Expect(fakeActor.RestageApplicationCallCount()).To(Equal(1)) 162 app, _, config := fakeActor.RestageApplicationArgsForCall(0) 163 Expect(app.GUID).To(Equal("app-guid")) 164 Expect(config).To(Equal(fakeConfig)) 165 }) 166 167 Context("when passed an appStarting message", func() { 168 BeforeEach(func() { 169 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 170 messages := make(chan *v2action.LogMessage) 171 logErrs := make(chan error) 172 appState := make(chan v2action.ApplicationStateChange) 173 warnings := make(chan string) 174 errs := make(chan error) 175 176 go func() { 177 messages <- v2action.NewLogMessage("log message 1", 1, time.Unix(0, 0), "STG", "1") 178 messages <- v2action.NewLogMessage("log message 2", 1, time.Unix(0, 0), "STG", "1") 179 appState <- v2action.ApplicationStateStaging 180 appState <- v2action.ApplicationStateStarting 181 close(messages) 182 close(logErrs) 183 close(appState) 184 close(warnings) 185 close(errs) 186 }() 187 188 return messages, logErrs, appState, warnings, errs 189 } 190 }) 191 192 It("displays the log", func() { 193 Expect(executeErr).ToNot(HaveOccurred()) 194 Expect(testUI.Out).To(Say("log message 1")) 195 Expect(testUI.Out).To(Say("log message 2")) 196 Expect(testUI.Out).To(Say("Waiting for app to start...")) 197 }) 198 }) 199 200 Context("when passed a log message", func() { 201 BeforeEach(func() { 202 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 203 messages := make(chan *v2action.LogMessage) 204 logErrs := make(chan error) 205 appState := make(chan v2action.ApplicationStateChange) 206 warnings := make(chan string) 207 errs := make(chan error) 208 209 go func() { 210 messages <- v2action.NewLogMessage("log message 1", 1, time.Unix(0, 0), "STG", "1") 211 messages <- v2action.NewLogMessage("log message 2", 1, time.Unix(0, 0), "STG", "1") 212 messages <- v2action.NewLogMessage("log message 3", 1, time.Unix(0, 0), "Something else", "1") 213 close(messages) 214 close(logErrs) 215 close(appState) 216 close(warnings) 217 close(errs) 218 }() 219 220 return messages, logErrs, appState, warnings, errs 221 } 222 }) 223 224 It("displays the log", func() { 225 Expect(executeErr).ToNot(HaveOccurred()) 226 Expect(testUI.Out).To(Say("log message 1")) 227 Expect(testUI.Out).To(Say("log message 2")) 228 Expect(testUI.Out).ToNot(Say("log message 3")) 229 }) 230 }) 231 232 Context("when passed an log err", func() { 233 Context("NOAA connection times out/closes", func() { 234 BeforeEach(func() { 235 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 236 messages := make(chan *v2action.LogMessage) 237 logErrs := make(chan error) 238 appState := make(chan v2action.ApplicationStateChange) 239 warnings := make(chan string) 240 errs := make(chan error) 241 242 go func() { 243 messages <- v2action.NewLogMessage("log message 1", 1, time.Unix(0, 0), "STG", "1") 244 messages <- v2action.NewLogMessage("log message 2", 1, time.Unix(0, 0), "STG", "1") 245 messages <- v2action.NewLogMessage("log message 3", 1, time.Unix(0, 0), "STG", "1") 246 logErrs <- v2action.NOAATimeoutError{} 247 close(messages) 248 close(logErrs) 249 close(appState) 250 close(warnings) 251 close(errs) 252 }() 253 254 return messages, logErrs, appState, warnings, errs 255 } 256 257 applicationSummary := v2action.ApplicationSummary{ 258 Application: v2action.Application{ 259 Name: "some-app", 260 GUID: "some-app-guid", 261 Instances: types.NullInt{Value: 3, IsSet: true}, 262 Memory: 128, 263 PackageUpdatedAt: time.Unix(0, 0), 264 DetectedBuildpack: types.FilteredString{IsSet: true, Value: "some-buildpack"}, 265 State: "STARTED", 266 DetectedStartCommand: types.FilteredString{IsSet: true, Value: "some start command"}, 267 }, 268 Stack: v2action.Stack{ 269 Name: "potatos", 270 }, 271 Routes: []v2action.Route{ 272 { 273 Host: "banana", 274 Domain: v2action.Domain{ 275 Name: "fruit.com", 276 }, 277 Path: "/hi", 278 }, 279 { 280 Domain: v2action.Domain{ 281 Name: "foobar.com", 282 }, 283 Port: types.NullInt{IsSet: true, Value: 13}, 284 }, 285 }, 286 } 287 warnings := []string{"app-summary-warning"} 288 289 applicationSummary.RunningInstances = []v2action.ApplicationInstanceWithStats{} 290 291 fakeActor.GetApplicationSummaryByNameAndSpaceReturns(applicationSummary, warnings, nil) 292 }) 293 294 It("displays a warning and continues until app has started", func() { 295 Expect(executeErr).To(BeNil()) 296 Expect(testUI.Out).To(Say("message 1")) 297 Expect(testUI.Out).To(Say("message 2")) 298 Expect(testUI.Out).To(Say("message 3")) 299 Expect(testUI.Err).To(Say("timeout connecting to log server, no log will be shown")) 300 Expect(testUI.Out).To(Say("name:\\s+some-app")) 301 }) 302 }) 303 304 Context("an unexpected error occurs", func() { 305 var expectedErr error 306 307 BeforeEach(func() { 308 expectedErr = errors.New("err log message") 309 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 310 messages := make(chan *v2action.LogMessage) 311 logErrs := make(chan error) 312 appState := make(chan v2action.ApplicationStateChange) 313 warnings := make(chan string) 314 errs := make(chan error) 315 316 go func() { 317 logErrs <- expectedErr 318 close(messages) 319 close(logErrs) 320 close(appState) 321 close(warnings) 322 close(errs) 323 }() 324 325 return messages, logErrs, appState, warnings, errs 326 } 327 }) 328 329 It("displays the error and continues to poll", func() { 330 Expect(executeErr).NotTo(HaveOccurred()) 331 Expect(testUI.Err).To(Say(expectedErr.Error())) 332 }) 333 }) 334 }) 335 336 Context("when passed a warning", func() { 337 Context("while NOAA is still logging", func() { 338 BeforeEach(func() { 339 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 340 messages := make(chan *v2action.LogMessage) 341 logErrs := make(chan error) 342 appState := make(chan v2action.ApplicationStateChange) 343 warnings := make(chan string) 344 errs := make(chan error) 345 346 go func() { 347 warnings <- "warning 1" 348 warnings <- "warning 2" 349 close(messages) 350 close(logErrs) 351 close(appState) 352 close(warnings) 353 close(errs) 354 }() 355 356 return messages, logErrs, appState, warnings, errs 357 } 358 }) 359 360 It("displays the warnings to STDERR", func() { 361 Expect(executeErr).ToNot(HaveOccurred()) 362 Expect(testUI.Err).To(Say("warning 1")) 363 Expect(testUI.Err).To(Say("warning 2")) 364 }) 365 }) 366 367 Context("while NOAA is no longer logging", func() { 368 BeforeEach(func() { 369 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 370 messages := make(chan *v2action.LogMessage) 371 logErrs := make(chan error) 372 appState := make(chan v2action.ApplicationStateChange) 373 warnings := make(chan string) 374 errs := make(chan error) 375 376 go func() { 377 warnings <- "warning 1" 378 warnings <- "warning 2" 379 logErrs <- v2action.NOAATimeoutError{} 380 close(messages) 381 close(logErrs) 382 warnings <- "warning 3" 383 warnings <- "warning 4" 384 close(appState) 385 close(warnings) 386 close(errs) 387 }() 388 389 return messages, logErrs, appState, warnings, errs 390 } 391 }) 392 393 It("displays the warnings to STDERR", func() { 394 Expect(executeErr).ToNot(HaveOccurred()) 395 Expect(testUI.Err).To(Say("warning 1")) 396 Expect(testUI.Err).To(Say("warning 2")) 397 Expect(testUI.Err).To(Say("timeout connecting to log server, no log will be shown")) 398 Expect(testUI.Err).To(Say("warning 3")) 399 Expect(testUI.Err).To(Say("warning 4")) 400 }) 401 }) 402 }) 403 404 Context("when passed an API err", func() { 405 var apiErr error 406 407 BeforeEach(func() { 408 fakeActor.RestageApplicationStub = func(app v2action.Application, client v2action.NOAAClient, config v2action.Config) (<-chan *v2action.LogMessage, <-chan error, <-chan v2action.ApplicationStateChange, <-chan string, <-chan error) { 409 messages := make(chan *v2action.LogMessage) 410 logErrs := make(chan error) 411 appState := make(chan v2action.ApplicationStateChange) 412 warnings := make(chan string) 413 errs := make(chan error) 414 415 go func() { 416 errs <- apiErr 417 close(messages) 418 close(logErrs) 419 close(appState) 420 close(warnings) 421 close(errs) 422 }() 423 424 return messages, logErrs, appState, warnings, errs 425 } 426 }) 427 428 Context("an unexpected error", func() { 429 BeforeEach(func() { 430 apiErr = errors.New("err log message") 431 }) 432 433 It("stops logging and returns the error", func() { 434 Expect(executeErr).To(MatchError(apiErr)) 435 }) 436 }) 437 438 Context("staging failed", func() { 439 BeforeEach(func() { 440 apiErr = actionerror.StagingFailedError{Reason: "Something, but not nothing"} 441 }) 442 443 It("stops logging and returns StagingFailedError", func() { 444 Expect(executeErr).To(MatchError(translatableerror.StagingFailedError{Message: "Something, but not nothing"})) 445 }) 446 }) 447 448 Context("staging timed out", func() { 449 BeforeEach(func() { 450 apiErr = actionerror.StagingTimeoutError{Name: "some-app", Timeout: time.Nanosecond} 451 }) 452 453 It("stops logging and returns StagingTimeoutError", func() { 454 Expect(executeErr).To(MatchError(translatableerror.StagingTimeoutError{AppName: "some-app", Timeout: time.Nanosecond})) 455 }) 456 }) 457 458 Context("when the app instance crashes", func() { 459 BeforeEach(func() { 460 apiErr = actionerror.ApplicationInstanceCrashedError{Name: "some-app"} 461 }) 462 463 It("stops logging and returns UnsuccessfulStartError", func() { 464 Expect(executeErr).To(MatchError(translatableerror.UnsuccessfulStartError{AppName: "some-app", BinaryName: "faceman"})) 465 }) 466 }) 467 468 Context("when the app instance flaps", func() { 469 BeforeEach(func() { 470 apiErr = actionerror.ApplicationInstanceFlappingError{Name: "some-app"} 471 }) 472 473 It("stops logging and returns UnsuccessfulStartError", func() { 474 Expect(executeErr).To(MatchError(translatableerror.UnsuccessfulStartError{AppName: "some-app", BinaryName: "faceman"})) 475 }) 476 }) 477 478 Context("starting timeout", func() { 479 BeforeEach(func() { 480 apiErr = actionerror.StartupTimeoutError{Name: "some-app"} 481 }) 482 483 It("stops logging and returns StartupTimeoutError", func() { 484 Expect(executeErr).To(MatchError(translatableerror.StartupTimeoutError{AppName: "some-app", BinaryName: "faceman"})) 485 }) 486 }) 487 }) 488 489 Context("when the app finishes starting", func() { 490 var ( 491 applicationSummary v2action.ApplicationSummary 492 warnings []string 493 ) 494 495 BeforeEach(func() { 496 applicationSummary = v2action.ApplicationSummary{ 497 Application: v2action.Application{ 498 Name: "some-app", 499 GUID: "some-app-guid", 500 Instances: types.NullInt{Value: 3, IsSet: true}, 501 Memory: 128, 502 PackageUpdatedAt: time.Unix(0, 0), 503 DetectedBuildpack: types.FilteredString{IsSet: true, Value: "some-buildpack"}, 504 State: "STARTED", 505 DetectedStartCommand: types.FilteredString{IsSet: true, Value: "some start command"}, 506 }, 507 IsolationSegment: "some-isolation-segment", 508 Stack: v2action.Stack{ 509 Name: "potatos", 510 }, 511 Routes: []v2action.Route{ 512 { 513 Host: "banana", 514 Domain: v2action.Domain{ 515 Name: "fruit.com", 516 }, 517 Path: "/hi", 518 }, 519 { 520 Domain: v2action.Domain{ 521 Name: "foobar.com", 522 }, 523 Port: types.NullInt{IsSet: true, Value: 13}, 524 }, 525 }, 526 } 527 warnings = []string{"app-summary-warning"} 528 529 applicationSummary.RunningInstances = []v2action.ApplicationInstanceWithStats{ 530 { 531 ID: 0, 532 State: v2action.ApplicationInstanceState(ccv2.ApplicationInstanceRunning), 533 Since: 1403140717.984577, 534 CPU: 0.73, 535 Disk: 50 * bytefmt.MEGABYTE, 536 DiskQuota: 2048 * bytefmt.MEGABYTE, 537 Memory: 100 * bytefmt.MEGABYTE, 538 MemoryQuota: 128 * bytefmt.MEGABYTE, 539 Details: "info from the backend", 540 }, 541 } 542 fakeActor.GetApplicationSummaryByNameAndSpaceReturns(applicationSummary, warnings, nil) 543 }) 544 545 It("displays the app summary with isolation segments as well as warnings", func() { 546 Expect(executeErr).ToNot(HaveOccurred()) 547 Expect(testUI.Out).To(Say("name:\\s+some-app")) 548 Expect(testUI.Out).To(Say("requested state:\\s+started")) 549 Expect(testUI.Out).To(Say("instances:\\s+1\\/3")) 550 Expect(testUI.Out).To(Say("isolation segment:\\s+some-isolation-segment")) 551 Expect(testUI.Out).To(Say("usage:\\s+128M x 3 instances")) 552 Expect(testUI.Out).To(Say("routes:\\s+banana.fruit.com/hi, foobar.com:13")) 553 Expect(testUI.Out).To(Say("last uploaded:\\s+\\w{3} [0-3]\\d \\w{3} [0-2]\\d:[0-5]\\d:[0-5]\\d \\w+ \\d{4}")) 554 Expect(testUI.Out).To(Say("stack:\\s+potatos")) 555 Expect(testUI.Out).To(Say("buildpack:\\s+some-buildpack")) 556 Expect(testUI.Out).To(Say("start command:\\s+some start command")) 557 558 Expect(testUI.Err).To(Say("app-summary-warning")) 559 }) 560 561 It("should display the instance table", func() { 562 Expect(executeErr).ToNot(HaveOccurred()) 563 Expect(testUI.Out).To(Say("state\\s+since\\s+cpu\\s+memory\\s+disk")) 564 Expect(testUI.Out).To(Say(`#0\s+running\s+2014-06-19T01:18:37Z\s+73.0%\s+100M of 128M\s+50M of 2G\s+info from the backend`)) 565 }) 566 }) 567 }) 568 }) 569 })