github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/actor/pluginaction/install_test.go (about) 1 package pluginaction_test 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 9 "code.cloudfoundry.org/cli/actor/actionerror" 10 . "code.cloudfoundry.org/cli/actor/pluginaction" 11 "code.cloudfoundry.org/cli/actor/pluginaction/pluginactionfakes" 12 "code.cloudfoundry.org/cli/api/plugin" 13 "code.cloudfoundry.org/cli/api/plugin/pluginfakes" 14 "code.cloudfoundry.org/cli/util/configv3" 15 "code.cloudfoundry.org/cli/util/generic" 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 ) 19 20 var _ = Describe("install actions", func() { 21 var ( 22 actor *Actor 23 fakeConfig *pluginactionfakes.FakeConfig 24 fakeClient *pluginactionfakes.FakePluginClient 25 tempPluginDir string 26 ) 27 28 BeforeEach(func() { 29 fakeConfig = new(pluginactionfakes.FakeConfig) 30 fakeClient = new(pluginactionfakes.FakePluginClient) 31 actor = NewActor(fakeConfig, fakeClient) 32 33 var err error 34 tempPluginDir, err = ioutil.TempDir("", "") 35 Expect(err).ToNot(HaveOccurred()) 36 }) 37 38 AfterEach(func() { 39 err := os.RemoveAll(tempPluginDir) 40 Expect(err).ToNot(HaveOccurred()) 41 }) 42 43 Describe("CreateExecutableCopy", func() { 44 Context("when the file exists", func() { 45 var pluginPath string 46 47 BeforeEach(func() { 48 tempFile, err := ioutil.TempFile("", "") 49 Expect(err).ToNot(HaveOccurred()) 50 51 _, err = tempFile.WriteString("cthulhu") 52 Expect(err).ToNot(HaveOccurred()) 53 err = tempFile.Close() 54 Expect(err).ToNot(HaveOccurred()) 55 56 pluginPath = tempFile.Name() 57 }) 58 59 AfterEach(func() { 60 err := os.Remove(pluginPath) 61 Expect(err).ToNot(HaveOccurred()) 62 }) 63 64 It("creates a copy of a file in plugin home", func() { 65 copyPath, err := actor.CreateExecutableCopy(pluginPath, tempPluginDir) 66 Expect(err).ToNot(HaveOccurred()) 67 68 contents, err := ioutil.ReadFile(copyPath) 69 Expect(err).ToNot(HaveOccurred()) 70 Expect(contents).To(BeEquivalentTo("cthulhu")) 71 }) 72 }) 73 74 Context("when the file does not exist", func() { 75 It("returns an os.PathError", func() { 76 _, err := actor.CreateExecutableCopy("i-don't-exist", tempPluginDir) 77 _, isPathError := err.(*os.PathError) 78 Expect(isPathError).To(BeTrue()) 79 }) 80 }) 81 }) 82 83 Describe("DownloadExecutableBinaryFromURL", func() { 84 var ( 85 path string 86 downloadErr error 87 fakeProxyReader *pluginfakes.FakeProxyReader 88 ) 89 90 JustBeforeEach(func() { 91 fakeProxyReader = new(pluginfakes.FakeProxyReader) 92 path, downloadErr = actor.DownloadExecutableBinaryFromURL("some-plugin-url.com", tempPluginDir, fakeProxyReader) 93 }) 94 95 Context("when the downloaded is successful", func() { 96 var ( 97 data []byte 98 ) 99 100 BeforeEach(func() { 101 data = []byte("some test data") 102 fakeClient.DownloadPluginStub = func(_ string, path string, _ plugin.ProxyReader) error { 103 err := ioutil.WriteFile(path, data, 0700) 104 Expect(err).ToNot(HaveOccurred()) 105 return nil 106 } 107 }) 108 It("returns the path to the file and the size", func() { 109 Expect(downloadErr).ToNot(HaveOccurred()) 110 fileData, err := ioutil.ReadFile(path) 111 Expect(err).ToNot(HaveOccurred()) 112 Expect(fileData).To(Equal(data)) 113 114 Expect(fakeClient.DownloadPluginCallCount()).To(Equal(1)) 115 pluginURL, downloadPath, proxyReader := fakeClient.DownloadPluginArgsForCall(0) 116 Expect(pluginURL).To(Equal("some-plugin-url.com")) 117 Expect(downloadPath).To(Equal(path)) 118 Expect(proxyReader).To(Equal(fakeProxyReader)) 119 }) 120 }) 121 122 Context("when there is an error downloading file", func() { 123 var expectedErr error 124 125 BeforeEach(func() { 126 expectedErr = errors.New("some error") 127 fakeClient.DownloadPluginReturns(expectedErr) 128 }) 129 130 It("returns the error", func() { 131 Expect(downloadErr).To(MatchError(expectedErr)) 132 }) 133 }) 134 }) 135 136 Describe("FileExists", func() { 137 var pluginPath string 138 139 Context("when the file exists", func() { 140 BeforeEach(func() { 141 pluginFile, err := ioutil.TempFile("", "") 142 Expect(err).NotTo(HaveOccurred()) 143 err = pluginFile.Close() 144 Expect(err).NotTo(HaveOccurred()) 145 146 pluginPath = pluginFile.Name() 147 }) 148 149 AfterEach(func() { 150 err := os.Remove(pluginPath) 151 Expect(err).NotTo(HaveOccurred()) 152 }) 153 154 It("returns true", func() { 155 Expect(actor.FileExists(pluginPath)).To(BeTrue()) 156 }) 157 }) 158 159 Context("when the file does not exist", func() { 160 It("returns false", func() { 161 Expect(actor.FileExists("/some/path/that/does/not/exist")).To(BeFalse()) 162 }) 163 }) 164 }) 165 166 Describe("GetAndValidatePlugin", func() { 167 var ( 168 fakePluginMetadata *pluginactionfakes.FakePluginMetadata 169 fakeCommandList *pluginactionfakes.FakeCommandList 170 plugin configv3.Plugin 171 validateErr error 172 ) 173 174 BeforeEach(func() { 175 fakePluginMetadata = new(pluginactionfakes.FakePluginMetadata) 176 fakeCommandList = new(pluginactionfakes.FakeCommandList) 177 }) 178 179 JustBeforeEach(func() { 180 plugin, validateErr = actor.GetAndValidatePlugin(fakePluginMetadata, fakeCommandList, "some-plugin-path") 181 }) 182 183 Context("when getting the plugin metadata returns an error", func() { 184 var expectedErr error 185 186 BeforeEach(func() { 187 expectedErr = errors.New("error getting metadata") 188 fakePluginMetadata.GetMetadataReturns(configv3.Plugin{}, expectedErr) 189 }) 190 191 It("returns a PluginInvalidError", func() { 192 Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{Err: expectedErr})) 193 }) 194 }) 195 196 Context("when the plugin name is missing", func() { 197 BeforeEach(func() { 198 fakePluginMetadata.GetMetadataReturns(configv3.Plugin{}, nil) 199 }) 200 201 It("returns a PluginInvalidError", func() { 202 Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{})) 203 }) 204 }) 205 206 Context("when the plugin does not have any commands", func() { 207 BeforeEach(func() { 208 fakePluginMetadata.GetMetadataReturns(configv3.Plugin{Name: "some-plugin"}, nil) 209 }) 210 211 It("returns a PluginInvalidError", func() { 212 Expect(validateErr).To(MatchError(actionerror.PluginInvalidError{})) 213 }) 214 }) 215 216 Context("when there are command conflicts", func() { 217 BeforeEach(func() { 218 fakePluginMetadata.GetMetadataReturns(configv3.Plugin{ 219 Name: "some-plugin", 220 Version: configv3.PluginVersion{ 221 Major: 1, 222 Minor: 1, 223 Build: 1, 224 }, 225 Commands: []configv3.PluginCommand{ 226 {Name: "some-other-command", Alias: "soc"}, 227 {Name: "some-command", Alias: "sc"}, 228 {Name: "version", Alias: "v"}, 229 {Name: "p", Alias: "push"}, 230 }, 231 }, nil) 232 }) 233 234 Context("when the plugin has command names that conflict with native command names", func() { 235 BeforeEach(func() { 236 fakeCommandList.HasCommandStub = func(commandName string) bool { 237 switch commandName { 238 case "version": 239 return true 240 default: 241 return false 242 } 243 } 244 }) 245 246 It("returns a PluginCommandsConflictError containing all conflicting command names", func() { 247 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 248 PluginName: "some-plugin", 249 PluginVersion: "1.1.1", 250 CommandNames: []string{"version"}, 251 CommandAliases: []string{}, 252 })) 253 }) 254 }) 255 256 Context("when the plugin has command names that conflict with native command aliases", func() { 257 BeforeEach(func() { 258 fakeCommandList.HasAliasStub = func(commandAlias string) bool { 259 switch commandAlias { 260 case "p": 261 return true 262 default: 263 return false 264 } 265 } 266 }) 267 268 It("returns a PluginCommandsConflictError containing all conflicting command names", func() { 269 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 270 PluginName: "some-plugin", 271 PluginVersion: "1.1.1", 272 CommandNames: []string{"p"}, 273 CommandAliases: []string{}, 274 })) 275 }) 276 }) 277 278 Context("when the plugin has command aliases that conflict with native command names", func() { 279 BeforeEach(func() { 280 fakeCommandList.HasCommandStub = func(commandName string) bool { 281 switch commandName { 282 case "push": 283 return true 284 default: 285 return false 286 } 287 } 288 }) 289 290 It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() { 291 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 292 PluginName: "some-plugin", 293 PluginVersion: "1.1.1", 294 CommandAliases: []string{"push"}, 295 CommandNames: []string{}, 296 })) 297 }) 298 }) 299 300 Context("when the plugin has command aliases that conflict with native command aliases", func() { 301 BeforeEach(func() { 302 fakeCommandList.HasAliasStub = func(commandAlias string) bool { 303 switch commandAlias { 304 case "v": 305 return true 306 default: 307 return false 308 } 309 } 310 }) 311 312 It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() { 313 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 314 PluginName: "some-plugin", 315 PluginVersion: "1.1.1", 316 CommandAliases: []string{"v"}, 317 CommandNames: []string{}, 318 })) 319 }) 320 }) 321 322 Context("when the plugin has command names that conflict with existing plugin command names", func() { 323 BeforeEach(func() { 324 fakeConfig.PluginsReturns([]configv3.Plugin{{ 325 Name: "installed-plugin-2", 326 Commands: []configv3.PluginCommand{{Name: "some-command"}}, 327 }}) 328 }) 329 330 It("returns a PluginCommandsConflictError containing all conflicting command names", func() { 331 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 332 PluginName: "some-plugin", 333 PluginVersion: "1.1.1", 334 CommandNames: []string{"some-command"}, 335 CommandAliases: []string{}, 336 })) 337 }) 338 }) 339 340 Context("when the plugin has command names that conflict with existing plugin command aliases", func() { 341 BeforeEach(func() { 342 fakeConfig.PluginsReturns([]configv3.Plugin{{ 343 Name: "installed-plugin-2", 344 Commands: []configv3.PluginCommand{{Alias: "some-command"}}}, 345 }) 346 }) 347 348 It("returns a PluginCommandsConflictError containing all conflicting command names", func() { 349 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 350 PluginName: "some-plugin", 351 PluginVersion: "1.1.1", 352 CommandNames: []string{"some-command"}, 353 CommandAliases: []string{}, 354 })) 355 }) 356 }) 357 358 Context("when the plugin has command aliases that conflict with existing plugin command names", func() { 359 BeforeEach(func() { 360 fakeConfig.PluginsReturns([]configv3.Plugin{{ 361 Name: "installed-plugin-2", 362 Commands: []configv3.PluginCommand{{Name: "sc"}}}, 363 }) 364 }) 365 366 It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() { 367 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 368 PluginName: "some-plugin", 369 PluginVersion: "1.1.1", 370 CommandNames: []string{}, 371 CommandAliases: []string{"sc"}, 372 })) 373 }) 374 }) 375 376 Context("when the plugin has command aliases that conflict with existing plugin command aliases", func() { 377 BeforeEach(func() { 378 fakeConfig.PluginsReturns([]configv3.Plugin{{ 379 Name: "installed-plugin-2", 380 Commands: []configv3.PluginCommand{{Alias: "sc"}}}, 381 }) 382 }) 383 384 It("returns a PluginCommandsConflictError containing all conflicting command aliases", func() { 385 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 386 PluginName: "some-plugin", 387 PluginVersion: "1.1.1", 388 CommandAliases: []string{"sc"}, 389 CommandNames: []string{}, 390 })) 391 }) 392 }) 393 394 Context("when the plugin has command names and aliases that conflict with existing native and plugin command names and aliases", func() { 395 BeforeEach(func() { 396 fakeConfig.PluginsReturns([]configv3.Plugin{ 397 { 398 Name: "installed-plugin-1", 399 Commands: []configv3.PluginCommand{ 400 {Name: "some-command"}, 401 {Alias: "some-other-command"}, 402 }, 403 }, 404 { 405 Name: "installed-plugin-2", 406 Commands: []configv3.PluginCommand{ 407 {Name: "sc"}, 408 {Alias: "soc"}, 409 }, 410 }, 411 }) 412 413 fakeCommandList.HasCommandStub = func(commandName string) bool { 414 switch commandName { 415 case "version", "p": 416 return true 417 default: 418 return false 419 } 420 } 421 422 fakeCommandList.HasAliasStub = func(commandAlias string) bool { 423 switch commandAlias { 424 case "v", "push": 425 return true 426 default: 427 return false 428 } 429 } 430 }) 431 432 It("returns a PluginCommandsConflictError with all conflicting command names and aliases", func() { 433 Expect(validateErr).To(MatchError(actionerror.PluginCommandsConflictError{ 434 PluginName: "some-plugin", 435 PluginVersion: "1.1.1", 436 CommandNames: []string{"p", "some-command", "some-other-command", "version"}, 437 CommandAliases: []string{"push", "sc", "soc", "v"}, 438 })) 439 }) 440 }) 441 442 Context("when the plugin is already installed", func() { 443 BeforeEach(func() { 444 fakeConfig.PluginsReturns([]configv3.Plugin{{ 445 Name: "some-plugin", 446 Commands: []configv3.PluginCommand{ 447 {Name: "some-command", Alias: "sc"}, 448 {Name: "some-other-command", Alias: "soc"}, 449 }, 450 }}) 451 }) 452 453 It("does not return any errors due to command name or alias conflict", func() { 454 Expect(validateErr).ToNot(HaveOccurred()) 455 }) 456 }) 457 }) 458 459 Context("when the plugin is valid", func() { 460 var pluginToBeInstalled configv3.Plugin 461 462 BeforeEach(func() { 463 pluginToBeInstalled = configv3.Plugin{ 464 Name: "some-plugin", 465 Version: configv3.PluginVersion{ 466 Major: 1, 467 Minor: 1, 468 Build: 1, 469 }, 470 Commands: []configv3.PluginCommand{ 471 { 472 Name: "some-command", 473 Alias: "sc", 474 }, 475 { 476 Name: "some-other-command", 477 Alias: "soc", 478 }, 479 }, 480 } 481 fakePluginMetadata.GetMetadataReturns(pluginToBeInstalled, nil) 482 fakeConfig.PluginsReturns([]configv3.Plugin{ 483 { 484 Name: "installed-plugin-1", 485 Commands: []configv3.PluginCommand{ 486 { 487 Name: "unique-command-1", 488 Alias: "uc1", 489 }, 490 }, 491 }, 492 { 493 Name: "installed-plugin-2", 494 Commands: []configv3.PluginCommand{ 495 { 496 Name: "unique-command-2", 497 Alias: "uc2", 498 }, 499 { 500 Name: "unique-command-3", 501 Alias: "uc3", 502 }, 503 }, 504 }, 505 }) 506 }) 507 508 It("returns the plugin and no errors", func() { 509 Expect(validateErr).ToNot(HaveOccurred()) 510 Expect(plugin).To(Equal(pluginToBeInstalled)) 511 512 Expect(fakePluginMetadata.GetMetadataCallCount()).To(Equal(1)) 513 Expect(fakePluginMetadata.GetMetadataArgsForCall(0)).To(Equal("some-plugin-path")) 514 515 Expect(fakeCommandList.HasCommandCallCount()).To(Equal(4)) 516 Expect(fakeCommandList.HasCommandArgsForCall(0)).To(Equal("some-command")) 517 Expect(fakeCommandList.HasCommandArgsForCall(1)).To(Equal("sc")) 518 Expect(fakeCommandList.HasCommandArgsForCall(2)).To(Equal("some-other-command")) 519 Expect(fakeCommandList.HasCommandArgsForCall(3)).To(Equal("soc")) 520 521 Expect(fakeCommandList.HasAliasCallCount()).To(Equal(4)) 522 Expect(fakeCommandList.HasAliasArgsForCall(0)).To(Equal("some-command")) 523 Expect(fakeCommandList.HasAliasArgsForCall(1)).To(Equal("sc")) 524 Expect(fakeCommandList.HasAliasArgsForCall(2)).To(Equal("some-other-command")) 525 Expect(fakeCommandList.HasAliasArgsForCall(3)).To(Equal("soc")) 526 527 Expect(fakeConfig.PluginsCallCount()).To(Equal(1)) 528 }) 529 }) 530 }) 531 532 Describe("InstallPluginFromLocalPath", func() { 533 var ( 534 plugin configv3.Plugin 535 installErr error 536 537 pluginHomeDir string 538 pluginPath string 539 tempDir string 540 ) 541 542 BeforeEach(func() { 543 plugin = configv3.Plugin{ 544 Name: "some-plugin", 545 Commands: []configv3.PluginCommand{ 546 {Name: "some-command"}, 547 }, 548 } 549 550 pluginFile, err := ioutil.TempFile("", "") 551 Expect(err).NotTo(HaveOccurred()) 552 err = pluginFile.Close() 553 Expect(err).NotTo(HaveOccurred()) 554 555 pluginPath = pluginFile.Name() 556 557 tempDir, err = ioutil.TempDir("", "") 558 Expect(err).ToNot(HaveOccurred()) 559 560 pluginHomeDir = filepath.Join(tempDir, ".cf", "plugin") 561 }) 562 563 AfterEach(func() { 564 err := os.Remove(pluginPath) 565 Expect(err).NotTo(HaveOccurred()) 566 567 err = os.RemoveAll(tempDir) 568 Expect(err).NotTo(HaveOccurred()) 569 }) 570 571 JustBeforeEach(func() { 572 installErr = actor.InstallPluginFromPath(pluginPath, plugin) 573 }) 574 575 Context("when an error is encountered copying the plugin to the plugin directory", func() { 576 BeforeEach(func() { 577 fakeConfig.PluginHomeReturns(pluginPath) 578 }) 579 580 It("returns the error", func() { 581 _, isPathError := installErr.(*os.PathError) 582 Expect(isPathError).To(BeTrue()) 583 }) 584 }) 585 586 Context("when an error is encountered writing the plugin config to disk", func() { 587 var ( 588 expectedErr error 589 ) 590 591 BeforeEach(func() { 592 fakeConfig.PluginHomeReturns(pluginHomeDir) 593 594 expectedErr = errors.New("write config error") 595 fakeConfig.WritePluginConfigReturns(expectedErr) 596 }) 597 598 It("returns the error", func() { 599 Expect(installErr).To(MatchError(expectedErr)) 600 }) 601 }) 602 603 Context("when no errors are encountered", func() { 604 BeforeEach(func() { 605 fakeConfig.PluginHomeReturns(pluginHomeDir) 606 }) 607 608 It("makes an executable copy of the plugin file in the plugin directory, updates the plugin config, and writes the config to disk", func() { 609 Expect(installErr).ToNot(HaveOccurred()) 610 611 installedPluginPath := generic.ExecutableFilename(filepath.Join(pluginHomeDir, "some-plugin")) 612 613 Expect(fakeConfig.PluginHomeCallCount()).To(Equal(1)) 614 615 Expect(fakeConfig.AddPluginCallCount()).To(Equal(1)) 616 Expect(fakeConfig.AddPluginArgsForCall(0)).To(Equal(configv3.Plugin{ 617 Name: "some-plugin", 618 Commands: []configv3.PluginCommand{ 619 {Name: "some-command"}, 620 }, 621 Location: installedPluginPath, 622 })) 623 624 Expect(fakeConfig.WritePluginConfigCallCount()).To(Equal(1)) 625 }) 626 }) 627 }) 628 })