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 }`