github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/applications/applications_test.go (about) 1 package applications_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 7 "net/http" 8 "net/http/httptest" 9 "time" 10 11 "code.cloudfoundry.org/cli/cf/api/apifakes" 12 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 13 14 "code.cloudfoundry.org/cli/cf/errors" 15 "code.cloudfoundry.org/cli/cf/models" 16 "code.cloudfoundry.org/cli/cf/net" 17 "code.cloudfoundry.org/cli/cf/terminal/terminalfakes" 18 testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" 19 testnet "code.cloudfoundry.org/cli/cf/util/testhelpers/net" 20 21 . "code.cloudfoundry.org/cli/cf/api/applications" 22 "code.cloudfoundry.org/cli/cf/trace/tracefakes" 23 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 24 . "github.com/onsi/ginkgo" 25 . "github.com/onsi/gomega" 26 "github.com/onsi/gomega/ghttp" 27 ) 28 29 var _ = Describe("ApplicationsRepository", func() { 30 Describe("finding apps by name", func() { 31 It("returns the app when it is found", func() { 32 ts, handler, repo := createAppRepo([]testnet.TestRequest{findAppRequest}) 33 defer ts.Close() 34 35 app, apiErr := repo.Read("My App") 36 Expect(handler).To(HaveAllRequestsCalled()) 37 Expect(apiErr).NotTo(HaveOccurred()) 38 Expect(app.Name).To(Equal("My App")) 39 Expect(app.GUID).To(Equal("app1-guid")) 40 Expect(app.Memory).To(Equal(int64(128))) 41 Expect(app.DiskQuota).To(Equal(int64(512))) 42 Expect(app.InstanceCount).To(Equal(1)) 43 Expect(app.EnvironmentVars).To(Equal(map[string]interface{}{"foo": "bar", "baz": "boom"})) 44 Expect(app.Routes[0].Host).To(Equal("app1")) 45 Expect(app.Routes[0].Domain.Name).To(Equal("cfapps.io")) 46 Expect(app.Stack.Name).To(Equal("awesome-stacks-ahoy")) 47 }) 48 49 It("returns a failure response when the app is not found", func() { 50 request := apifakes.NewCloudControllerTestRequest(findAppRequest) 51 request.Response = testnet.TestResponse{Status: http.StatusOK, Body: `{"resources": []}`} 52 53 ts, handler, repo := createAppRepo([]testnet.TestRequest{request}) 54 defer ts.Close() 55 56 _, apiErr := repo.Read("My App") 57 Expect(handler).To(HaveAllRequestsCalled()) 58 Expect(apiErr.(*errors.ModelNotFoundError)).NotTo(BeNil()) 59 }) 60 }) 61 62 Describe(".GetApp", func() { 63 It("returns an application using the given app guid", func() { 64 request := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 65 Method: "GET", 66 Path: "/v2/apps/app-guid", 67 Response: appModelResponse, 68 }) 69 ts, handler, repo := createAppRepo([]testnet.TestRequest{request}) 70 defer ts.Close() 71 app, err := repo.GetApp("app-guid") 72 73 Expect(err).ToNot(HaveOccurred()) 74 Expect(handler).To(HaveAllRequestsCalled()) 75 Expect(app.Name).To(Equal("My App")) 76 }) 77 }) 78 79 Describe(".GetAppRoutes", func() { 80 var ( 81 testServer *httptest.Server 82 testHandler *testnet.TestHandler 83 configRepo coreconfig.ReadWriter 84 repo Repository 85 appGUID string 86 ) 87 88 AfterEach(func() { 89 testServer.Close() 90 }) 91 92 Context("when there are multiple pages of routes", func() { 93 BeforeEach(func() { 94 appGUID = "my-app-guid" 95 96 paginatedRouteRequest1 := testnet.TestRequest{ 97 Method: "GET", 98 Path: fmt.Sprintf("/v2/apps/%s/routes", appGUID), 99 Response: testnet.TestResponse{ 100 Status: http.StatusOK, 101 Body: `{ 102 "next_url": "/v2/apps/my-app-guid/routes?page=2", 103 "resources": [ 104 { 105 "metadata": { 106 "guid": "route-1-guid" 107 }, 108 "entity": { 109 "host": "my-host", 110 "path": "some-path", 111 "port": null 112 } 113 } 114 ] 115 }`, 116 }, 117 } 118 119 paginatedRouteRequest2 := testnet.TestRequest{ 120 Method: "GET", 121 Path: fmt.Sprintf("/v2/apps/%s/routes?page=2", appGUID), 122 Response: testnet.TestResponse{ 123 Status: http.StatusOK, 124 Body: `{ 125 "next_url": null, 126 "resources": [ 127 { 128 "metadata": { 129 "guid": "route-2-guid" 130 }, 131 "entity": { 132 "host": "my-host", 133 "path": "some-path", 134 "port": null 135 } 136 } 137 ] 138 }`, 139 }, 140 } 141 142 configRepo = testconfig.NewRepositoryWithDefaults() 143 testServer, testHandler = testnet.NewServer([]testnet.TestRequest{paginatedRouteRequest1, paginatedRouteRequest2}) 144 configRepo.SetAPIEndpoint(testServer.URL) 145 gateway := net.NewCloudControllerGateway(configRepo, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "") 146 repo = NewCloudControllerRepository(configRepo, gateway) 147 }) 148 149 It("returns all of the routes", func() { 150 routes, err := repo.GetAppRoutes(appGUID) 151 Expect(err).ToNot(HaveOccurred()) 152 Expect(testHandler).To(HaveAllRequestsCalled()) 153 Expect(len(routes)).To(Equal(2)) 154 155 var GUIDs []string 156 for _, route := range routes { 157 GUIDs = append(GUIDs, route.GUID) 158 } 159 Expect(GUIDs).To(ContainElement("route-1-guid")) 160 Expect(GUIDs).To(ContainElement("route-2-guid")) 161 }) 162 }) 163 164 Context("when an error occurs fetching the routes", func() { 165 BeforeEach(func() { 166 request := testnet.TestRequest{ 167 Method: "GET", 168 Path: fmt.Sprintf("/v2/apps/%s/routes", appGUID), 169 Response: testnet.TestResponse{ 170 Status: http.StatusInternalServerError, 171 Body: "{}", 172 }, 173 } 174 testServer, testHandler, repo = createAppRepo([]testnet.TestRequest{request}) 175 }) 176 177 It("returns the error", func() { 178 routes, err := repo.GetAppRoutes(appGUID) 179 Expect(err).To(HaveOccurred()) 180 Expect(testHandler).To(HaveAllRequestsCalled()) 181 Expect(routes).To(BeEmpty()) 182 }) 183 }) 184 }) 185 186 Describe(".ReadFromSpace", func() { 187 It("returns an application using the given space guid", func() { 188 request := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 189 Method: "GET", 190 Path: "/v2/spaces/another-space-guid/apps?q=name%3AMy+App&inline-relations-depth=1", 191 Response: singleAppResponse, 192 }) 193 ts, handler, repo := createAppRepo([]testnet.TestRequest{request}) 194 defer ts.Close() 195 app, err := repo.ReadFromSpace("My App", "another-space-guid") 196 197 Expect(err).ToNot(HaveOccurred()) 198 Expect(handler).To(HaveAllRequestsCalled()) 199 Expect(app.Name).To(Equal("My App")) 200 }) 201 }) 202 203 Describe("Create", func() { 204 var ( 205 ccServer *ghttp.Server 206 repo CloudControllerRepository 207 appParams models.AppParams 208 ) 209 210 BeforeEach(func() { 211 ccServer = ghttp.NewServer() 212 configRepo := testconfig.NewRepositoryWithDefaults() 213 configRepo.SetAPIEndpoint(ccServer.URL()) 214 gateway := net.NewCloudControllerGateway(configRepo, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "") 215 repo = NewCloudControllerRepository(configRepo, gateway) 216 217 name := "my-cool-app" 218 buildpackURL := "buildpack-url" 219 spaceGUID := "some-space-guid" 220 stackGUID := "some-stack-guid" 221 command := "some-command" 222 memory := int64(2048) 223 diskQuota := int64(512) 224 instanceCount := 3 225 226 appParams = models.AppParams{ 227 Name: &name, 228 BuildpackURL: &buildpackURL, 229 SpaceGUID: &spaceGUID, 230 StackGUID: &stackGUID, 231 Command: &command, 232 Memory: &memory, 233 DiskQuota: &diskQuota, 234 InstanceCount: &instanceCount, 235 } 236 237 ccServer.AppendHandlers( 238 ghttp.CombineHandlers( 239 ghttp.VerifyRequest("POST", "/v2/apps"), 240 ghttp.VerifyJSON(`{ 241 "name":"my-cool-app", 242 "instances":3, 243 "buildpack":"buildpack-url", 244 "memory":2048, 245 "disk_quota": 512, 246 "space_guid":"some-space-guid", 247 "stack_guid":"some-stack-guid", 248 "command":"some-command" 249 }`), 250 ), 251 ) 252 }) 253 254 AfterEach(func() { 255 ccServer.Close() 256 }) 257 258 It("tries to create the app", func() { 259 repo.Create(appParams) 260 Expect(ccServer.ReceivedRequests()).To(HaveLen(1)) 261 }) 262 263 Context("when the create succeeds", func() { 264 BeforeEach(func() { 265 h := ccServer.GetHandler(0) 266 ccServer.SetHandler(0, 267 ghttp.CombineHandlers( 268 h, 269 ghttp.RespondWith(http.StatusCreated, `{ 270 "metadata": { 271 "guid": "my-cool-app-guid" 272 }, 273 "entity": { 274 "name": "my-cool-app" 275 } 276 }`), 277 ), 278 ) 279 }) 280 281 It("returns the application", func() { 282 createdApp, err := repo.Create(appParams) 283 Expect(err).NotTo(HaveOccurred()) 284 285 app := models.Application{} 286 app.Name = "my-cool-app" 287 app.GUID = "my-cool-app-guid" 288 Expect(createdApp).To(Equal(app)) 289 }) 290 }) 291 292 Context("when the create fails", func() { 293 BeforeEach(func() { 294 h := ccServer.GetHandler(0) 295 ccServer.SetHandler(0, 296 ghttp.CombineHandlers( 297 h, 298 ghttp.RespondWith(http.StatusInternalServerError, ""), 299 ), 300 ) 301 }) 302 303 It("returns an error", func() { 304 _, err := repo.Create(appParams) 305 Expect(err).To(HaveOccurred()) 306 }) 307 }) 308 }) 309 310 Describe("reading environment for an app", func() { 311 Context("when the response can be parsed as json", func() { 312 var ( 313 testServer *httptest.Server 314 userEnv *models.Environment 315 err error 316 handler *testnet.TestHandler 317 repo Repository 318 ) 319 320 AfterEach(func() { 321 testServer.Close() 322 }) 323 324 Context("when there are system provided env vars", func() { 325 BeforeEach(func() { 326 327 var appEnvRequest = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 328 Method: "GET", 329 Path: "/v2/apps/some-cool-app-guid/env", 330 Response: testnet.TestResponse{ 331 Status: http.StatusOK, 332 Body: ` 333 { 334 "staging_env_json": { 335 "STAGING_ENV": "staging_value", 336 "staging": true, 337 "number": 42 338 }, 339 "running_env_json": { 340 "RUNNING_ENV": "running_value", 341 "running": false, 342 "number": 37 343 }, 344 "environment_json": { 345 "key": "value", 346 "number": 123, 347 "bool": true 348 }, 349 "system_env_json": { 350 "VCAP_SERVICES": { 351 "system_hash": { 352 "system_key": "system_value" 353 } 354 } 355 } 356 } 357 `, 358 }}) 359 360 testServer, handler, repo = createAppRepo([]testnet.TestRequest{appEnvRequest}) 361 userEnv, err = repo.ReadEnv("some-cool-app-guid") 362 Expect(err).ToNot(HaveOccurred()) 363 Expect(handler).To(HaveAllRequestsCalled()) 364 }) 365 366 It("returns the user environment, vcap services, running/staging env variables", func() { 367 Expect(userEnv.Environment["key"]).To(Equal("value")) 368 Expect(userEnv.Environment["number"]).To(Equal(float64(123))) 369 Expect(userEnv.Environment["bool"]).To(BeTrue()) 370 Expect(userEnv.Running["RUNNING_ENV"]).To(Equal("running_value")) 371 Expect(userEnv.Running["running"]).To(BeFalse()) 372 Expect(userEnv.Running["number"]).To(Equal(float64(37))) 373 Expect(userEnv.Staging["STAGING_ENV"]).To(Equal("staging_value")) 374 Expect(userEnv.Staging["staging"]).To(BeTrue()) 375 Expect(userEnv.Staging["number"]).To(Equal(float64(42))) 376 377 vcapServices := userEnv.System["VCAP_SERVICES"] 378 data, err := json.Marshal(vcapServices) 379 380 Expect(err).ToNot(HaveOccurred()) 381 Expect(string(data)).To(ContainSubstring("\"system_key\":\"system_value\"")) 382 }) 383 384 }) 385 386 Context("when there are no environment variables", func() { 387 BeforeEach(func() { 388 var emptyEnvRequest = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 389 Method: "GET", 390 Path: "/v2/apps/some-cool-app-guid/env", 391 Response: testnet.TestResponse{ 392 Status: http.StatusOK, 393 Body: `{"system_env_json": {"VCAP_SERVICES": {} }}`, 394 }}) 395 396 testServer, handler, repo = createAppRepo([]testnet.TestRequest{emptyEnvRequest}) 397 userEnv, err = repo.ReadEnv("some-cool-app-guid") 398 Expect(err).ToNot(HaveOccurred()) 399 Expect(handler).To(HaveAllRequestsCalled()) 400 }) 401 402 It("returns an empty string", func() { 403 Expect(len(userEnv.Environment)).To(Equal(0)) 404 Expect(len(userEnv.System["VCAP_SERVICES"].(map[string]interface{}))).To(Equal(0)) 405 }) 406 }) 407 }) 408 }) 409 410 Describe("restaging applications", func() { 411 It("POSTs to the right URL", func() { 412 appRestageRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 413 Method: "POST", 414 Path: "/v2/apps/some-cool-app-guid/restage", 415 Response: testnet.TestResponse{ 416 Status: http.StatusOK, 417 Body: "", 418 }, 419 }) 420 421 ts, handler, repo := createAppRepo([]testnet.TestRequest{appRestageRequest}) 422 defer ts.Close() 423 424 repo.CreateRestageRequest("some-cool-app-guid") 425 Expect(handler).To(HaveAllRequestsCalled()) 426 }) 427 }) 428 429 Describe("updating applications", func() { 430 It("makes the right request", func() { 431 ts, handler, repo := createAppRepo([]testnet.TestRequest{updateApplicationRequest}) 432 defer ts.Close() 433 434 app := models.Application{} 435 app.GUID = "my-app-guid" 436 app.Name = "my-cool-app" 437 app.BuildpackURL = "buildpack-url" 438 app.Command = "some-command" 439 app.HealthCheckType = "none" 440 app.Memory = 2048 441 app.InstanceCount = 3 442 app.Stack = &models.Stack{GUID: "some-stack-guid"} 443 app.SpaceGUID = "some-space-guid" 444 app.State = "started" 445 app.DiskQuota = 512 446 Expect(app.EnvironmentVars).To(BeNil()) 447 448 updatedApp, apiErr := repo.Update(app.GUID, app.ToParams()) 449 450 Expect(handler).To(HaveAllRequestsCalled()) 451 Expect(apiErr).NotTo(HaveOccurred()) 452 Expect(updatedApp.Command).To(Equal("some-command")) 453 Expect(updatedApp.DetectedStartCommand).To(Equal("detected command")) 454 Expect(updatedApp.Name).To(Equal("my-cool-app")) 455 Expect(updatedApp.GUID).To(Equal("my-cool-app-guid")) 456 }) 457 458 It("sets environment variables", func() { 459 request := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 460 Method: "PUT", 461 Path: "/v2/apps/app1-guid", 462 Matcher: testnet.RequestBodyMatcher(`{"environment_json":{"DATABASE_URL":"mysql://example.com/my-db"}}`), 463 Response: testnet.TestResponse{Status: http.StatusCreated}, 464 }) 465 466 ts, handler, repo := createAppRepo([]testnet.TestRequest{request}) 467 defer ts.Close() 468 469 envParams := map[string]interface{}{"DATABASE_URL": "mysql://example.com/my-db"} 470 params := models.AppParams{EnvironmentVars: &envParams} 471 472 _, apiErr := repo.Update("app1-guid", params) 473 474 Expect(handler).To(HaveAllRequestsCalled()) 475 Expect(apiErr).NotTo(HaveOccurred()) 476 }) 477 478 It("can remove environment variables", func() { 479 request := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 480 Method: "PUT", 481 Path: "/v2/apps/app1-guid", 482 Matcher: testnet.RequestBodyMatcher(`{"environment_json":{}}`), 483 Response: testnet.TestResponse{Status: http.StatusCreated}, 484 }) 485 486 ts, handler, repo := createAppRepo([]testnet.TestRequest{request}) 487 defer ts.Close() 488 489 envParams := map[string]interface{}{} 490 params := models.AppParams{EnvironmentVars: &envParams} 491 492 _, apiErr := repo.Update("app1-guid", params) 493 494 Expect(handler).To(HaveAllRequestsCalled()) 495 Expect(apiErr).NotTo(HaveOccurred()) 496 }) 497 }) 498 499 It("deletes applications", func() { 500 deleteApplicationRequest := apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 501 Method: "DELETE", 502 Path: "/v2/apps/my-cool-app-guid?recursive=true", 503 Response: testnet.TestResponse{Status: http.StatusOK, Body: ""}, 504 }) 505 506 ts, handler, repo := createAppRepo([]testnet.TestRequest{deleteApplicationRequest}) 507 defer ts.Close() 508 509 apiErr := repo.Delete("my-cool-app-guid") 510 Expect(handler).To(HaveAllRequestsCalled()) 511 Expect(apiErr).NotTo(HaveOccurred()) 512 }) 513 }) 514 515 var appModelResponse = testnet.TestResponse{ 516 Status: http.StatusOK, 517 Body: ` 518 { 519 "metadata": { 520 "guid": "app1-guid" 521 }, 522 "entity": { 523 "name": "My App", 524 "environment_json": { 525 "foo": "bar", 526 "baz": "boom" 527 }, 528 "memory": 128, 529 "instances": 1, 530 "disk_quota": 512, 531 "state": "STOPPED", 532 "stack": { 533 "metadata": { 534 "guid": "app1-route-guid" 535 }, 536 "entity": { 537 "name": "awesome-stacks-ahoy" 538 } 539 }, 540 "routes": [ 541 { 542 "metadata": { 543 "guid": "app1-route-guid" 544 }, 545 "entity": { 546 "host": "app1", 547 "domain": { 548 "metadata": { 549 "guid": "domain1-guid" 550 }, 551 "entity": { 552 "name": "cfapps.io" 553 } 554 } 555 } 556 } 557 ] 558 } 559 } 560 `} 561 562 var singleAppResponse = testnet.TestResponse{ 563 Status: http.StatusOK, 564 Body: ` 565 { 566 "resources": [ 567 { 568 "metadata": { 569 "guid": "app1-guid" 570 }, 571 "entity": { 572 "name": "My App", 573 "environment_json": { 574 "foo": "bar", 575 "baz": "boom" 576 }, 577 "memory": 128, 578 "instances": 1, 579 "disk_quota": 512, 580 "state": "STOPPED", 581 "stack": { 582 "metadata": { 583 "guid": "app1-route-guid" 584 }, 585 "entity": { 586 "name": "awesome-stacks-ahoy" 587 } 588 }, 589 "routes": [ 590 { 591 "metadata": { 592 "guid": "app1-route-guid" 593 }, 594 "entity": { 595 "host": "app1", 596 "domain": { 597 "metadata": { 598 "guid": "domain1-guid" 599 }, 600 "entity": { 601 "name": "cfapps.io" 602 } 603 } 604 } 605 } 606 ] 607 } 608 } 609 ] 610 }`} 611 612 var findAppRequest = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 613 Method: "GET", 614 Path: "/v2/spaces/my-space-guid/apps?q=name%3AMy+App&inline-relations-depth=1", 615 Response: singleAppResponse, 616 }) 617 618 var createApplicationResponse = ` 619 { 620 "metadata": { 621 "guid": "my-cool-app-guid" 622 }, 623 "entity": { 624 "name": "my-cool-app" 625 } 626 }` 627 628 var updateApplicationResponse = ` 629 { 630 "metadata": { 631 "guid": "my-cool-app-guid" 632 }, 633 "entity": { 634 "name": "my-cool-app", 635 "command": "some-command", 636 "detected_start_command": "detected command" 637 } 638 }` 639 640 var updateApplicationRequest = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 641 Method: "PUT", 642 Path: "/v2/apps/my-app-guid?inline-relations-depth=1", 643 Matcher: testnet.RequestBodyMatcher(`{ 644 "name":"my-cool-app", 645 "instances":3, 646 "buildpack":"buildpack-url", 647 "docker_image":"", 648 "memory":2048, 649 "health_check_type":"none", 650 "health_check_http_endpoint":"", 651 "disk_quota":512, 652 "space_guid":"some-space-guid", 653 "state":"STARTED", 654 "stack_guid":"some-stack-guid", 655 "command":"some-command" 656 }`), 657 Response: testnet.TestResponse{ 658 Status: http.StatusOK, 659 Body: updateApplicationResponse}, 660 }) 661 662 func createAppRepo(requests []testnet.TestRequest) (ts *httptest.Server, handler *testnet.TestHandler, repo Repository) { 663 ts, handler = testnet.NewServer(requests) 664 configRepo := testconfig.NewRepositoryWithDefaults() 665 configRepo.SetAPIEndpoint(ts.URL) 666 gateway := net.NewCloudControllerGateway(configRepo, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "") 667 repo = NewCloudControllerRepository(configRepo, gateway) 668 return 669 }