github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/terminal/ui_test.go (about) 1 package terminal_test 2 3 import ( 4 "errors" 5 "io" 6 "os" 7 "strings" 8 9 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 10 "code.cloudfoundry.org/cli/cf/i18n" 11 "code.cloudfoundry.org/cli/cf/models" 12 "code.cloudfoundry.org/cli/cf/trace/tracefakes" 13 "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" 14 testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" 15 io_helpers "code.cloudfoundry.org/cli/cf/util/testhelpers/io" 16 newUI "code.cloudfoundry.org/cli/util/ui" 17 18 . "code.cloudfoundry.org/cli/cf/terminal" 19 "code.cloudfoundry.org/cli/cf/trace" 20 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 21 . "github.com/onsi/ginkgo" 22 . "github.com/onsi/gomega" 23 "github.com/onsi/gomega/gbytes" 24 ) 25 26 type readFailer struct{} 27 28 func (r readFailer) Read(p []byte) (n int, err error) { 29 return 0, errors.New("read failer always fails") 30 } 31 32 var _ = Describe("UI", func() { 33 var fakeLogger *tracefakes.FakePrinter 34 BeforeEach(func() { 35 fakeLogger = new(tracefakes.FakePrinter) 36 }) 37 38 Describe("Printing message to stdout with PrintCapturingNoOutput", func() { 39 It("prints strings without using the TeePrinter", func() { 40 bucket := gbytes.NewBuffer() 41 42 printer := NewTeePrinter(os.Stdout) 43 printer.SetOutputBucket(bucket) 44 45 io_helpers.SimulateStdin("", func(reader io.Reader) { 46 output := io_helpers.CaptureOutput(func() { 47 ui := NewUI(reader, os.Stdout, printer, fakeLogger) 48 ui.PrintCapturingNoOutput("Hello") 49 }) 50 51 Expect("Hello").To(Equal(strings.Join(output, ""))) 52 Expect(bucket.Contents()).To(HaveLen(0)) 53 }) 54 }) 55 }) 56 57 Describe("Printing message to stdout with Say", func() { 58 It("prints strings", func() { 59 io_helpers.SimulateStdin("", func(reader io.Reader) { 60 output := io_helpers.CaptureOutput(func() { 61 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 62 ui.Say("Hello") 63 }) 64 65 Expect("Hello").To(Equal(strings.Join(output, ""))) 66 }) 67 }) 68 69 It("prints formatted strings", func() { 70 io_helpers.SimulateStdin("", func(reader io.Reader) { 71 output := io_helpers.CaptureOutput(func() { 72 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 73 ui.Say("Hello %s", "World!") 74 }) 75 76 Expect("Hello World!").To(Equal(strings.Join(output, ""))) 77 }) 78 }) 79 80 It("does not format strings when provided no args", func() { 81 output := io_helpers.CaptureOutput(func() { 82 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 83 ui.Say("Hello %s World!") // whoops 84 }) 85 86 Expect(strings.Join(output, "")).To(Equal("Hello %s World!")) 87 }) 88 }) 89 90 Describe("Asking user for input", func() { 91 It("allows string with whitespaces", func() { 92 _ = io_helpers.CaptureOutput(func() { 93 io_helpers.SimulateStdin("foo bar\n", func(reader io.Reader) { 94 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 95 Expect(ui.Ask("?")).To(Equal("foo bar")) 96 }) 97 }) 98 }) 99 100 When("reading from stdin fails", func() { 101 It("returns empty string", func() { 102 _ = io_helpers.CaptureOutput(func() { 103 ui := NewUI(&readFailer{}, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 104 Expect(ui.Ask("?")).To(Equal("")) 105 }) 106 }) 107 }) 108 109 When("a multi-line file is piped to the cli", func() { 110 It("consumes one line each prompt", func() { 111 _ = io_helpers.CaptureOutput(func() { 112 io_helpers.SimulateStdin("foo\nbar\nbat\n", func(reader io.Reader) { 113 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 114 Expect(ui.Ask("?")).To(Equal("foo")) 115 Expect(ui.Ask("?")).To(Equal("bar")) 116 Expect(ui.Ask("?")).To(Equal("bat")) 117 }) 118 }) 119 }) 120 }) 121 122 It("always outputs the prompt, even when output is disabled", func() { 123 output := io_helpers.CaptureOutput(func() { 124 io_helpers.SimulateStdin("things are great\n", func(reader io.Reader) { 125 printer := NewTeePrinter(os.Stdout) 126 printer.DisableTerminalOutput(true) 127 ui := NewUI(reader, os.Stdout, printer, fakeLogger) 128 ui.Ask("You like things?") 129 }) 130 }) 131 Expect(strings.Join(output, "")).To(ContainSubstring("You like things?")) 132 }) 133 }) 134 135 Describe("Confirming user input", func() { 136 It("treats 'y' as an affirmative confirmation", func() { 137 io_helpers.SimulateStdin("y\n", func(reader io.Reader) { 138 out := io_helpers.CaptureOutput(func() { 139 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 140 Expect(ui.Confirm("Hello World?")).To(BeTrue()) 141 }) 142 143 Expect(out).To(ContainSubstrings([]string{"Hello World?"})) 144 }) 145 }) 146 147 It("treats 'yes' as an affirmative confirmation when default language is not en_US", func() { 148 oldLang := os.Getenv("LC_ALL") 149 defer os.Setenv("LC_ALL", oldLang) 150 151 oldT := i18n.T 152 defer func() { 153 i18n.T = oldT 154 }() 155 156 os.Setenv("LC_ALL", "fr_FR") 157 158 config := configuration.NewRepositoryWithDefaults() 159 i18n.T = i18n.Init(config) 160 161 io_helpers.SimulateStdin("yes\n", func(reader io.Reader) { 162 out := io_helpers.CaptureOutput(func() { 163 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 164 Expect(ui.Confirm("Hello World?")).To(BeTrue()) 165 }) 166 Expect(out).To(ContainSubstrings([]string{"Hello World?"})) 167 }) 168 }) 169 170 It("treats 'yes' as an affirmative confirmation", func() { 171 io_helpers.SimulateStdin("yes\n", func(reader io.Reader) { 172 out := io_helpers.CaptureOutput(func() { 173 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 174 Expect(ui.Confirm("Hello World?")).To(BeTrue()) 175 }) 176 177 Expect(out).To(ContainSubstrings([]string{"Hello World?"})) 178 }) 179 }) 180 181 It("treats other input as a negative confirmation", func() { 182 io_helpers.SimulateStdin("wat\n", func(reader io.Reader) { 183 out := io_helpers.CaptureOutput(func() { 184 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 185 Expect(ui.Confirm("Hello World?")).To(BeFalse()) 186 }) 187 188 Expect(out).To(ContainSubstrings([]string{"Hello World?"})) 189 }) 190 }) 191 }) 192 193 Describe("Confirming deletion", func() { 194 It("formats a nice output string with exactly one prompt", func() { 195 io_helpers.SimulateStdin("y\n", func(reader io.Reader) { 196 out := io_helpers.CaptureOutput(func() { 197 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 198 Expect(ui.ConfirmDelete("fizzbuzz", "bizzbump")).To(BeTrue()) 199 }) 200 201 Expect(out).To(ContainSubstrings([]string{ 202 "Really delete the fizzbuzz", 203 "bizzbump", 204 "?> ", 205 })) 206 }) 207 }) 208 209 It("treats 'yes' as an affirmative confirmation", func() { 210 io_helpers.SimulateStdin("yes\n", func(reader io.Reader) { 211 out := io_helpers.CaptureOutput(func() { 212 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 213 Expect(ui.ConfirmDelete("modelType", "modelName")).To(BeTrue()) 214 }) 215 216 Expect(out).To(ContainSubstrings([]string{"modelType modelName"})) 217 }) 218 }) 219 220 It("treats other input as a negative confirmation and warns the user", func() { 221 io_helpers.SimulateStdin("wat\n", func(reader io.Reader) { 222 out := io_helpers.CaptureOutput(func() { 223 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 224 Expect(ui.ConfirmDelete("modelType", "modelName")).To(BeFalse()) 225 }) 226 227 Expect(out).To(ContainSubstrings([]string{"Delete cancelled"})) 228 }) 229 }) 230 }) 231 232 Describe("Confirming deletion with associations", func() { 233 It("warns the user that associated objects will also be deleted", func() { 234 io_helpers.SimulateStdin("wat\n", func(reader io.Reader) { 235 out := io_helpers.CaptureOutput(func() { 236 ui := NewUI(reader, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 237 Expect(ui.ConfirmDeleteWithAssociations("modelType", "modelName")).To(BeFalse()) 238 }) 239 240 Expect(out).To(ContainSubstrings([]string{"Delete cancelled"})) 241 }) 242 }) 243 }) 244 245 Context("when user is not logged in", func() { 246 var config coreconfig.Reader 247 248 BeforeEach(func() { 249 config = testconfig.NewRepository() 250 }) 251 252 It("prompts the user to login", func() { 253 output := io_helpers.CaptureOutput(func() { 254 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 255 ui.ShowConfiguration(config) 256 }) 257 258 Expect(output).ToNot(ContainSubstrings([]string{"API endpoint:"})) 259 Expect(output).To(ContainSubstrings([]string{"Not logged in", "Use", "log in"})) 260 }) 261 }) 262 263 Context("when an api endpoint is set and the user logged in", func() { 264 var config coreconfig.ReadWriter 265 266 BeforeEach(func() { 267 accessToken := coreconfig.TokenInfo{ 268 UserGUID: "my-user-guid", 269 Username: "my-user", 270 Email: "my-user-email", 271 } 272 config = testconfig.NewRepositoryWithAccessToken(accessToken) 273 config.SetAPIEndpoint("https://test.example.org") 274 config.SetAPIVersion("☃☃☃") 275 }) 276 277 Describe("tells the user what is set in the config", func() { 278 var output []string 279 280 JustBeforeEach(func() { 281 output = io_helpers.CaptureOutput(func() { 282 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 283 ui.ShowConfiguration(config) 284 }) 285 }) 286 287 It("tells the user which api endpoint is set", func() { 288 Expect(output).To(ContainSubstrings([]string{"API endpoint:", "https://test.example.org"})) 289 }) 290 291 It("tells the user the api version", func() { 292 Expect(output).To(ContainSubstrings([]string{"API version:", "☃☃☃"})) 293 }) 294 295 It("tells the user which user is logged in", func() { 296 Expect(output).To(ContainSubstrings([]string{"User:", "my-user-email"})) 297 }) 298 299 Context("when an org is targeted", func() { 300 BeforeEach(func() { 301 config.SetOrganizationFields(models.OrganizationFields{ 302 Name: "org-name", 303 GUID: "org-guid", 304 }) 305 }) 306 307 It("tells the user which org is targeted", func() { 308 Expect(output).To(ContainSubstrings([]string{"Org:", "org-name"})) 309 }) 310 }) 311 312 Context("when a space is targeted", func() { 313 BeforeEach(func() { 314 config.SetSpaceFields(models.SpaceFields{ 315 Name: "my-space", 316 GUID: "space-guid", 317 }) 318 }) 319 320 It("tells the user which space is targeted", func() { 321 Expect(output).To(ContainSubstrings([]string{"Space:", "my-space"})) 322 }) 323 }) 324 }) 325 326 It("prompts the user to target an org and space when no org or space is targeted", func() { 327 output := io_helpers.CaptureOutput(func() { 328 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 329 ui.ShowConfiguration(config) 330 }) 331 332 Expect(output).To(ContainSubstrings([]string{"No", "org", "space", "targeted", "-o ORG", "-s SPACE"})) 333 }) 334 335 It("prompts the user to target an org when no org is targeted", func() { 336 sf := models.SpaceFields{} 337 sf.GUID = "guid" 338 sf.Name = "name" 339 340 output := io_helpers.CaptureOutput(func() { 341 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 342 ui.ShowConfiguration(config) 343 }) 344 345 Expect(output).To(ContainSubstrings([]string{"No", "org", "targeted", "-o ORG"})) 346 }) 347 348 It("prompts the user to target a space when no space is targeted", func() { 349 of := models.OrganizationFields{} 350 of.GUID = "of-guid" 351 of.Name = "of-name" 352 353 output := io_helpers.CaptureOutput(func() { 354 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 355 ui.ShowConfiguration(config) 356 }) 357 358 Expect(output).To(ContainSubstrings([]string{"No", "space", "targeted", "-s SPACE"})) 359 }) 360 }) 361 362 Describe("failing", func() { 363 Context("when 'T' func is not initialized", func() { 364 var t newUI.TranslateFunc 365 BeforeEach(func() { 366 t = i18n.T 367 i18n.T = nil 368 }) 369 370 AfterEach(func() { 371 i18n.T = t 372 }) 373 374 It("does not duplicate output if logger is set to stdout", func() { 375 output := io_helpers.CaptureOutput(func() { 376 logger := trace.NewWriterPrinter(os.Stdout, true) 377 NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), logger).Failed("this should print only once") 378 }) 379 380 Expect(output).To(HaveLen(3)) 381 Expect(output[0]).To(Equal("FAILED")) 382 Expect(output[1]).To(Equal("this should print only once")) 383 Expect(output[2]).To(Equal("")) 384 }) 385 }) 386 387 Context("when 'T' func is initialized", func() { 388 It("does not duplicate output if logger is set to stdout", func() { 389 output := io_helpers.CaptureOutput( 390 func() { 391 logger := trace.NewWriterPrinter(os.Stdout, true) 392 NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), logger).Failed("this should print only once") 393 }) 394 395 Expect(output).To(HaveLen(3)) 396 Expect(output[0]).To(Equal("FAILED")) 397 Expect(output[1]).To(Equal("this should print only once")) 398 Expect(output[2]).To(Equal("")) 399 }) 400 }) 401 }) 402 403 Describe("NotifyUpdateIfNeeded", func() { 404 var ( 405 output []string 406 config coreconfig.ReadWriter 407 ) 408 409 BeforeEach(func() { 410 config = testconfig.NewRepository() 411 }) 412 413 It("Prints a notification to user if current version < min cli version", func() { 414 config.SetMinCLIVersion("6.0.0") 415 config.SetMinRecommendedCLIVersion("6.5.0") 416 config.SetAPIVersion("2.15.1") 417 config.SetCLIVersion("5.0.0") 418 output = io_helpers.CaptureOutput(func() { 419 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 420 ui.NotifyUpdateIfNeeded(config) 421 }) 422 423 Expect(output).To(ContainSubstrings([]string{"Cloud Foundry API version", 424 "requires CLI version 6.0.0", 425 "You are currently on version 5.0.0", 426 "To upgrade your CLI, please visit: https://github.com/cloudfoundry/cli#downloads", 427 })) 428 }) 429 430 It("Doesn't print a notification to user if current version >= min cli version", func() { 431 config.SetMinCLIVersion("6.0.0") 432 config.SetMinRecommendedCLIVersion("6.5.0") 433 config.SetAPIVersion("2.15.1") 434 config.SetCLIVersion("6.0.0") 435 output = io_helpers.CaptureOutput(func() { 436 ui := NewUI(os.Stdin, os.Stdout, NewTeePrinter(os.Stdout), fakeLogger) 437 ui.NotifyUpdateIfNeeded(config) 438 }) 439 440 Expect(output[0]).To(Equal("")) 441 }) 442 }) 443 })