github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/cf/commands/application/ssh_test.go (about)

     1  package application_test
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"time"
     8  
     9  	"code.cloudfoundry.org/cli/cf/api/apifakes"
    10  	"code.cloudfoundry.org/cli/cf/commandregistry"
    11  	"code.cloudfoundry.org/cli/cf/commands/commandsfakes"
    12  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    13  	"code.cloudfoundry.org/cli/cf/models"
    14  	"code.cloudfoundry.org/cli/cf/net"
    15  	"code.cloudfoundry.org/cli/cf/requirements"
    16  	"code.cloudfoundry.org/cli/cf/requirements/requirementsfakes"
    17  	"code.cloudfoundry.org/cli/cf/ssh/sshfakes"
    18  	testcmd "code.cloudfoundry.org/cli/util/testhelpers/commands"
    19  	testconfig "code.cloudfoundry.org/cli/util/testhelpers/configuration"
    20  	testnet "code.cloudfoundry.org/cli/util/testhelpers/net"
    21  	testterm "code.cloudfoundry.org/cli/util/testhelpers/terminal"
    22  
    23  	"code.cloudfoundry.org/cli/cf/trace/tracefakes"
    24  	. "code.cloudfoundry.org/cli/util/testhelpers/matchers"
    25  	. "github.com/onsi/ginkgo"
    26  	. "github.com/onsi/gomega"
    27  )
    28  
    29  var _ = Describe("SSH command", func() {
    30  	var (
    31  		ui *testterm.FakeUI
    32  
    33  		sshCodeGetter         *commandsfakes.FakeSSHCodeGetter
    34  		originalSSHCodeGetter commandregistry.Command
    35  
    36  		requirementsFactory *requirementsfakes.FakeFactory
    37  		configRepo          coreconfig.Repository
    38  		deps                commandregistry.Dependency
    39  		ccGateway           net.Gateway
    40  
    41  		fakeSecureShell *sshfakes.FakeSecureShell
    42  	)
    43  
    44  	BeforeEach(func() {
    45  		ui = &testterm.FakeUI{}
    46  		configRepo = testconfig.NewRepositoryWithDefaults()
    47  		requirementsFactory = new(requirementsfakes.FakeFactory)
    48  		deps.Gateways = make(map[string]net.Gateway)
    49  
    50  		//save original command and restore later
    51  		originalSSHCodeGetter = commandregistry.Commands.FindCommand("ssh-code")
    52  
    53  		sshCodeGetter = new(commandsfakes.FakeSSHCodeGetter)
    54  
    55  		//setup fakes to correctly interact with commandregistry
    56  		sshCodeGetter.SetDependencyStub = func(_ commandregistry.Dependency, _ bool) commandregistry.Command {
    57  			return sshCodeGetter
    58  		}
    59  		sshCodeGetter.MetaDataReturns(commandregistry.CommandMetadata{Name: "ssh-code"})
    60  	})
    61  
    62  	AfterEach(func() {
    63  		//restore original command
    64  		commandregistry.Register(originalSSHCodeGetter)
    65  	})
    66  
    67  	updateCommandDependency := func(pluginCall bool) {
    68  		deps.UI = ui
    69  		deps.Config = configRepo
    70  
    71  		//inject fake 'sshCodeGetter' into registry
    72  		commandregistry.Register(sshCodeGetter)
    73  
    74  		commandregistry.Commands.SetCommand(commandregistry.Commands.FindCommand("ssh").SetDependency(deps, pluginCall))
    75  	}
    76  
    77  	runCommand := func(args ...string) bool {
    78  		return testcmd.RunCLICommand("ssh", args, requirementsFactory, updateCommandDependency, false, ui)
    79  	}
    80  
    81  	Describe("Requirements", func() {
    82  		It("fails with usage when not provided exactly one arg", func() {
    83  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
    84  
    85  			runCommand()
    86  			Expect(ui.Outputs()).To(ContainSubstrings(
    87  				[]string{"Incorrect Usage", "Requires", "argument"},
    88  			))
    89  
    90  		})
    91  
    92  		It("fails requirements when not logged in", func() {
    93  			requirementsFactory.NewLoginRequirementReturns(requirements.Failing{Message: "not logged in"})
    94  			Expect(runCommand("my-app")).To(BeFalse())
    95  		})
    96  
    97  		It("fails if a space is not targeted", func() {
    98  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
    99  			requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Failing{Message: "not targeting space"})
   100  			Expect(runCommand("my-app")).To(BeFalse())
   101  		})
   102  
   103  		It("fails if a application is not found", func() {
   104  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
   105  			requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{})
   106  			applicationReq := new(requirementsfakes.FakeApplicationRequirement)
   107  			applicationReq.ExecuteReturns(errors.New("no app"))
   108  			requirementsFactory.NewApplicationRequirementReturns(applicationReq)
   109  
   110  			Expect(runCommand("my-app")).To(BeFalse())
   111  		})
   112  
   113  		Describe("SSHOptions", func() {
   114  			Context("when an error is returned during initialization", func() {
   115  				It("shows error and prints command usage", func() {
   116  					Expect(runCommand("app_name", "-L", "[9999:localhost...")).To(BeFalse())
   117  					Expect(ui.Outputs()).To(ContainSubstrings(
   118  						[]string{"Incorrect Usage"},
   119  						[]string{"USAGE:"},
   120  					))
   121  				})
   122  			})
   123  		})
   124  	})
   125  
   126  	Describe("Specifying application index", func() {
   127  		BeforeEach(func() {
   128  			var app models.Application
   129  
   130  			app = models.Application{}
   131  			app.Name = "my-app"
   132  			app.State = "started"
   133  			app.GUID = "my-app-guid"
   134  			app.EnableSSH = true
   135  			app.Diego = true
   136  			app.InstanceCount = 3
   137  
   138  			applicationReq := new(requirementsfakes.FakeApplicationRequirement)
   139  			applicationReq.GetApplicationReturns(app)
   140  			requirementsFactory.NewApplicationRequirementReturns(applicationReq)
   141  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
   142  			requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{})
   143  		})
   144  
   145  		Context("when an app instance is provided", func() {
   146  			Context("when it is negative", func() {
   147  				It("returns an error", func() {
   148  					Expect(runCommand("my-app", "-i", "-3")).To(BeFalse())
   149  					Expect(ui.Outputs()).To(ContainSubstrings(
   150  						[]string{"The application instance index cannot be negative"},
   151  					))
   152  				})
   153  			})
   154  
   155  			Context("when the app index exceeds the last valid index", func() {
   156  				It("returns an error", func() {
   157  					Expect(runCommand("my-app", "-i", "3")).To(BeFalse())
   158  					Expect(ui.Outputs()).To(ContainSubstrings(
   159  						[]string{"The specified application instance does not exist"},
   160  					))
   161  				})
   162  			})
   163  		})
   164  	})
   165  
   166  	Describe("ssh", func() {
   167  		var (
   168  			currentApp models.Application
   169  		)
   170  
   171  		BeforeEach(func() {
   172  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
   173  			requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{})
   174  			currentApp = models.Application{}
   175  			currentApp.Name = "my-app"
   176  			currentApp.State = "started"
   177  			currentApp.GUID = "my-app-guid"
   178  			currentApp.EnableSSH = true
   179  			currentApp.Diego = true
   180  
   181  			applicationReq := new(requirementsfakes.FakeApplicationRequirement)
   182  			applicationReq.GetApplicationReturns(currentApp)
   183  			requirementsFactory.NewApplicationRequirementReturns(applicationReq)
   184  		})
   185  
   186  		Describe("Error getting required info to run ssh", func() {
   187  			var (
   188  				testServer *httptest.Server
   189  				handler    *testnet.TestHandler
   190  			)
   191  
   192  			AfterEach(func() {
   193  				testServer.Close()
   194  			})
   195  
   196  			Context("error when getting SSH info from /v2/info", func() {
   197  				BeforeEach(func() {
   198  					getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   199  						Method: "GET",
   200  						Path:   "/v2/info",
   201  						Response: testnet.TestResponse{
   202  							Status: http.StatusNotFound,
   203  							Body:   `{}`,
   204  						},
   205  					})
   206  
   207  					testServer, handler = testnet.NewServer([]testnet.TestRequest{getRequest})
   208  					configRepo.SetAPIEndpoint(testServer.URL)
   209  					ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   210  					deps.Gateways["cloud-controller"] = ccGateway
   211  				})
   212  
   213  				It("notifies users", func() {
   214  					runCommand("my-app")
   215  
   216  					Expect(handler).To(HaveAllRequestsCalled())
   217  					Expect(ui.Outputs()).To(ContainSubstrings(
   218  						[]string{"Error getting SSH info", "404"},
   219  					))
   220  
   221  				})
   222  			})
   223  
   224  			Context("error when getting oauth token", func() {
   225  				BeforeEach(func() {
   226  					sshCodeGetter.GetReturns("", errors.New("auth api error"))
   227  
   228  					getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   229  						Method: "GET",
   230  						Path:   "/v2/info",
   231  						Response: testnet.TestResponse{
   232  							Status: http.StatusOK,
   233  							Body:   `{}`,
   234  						},
   235  					})
   236  
   237  					testServer, handler = testnet.NewServer([]testnet.TestRequest{getRequest})
   238  					configRepo.SetAPIEndpoint(testServer.URL)
   239  					ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   240  					deps.Gateways["cloud-controller"] = ccGateway
   241  				})
   242  
   243  				It("notifies users", func() {
   244  					runCommand("my-app")
   245  
   246  					Expect(handler).To(HaveAllRequestsCalled())
   247  					Expect(ui.Outputs()).To(ContainSubstrings(
   248  						[]string{"Error getting one time auth code", "auth api error"},
   249  					))
   250  
   251  				})
   252  			})
   253  		})
   254  
   255  		Describe("Connecting to ssh server", func() {
   256  			var testServer *httptest.Server
   257  
   258  			AfterEach(func() {
   259  				testServer.Close()
   260  			})
   261  
   262  			BeforeEach(func() {
   263  				fakeSecureShell = new(sshfakes.FakeSecureShell)
   264  
   265  				deps.WildcardDependency = fakeSecureShell
   266  
   267  				getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   268  					Method: "GET",
   269  					Path:   "/v2/info",
   270  					Response: testnet.TestResponse{
   271  						Status: http.StatusOK,
   272  						Body:   getInfoResponseBody,
   273  					},
   274  				})
   275  
   276  				testServer, _ = testnet.NewServer([]testnet.TestRequest{getRequest})
   277  				configRepo.SetAPIEndpoint(testServer.URL)
   278  				ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   279  				deps.Gateways["cloud-controller"] = ccGateway
   280  			})
   281  
   282  			Context("Error when connecting", func() {
   283  				It("notifies users", func() {
   284  					fakeSecureShell.ConnectReturns(errors.New("dial errorrr"))
   285  
   286  					runCommand("my-app")
   287  
   288  					Expect(ui.Outputs()).To(ContainSubstrings(
   289  						[]string{"Error opening SSH connection", "dial error"},
   290  					))
   291  
   292  				})
   293  			})
   294  
   295  			Context("Error port forwarding when -L is provided", func() {
   296  				It("notifies users", func() {
   297  					fakeSecureShell.LocalPortForwardReturns(errors.New("listen error"))
   298  
   299  					runCommand("my-app", "-L", "8000:localhost:8000")
   300  
   301  					Expect(ui.Outputs()).To(ContainSubstrings(
   302  						[]string{"Error forwarding port", "listen error"},
   303  					))
   304  
   305  				})
   306  			})
   307  
   308  			Context("when -N is provided", func() {
   309  				It("calls secureShell.Wait()", func() {
   310  					fakeSecureShell.ConnectReturns(nil)
   311  					fakeSecureShell.LocalPortForwardReturns(nil)
   312  
   313  					runCommand("my-app", "-N")
   314  
   315  					Expect(fakeSecureShell.WaitCallCount()).To(Equal(1))
   316  				})
   317  			})
   318  
   319  			Context("when -N is provided", func() {
   320  				It("calls secureShell.InteractiveSession()", func() {
   321  					fakeSecureShell.ConnectReturns(nil)
   322  					fakeSecureShell.LocalPortForwardReturns(nil)
   323  
   324  					runCommand("my-app", "-k")
   325  
   326  					Expect(fakeSecureShell.InteractiveSessionCallCount()).To(Equal(1))
   327  				})
   328  			})
   329  
   330  			Context("when Wait() or InteractiveSession() returns error", func() {
   331  
   332  				It("notifities users", func() {
   333  					fakeSecureShell.ConnectReturns(nil)
   334  					fakeSecureShell.LocalPortForwardReturns(nil)
   335  
   336  					fakeSecureShell.InteractiveSessionReturns(errors.New("ssh exit error"))
   337  					runCommand("my-app", "-k")
   338  
   339  					Expect(ui.Outputs()).To(ContainSubstrings(
   340  						[]string{"ssh exit error"},
   341  					))
   342  
   343  				})
   344  			})
   345  		})
   346  	})
   347  })
   348  
   349  const getInfoResponseBody string = `
   350  {
   351     "name": "vcap",
   352     "build": "2222",
   353     "support": "http://support.cloudfoundry.com",
   354     "version": 2,
   355     "description": "Cloud Foundry sponsored by ABC",
   356     "authorization_endpoint": "https://login.run.abc.com",
   357     "token_endpoint": "https://uaa.run.abc.com",
   358     "min_cli_version": null,
   359     "min_recommended_cli_version": null,
   360     "api_version": "2.35.0",
   361     "app_ssh_endpoint": "ssh.run.pivotal.io:2222",
   362     "app_ssh_host_key_fingerprint": "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11",
   363     "logging_endpoint": "wss://loggregator.run.abc.com:443",
   364     "doppler_logging_endpoint": "wss://doppler.run.abc.com:443",
   365     "user": "6e477566-ac8d-4653-98c6-d319595ec7b0"
   366  }`