github.com/elopio/cli@v6.21.2-0.20160902224010-ea909d1fdb2f+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/testhelpers/commands"
    19  	testconfig "code.cloudfoundry.org/cli/testhelpers/configuration"
    20  	testnet "code.cloudfoundry.org/cli/testhelpers/net"
    21  	testterm "code.cloudfoundry.org/cli/testhelpers/terminal"
    22  
    23  	"code.cloudfoundry.org/cli/cf/trace/tracefakes"
    24  	. "code.cloudfoundry.org/cli/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("Flag options", func() {
   114  			var args []string
   115  
   116  			BeforeEach(func() {
   117  				requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
   118  				requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{})
   119  			})
   120  
   121  			Context("when an -i flag is provided", func() {
   122  				BeforeEach(func() {
   123  					args = append(args, "app-name")
   124  				})
   125  
   126  				Context("with a negative integer argument", func() {
   127  					BeforeEach(func() {
   128  						args = append(args, "-i", "-3")
   129  					})
   130  
   131  					It("returns an error", func() {
   132  						Expect(runCommand(args...)).To(BeFalse())
   133  						Expect(ui.Outputs()).To(ContainSubstrings(
   134  							[]string{"Incorrect Usage", "cannot be negative"},
   135  						))
   136  
   137  					})
   138  				})
   139  			})
   140  		})
   141  
   142  		Describe("SSHOptions", func() {
   143  			Context("when an error is returned during initialization", func() {
   144  				It("shows error and prints command usage", func() {
   145  					Expect(runCommand("app_name", "-L", "[9999:localhost...")).To(BeFalse())
   146  					Expect(ui.Outputs()).To(ContainSubstrings(
   147  						[]string{"Incorrect Usage"},
   148  						[]string{"USAGE:"},
   149  					))
   150  
   151  				})
   152  			})
   153  		})
   154  
   155  	})
   156  
   157  	Describe("ssh", func() {
   158  		var (
   159  			currentApp models.Application
   160  		)
   161  
   162  		BeforeEach(func() {
   163  			requirementsFactory.NewLoginRequirementReturns(requirements.Passing{})
   164  			requirementsFactory.NewTargetedSpaceRequirementReturns(requirements.Passing{})
   165  			currentApp = models.Application{}
   166  			currentApp.Name = "my-app"
   167  			currentApp.State = "started"
   168  			currentApp.GUID = "my-app-guid"
   169  			currentApp.EnableSSH = true
   170  			currentApp.Diego = true
   171  
   172  			applicationReq := new(requirementsfakes.FakeApplicationRequirement)
   173  			applicationReq.GetApplicationReturns(currentApp)
   174  			requirementsFactory.NewApplicationRequirementReturns(applicationReq)
   175  		})
   176  
   177  		Describe("Error getting required info to run ssh", func() {
   178  			var (
   179  				testServer *httptest.Server
   180  				handler    *testnet.TestHandler
   181  			)
   182  
   183  			AfterEach(func() {
   184  				testServer.Close()
   185  			})
   186  
   187  			Context("error when getting SSH info from /v2/info", func() {
   188  				BeforeEach(func() {
   189  					getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   190  						Method: "GET",
   191  						Path:   "/v2/info",
   192  						Response: testnet.TestResponse{
   193  							Status: http.StatusNotFound,
   194  							Body:   `{}`,
   195  						},
   196  					})
   197  
   198  					testServer, handler = testnet.NewServer([]testnet.TestRequest{getRequest})
   199  					configRepo.SetAPIEndpoint(testServer.URL)
   200  					ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   201  					deps.Gateways["cloud-controller"] = ccGateway
   202  				})
   203  
   204  				It("notifies users", func() {
   205  					runCommand("my-app")
   206  
   207  					Expect(handler).To(HaveAllRequestsCalled())
   208  					Expect(ui.Outputs()).To(ContainSubstrings(
   209  						[]string{"Error getting SSH info", "404"},
   210  					))
   211  
   212  				})
   213  			})
   214  
   215  			Context("error when getting oauth token", func() {
   216  				BeforeEach(func() {
   217  					sshCodeGetter.GetReturns("", errors.New("auth api error"))
   218  
   219  					getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   220  						Method: "GET",
   221  						Path:   "/v2/info",
   222  						Response: testnet.TestResponse{
   223  							Status: http.StatusOK,
   224  							Body:   `{}`,
   225  						},
   226  					})
   227  
   228  					testServer, handler = testnet.NewServer([]testnet.TestRequest{getRequest})
   229  					configRepo.SetAPIEndpoint(testServer.URL)
   230  					ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   231  					deps.Gateways["cloud-controller"] = ccGateway
   232  				})
   233  
   234  				It("notifies users", func() {
   235  					runCommand("my-app")
   236  
   237  					Expect(handler).To(HaveAllRequestsCalled())
   238  					Expect(ui.Outputs()).To(ContainSubstrings(
   239  						[]string{"Error getting one time auth code", "auth api error"},
   240  					))
   241  
   242  				})
   243  			})
   244  		})
   245  
   246  		Describe("Connecting to ssh server", func() {
   247  			var testServer *httptest.Server
   248  
   249  			AfterEach(func() {
   250  				testServer.Close()
   251  			})
   252  
   253  			BeforeEach(func() {
   254  				fakeSecureShell = new(sshfakes.FakeSecureShell)
   255  
   256  				deps.WildcardDependency = fakeSecureShell
   257  
   258  				getRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{
   259  					Method: "GET",
   260  					Path:   "/v2/info",
   261  					Response: testnet.TestResponse{
   262  						Status: http.StatusOK,
   263  						Body:   getInfoResponseBody,
   264  					},
   265  				})
   266  
   267  				testServer, _ = testnet.NewServer([]testnet.TestRequest{getRequest})
   268  				configRepo.SetAPIEndpoint(testServer.URL)
   269  				ccGateway = net.NewCloudControllerGateway(configRepo, time.Now, &testterm.FakeUI{}, new(tracefakes.FakePrinter), "")
   270  				deps.Gateways["cloud-controller"] = ccGateway
   271  			})
   272  
   273  			Context("Error when connecting", func() {
   274  				It("notifies users", func() {
   275  					fakeSecureShell.ConnectReturns(errors.New("dial errorrr"))
   276  
   277  					runCommand("my-app")
   278  
   279  					Expect(ui.Outputs()).To(ContainSubstrings(
   280  						[]string{"Error opening SSH connection", "dial error"},
   281  					))
   282  
   283  				})
   284  			})
   285  
   286  			Context("Error port forwarding when -L is provided", func() {
   287  				It("notifies users", func() {
   288  					fakeSecureShell.LocalPortForwardReturns(errors.New("listen error"))
   289  
   290  					runCommand("my-app", "-L", "8000:localhost:8000")
   291  
   292  					Expect(ui.Outputs()).To(ContainSubstrings(
   293  						[]string{"Error forwarding port", "listen error"},
   294  					))
   295  
   296  				})
   297  			})
   298  
   299  			Context("when -N is provided", func() {
   300  				It("calls secureShell.Wait()", func() {
   301  					fakeSecureShell.ConnectReturns(nil)
   302  					fakeSecureShell.LocalPortForwardReturns(nil)
   303  
   304  					runCommand("my-app", "-N")
   305  
   306  					Expect(fakeSecureShell.WaitCallCount()).To(Equal(1))
   307  				})
   308  			})
   309  
   310  			Context("when -N is provided", func() {
   311  				It("calls secureShell.InteractiveSession()", func() {
   312  					fakeSecureShell.ConnectReturns(nil)
   313  					fakeSecureShell.LocalPortForwardReturns(nil)
   314  
   315  					runCommand("my-app", "-k")
   316  
   317  					Expect(fakeSecureShell.InteractiveSessionCallCount()).To(Equal(1))
   318  				})
   319  			})
   320  
   321  			Context("when Wait() or InteractiveSession() returns error", func() {
   322  
   323  				It("notifities users", func() {
   324  					fakeSecureShell.ConnectReturns(nil)
   325  					fakeSecureShell.LocalPortForwardReturns(nil)
   326  
   327  					fakeSecureShell.InteractiveSessionReturns(errors.New("ssh exit error"))
   328  					runCommand("my-app", "-k")
   329  
   330  					Expect(ui.Outputs()).To(ContainSubstrings(
   331  						[]string{"ssh exit error"},
   332  					))
   333  
   334  				})
   335  			})
   336  		})
   337  	})
   338  })
   339  
   340  const getInfoResponseBody string = `
   341  {
   342     "name": "vcap",
   343     "build": "2222",
   344     "support": "http://support.cloudfoundry.com",
   345     "version": 2,
   346     "description": "Cloud Foundry sponsored by ABC",
   347     "authorization_endpoint": "https://login.run.abc.com",
   348     "token_endpoint": "https://uaa.run.abc.com",
   349     "min_cli_version": null,
   350     "min_recommended_cli_version": null,
   351     "api_version": "2.35.0",
   352     "app_ssh_endpoint": "ssh.run.pivotal.io:2222",
   353     "app_ssh_host_key_fingerprint": "11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11",
   354     "logging_endpoint": "wss://loggregator.run.abc.com:443",
   355     "doppler_logging_endpoint": "wss://doppler.run.abc.com:443",
   356     "user": "6e477566-ac8d-4653-98c6-d319595ec7b0"
   357  }`