github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/services_test.go (about) 1 package api_test 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "time" 9 10 "code.cloudfoundry.org/cli/cf/api/apifakes" 11 "code.cloudfoundry.org/cli/cf/api/resources" 12 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 13 "code.cloudfoundry.org/cli/cf/errors" 14 "code.cloudfoundry.org/cli/cf/models" 15 "code.cloudfoundry.org/cli/cf/net" 16 "code.cloudfoundry.org/cli/cf/terminal/terminalfakes" 17 "code.cloudfoundry.org/cli/cf/trace/tracefakes" 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" 22 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 ) 26 27 var _ = Describe("Services Repo", func() { 28 var ( 29 testServer *httptest.Server 30 testHandler *testnet.TestHandler 31 configRepo coreconfig.ReadWriter 32 repo ServiceRepository 33 ) 34 35 setupTestServer := func(reqs ...testnet.TestRequest) { 36 testServer, testHandler = testnet.NewServer(reqs) 37 configRepo.SetAPIEndpoint(testServer.URL) 38 } 39 40 BeforeEach(func() { 41 configRepo = testconfig.NewRepositoryWithDefaults() 42 configRepo.SetAccessToken("BEARER my_access_token") 43 44 gateway := net.NewCloudControllerGateway(configRepo, time.Now, new(terminalfakes.FakeUI), new(tracefakes.FakePrinter), "") 45 repo = NewCloudControllerServiceRepository(configRepo, gateway) 46 }) 47 48 AfterEach(func() { 49 if testServer != nil { 50 testServer.Close() 51 } 52 }) 53 54 Describe("GetAllServiceOfferings", func() { 55 BeforeEach(func() { 56 setupTestServer( 57 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 58 Method: "GET", 59 Path: "/v2/services", 60 Response: firstOfferingsResponse, 61 }), 62 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 63 Method: "GET", 64 Path: "/v2/services", 65 Response: multipleOfferingsResponse, 66 }), 67 ) 68 }) 69 70 It("gets all public service offerings", func() { 71 offerings, err := repo.GetAllServiceOfferings() 72 73 Expect(testHandler).To(HaveAllRequestsCalled()) 74 Expect(err).NotTo(HaveOccurred()) 75 Expect(len(offerings)).To(Equal(3)) 76 77 firstOffering := offerings[0] 78 Expect(firstOffering.Label).To(Equal("first-Offering 1")) 79 Expect(firstOffering.Version).To(Equal("1.0")) 80 Expect(firstOffering.Description).To(Equal("first Offering 1 description")) 81 Expect(firstOffering.Provider).To(Equal("Offering 1 provider")) 82 Expect(firstOffering.GUID).To(Equal("first-offering-1-guid")) 83 }) 84 }) 85 86 Describe("GetServiceOfferingsForSpace", func() { 87 It("gets all service offerings in a given space", func() { 88 setupTestServer( 89 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 90 Method: "GET", 91 Path: "/v2/spaces/my-space-guid/services", 92 Response: firstOfferingsForSpaceResponse, 93 }), 94 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 95 Method: "GET", 96 Path: "/v2/spaces/my-space-guid/services", 97 Response: multipleOfferingsResponse, 98 })) 99 100 offerings, err := repo.GetServiceOfferingsForSpace("my-space-guid") 101 102 Expect(testHandler).To(HaveAllRequestsCalled()) 103 Expect(err).NotTo(HaveOccurred()) 104 105 Expect(len(offerings)).To(Equal(3)) 106 107 firstOffering := offerings[0] 108 Expect(firstOffering.Label).To(Equal("first-Offering 1")) 109 Expect(firstOffering.Version).To(Equal("1.0")) 110 Expect(firstOffering.Description).To(Equal("first Offering 1 description")) 111 Expect(firstOffering.Provider).To(Equal("Offering 1 provider")) 112 Expect(firstOffering.GUID).To(Equal("first-offering-1-guid")) 113 Expect(len(firstOffering.Plans)).To(Equal(0)) 114 115 secondOffering := offerings[1] 116 Expect(secondOffering.Label).To(Equal("Offering 1")) 117 Expect(secondOffering.Version).To(Equal("1.0")) 118 Expect(secondOffering.Description).To(Equal("Offering 1 description")) 119 Expect(secondOffering.Provider).To(Equal("Offering 1 provider")) 120 Expect(secondOffering.GUID).To(Equal("offering-1-guid")) 121 Expect(len(secondOffering.Plans)).To(Equal(0)) 122 }) 123 }) 124 125 Describe("find by service broker", func() { 126 BeforeEach(func() { 127 body1 := ` 128 { 129 "total_results": 2, 130 "total_pages": 2, 131 "prev_url": null, 132 "next_url": "/v2/services?q=service_broker_guid%3Amy-service-broker-guid&page=2", 133 "resources": [ 134 { 135 "metadata": { 136 "guid": "my-service-guid" 137 }, 138 "entity": { 139 "label": "my-service", 140 "provider": "androsterone-ensphere", 141 "description": "Dummy addon that is cool", 142 "version": "damageableness-preheat" 143 } 144 } 145 ] 146 }` 147 body2 := ` 148 { 149 "total_results": 1, 150 "total_pages": 1, 151 "next_url": null, 152 "resources": [ 153 { 154 "metadata": { 155 "guid": "my-service-guid2" 156 }, 157 "entity": { 158 "label": "my-service2", 159 "provider": "androsterone-ensphere", 160 "description": "Dummy addon that is cooler", 161 "version": "seraphine-lowdah" 162 } 163 } 164 ] 165 }` 166 167 setupTestServer( 168 apifakes.NewCloudControllerTestRequest( 169 testnet.TestRequest{ 170 Method: "GET", 171 Path: "/v2/services?q=service_broker_guid%3Amy-service-broker-guid", 172 Response: testnet.TestResponse{Status: http.StatusOK, Body: body1}, 173 }), 174 apifakes.NewCloudControllerTestRequest( 175 testnet.TestRequest{ 176 Method: "GET", 177 Path: "/v2/services?q=service_broker_guid%3Amy-service-broker-guid", 178 Response: testnet.TestResponse{Status: http.StatusOK, Body: body2}, 179 }), 180 ) 181 }) 182 183 It("returns the service brokers services", func() { 184 services, err := repo.ListServicesFromBroker("my-service-broker-guid") 185 186 Expect(err).NotTo(HaveOccurred()) 187 Expect(testHandler).To(HaveAllRequestsCalled()) 188 Expect(len(services)).To(Equal(2)) 189 190 Expect(services[0].GUID).To(Equal("my-service-guid")) 191 Expect(services[1].GUID).To(Equal("my-service-guid2")) 192 }) 193 }) 194 195 Describe("returning services for many brokers", func() { 196 path1 := "/v2/services?q=service_broker_guid%20IN%20my-service-broker-guid,my-service-broker-guid2" 197 body1 := ` 198 { 199 "total_results": 2, 200 "total_pages": 2, 201 "prev_url": null, 202 "next_url": "/v2/services?q=service_broker_guid%20IN%20my-service-broker-guid,my-service-broker-guid2&page=2", 203 "resources": [ 204 { 205 "metadata": { 206 "guid": "my-service-guid" 207 }, 208 "entity": { 209 "label": "my-service", 210 "provider": "androsterone-ensphere", 211 "description": "Dummy addon that is cool", 212 "version": "damageableness-preheat" 213 } 214 } 215 ] 216 }` 217 path2 := "/v2/services?q=service_broker_guid%20IN%20my-service-broker-guid,my-service-broker-guid2&page=2" 218 body2 := ` 219 { 220 "total_results": 2, 221 "total_pages": 2, 222 "prev_url": "/v2/services?q=service_broker_guid%20IN%20my-service-broker-guid,my-service-broker-guid2", 223 "next_url": null, 224 "resources": [ 225 { 226 "metadata": { 227 "guid": "my-service-guid2" 228 }, 229 "entity": { 230 "label": "my-service2", 231 "provider": "androsterone-ensphere", 232 "description": "Dummy addon that is cool", 233 "version": "damageableness-preheat" 234 } 235 } 236 ] 237 }` 238 BeforeEach(func() { 239 setupTestServer( 240 apifakes.NewCloudControllerTestRequest( 241 testnet.TestRequest{ 242 Method: "GET", 243 Path: path1, 244 Response: testnet.TestResponse{Status: http.StatusOK, Body: body1}, 245 }), 246 apifakes.NewCloudControllerTestRequest( 247 testnet.TestRequest{ 248 Method: "GET", 249 Path: path2, 250 Response: testnet.TestResponse{Status: http.StatusOK, Body: body2}, 251 }), 252 ) 253 }) 254 255 It("returns the service brokers services", func() { 256 brokerGUIDs := []string{"my-service-broker-guid", "my-service-broker-guid2"} 257 services, err := repo.ListServicesFromManyBrokers(brokerGUIDs) 258 259 Expect(err).NotTo(HaveOccurred()) 260 Expect(testHandler).To(HaveAllRequestsCalled()) 261 Expect(len(services)).To(Equal(2)) 262 263 Expect(services[0].GUID).To(Equal("my-service-guid")) 264 Expect(services[1].GUID).To(Equal("my-service-guid2")) 265 }) 266 }) 267 268 Describe("creating a service instance", func() { 269 It("makes the right request", func() { 270 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 271 Method: "POST", 272 Path: "/v2/service_instances?accepts_incomplete=true", 273 Matcher: testnet.RequestBodyMatcher(`{"name":"instance-name","service_plan_guid":"plan-guid","space_guid":"my-space-guid"}`), 274 Response: testnet.TestResponse{Status: http.StatusCreated}, 275 })) 276 277 err := repo.CreateServiceInstance("instance-name", "plan-guid", nil, nil) 278 Expect(testHandler).To(HaveAllRequestsCalled()) 279 Expect(err).NotTo(HaveOccurred()) 280 }) 281 282 Context("when there are parameters", func() { 283 It("sends the parameters as part of the request body", func() { 284 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 285 Method: "POST", 286 Path: "/v2/service_instances?accepts_incomplete=true", 287 Matcher: testnet.RequestBodyMatcher(`{"name":"instance-name","service_plan_guid":"plan-guid","space_guid":"my-space-guid","parameters": {"data": "hello"}}`), 288 Response: testnet.TestResponse{Status: http.StatusCreated}, 289 })) 290 291 paramsMap := make(map[string]interface{}) 292 paramsMap["data"] = "hello" 293 294 err := repo.CreateServiceInstance("instance-name", "plan-guid", paramsMap, nil) 295 Expect(testHandler).To(HaveAllRequestsCalled()) 296 Expect(err).NotTo(HaveOccurred()) 297 }) 298 299 Context("and there is a failure during serialization", func() { 300 It("returns the serialization error", func() { 301 paramsMap := make(map[string]interface{}) 302 paramsMap["data"] = make(chan bool) 303 304 err := repo.CreateServiceInstance("instance-name", "plan-guid", paramsMap, nil) 305 Expect(err).To(MatchError("json: unsupported type: chan bool")) 306 }) 307 }) 308 }) 309 310 Context("when there are tags", func() { 311 It("sends the tags as part of the request body", func() { 312 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 313 Method: "POST", 314 Path: "/v2/service_instances?accepts_incomplete=true", 315 Matcher: testnet.RequestBodyMatcher(`{"name":"instance-name","service_plan_guid":"plan-guid","space_guid":"my-space-guid","tags": ["foo", "bar"]}`), 316 Response: testnet.TestResponse{Status: http.StatusCreated}, 317 })) 318 319 tags := []string{"foo", "bar"} 320 321 err := repo.CreateServiceInstance("instance-name", "plan-guid", nil, tags) 322 Expect(testHandler).To(HaveAllRequestsCalled()) 323 Expect(err).NotTo(HaveOccurred()) 324 }) 325 }) 326 327 Context("when the name is taken but an identical service exists", func() { 328 BeforeEach(func() { 329 setupTestServer( 330 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 331 Method: "POST", 332 Path: "/v2/service_instances?accepts_incomplete=true", 333 Matcher: testnet.RequestBodyMatcher(`{"name":"my-service","service_plan_guid":"plan-guid","space_guid":"my-space-guid"}`), 334 Response: testnet.TestResponse{ 335 Status: http.StatusBadRequest, 336 Body: `{"code":60002,"description":"The service instance name is taken: my-service"}`, 337 }}), 338 findServiceInstanceReq, 339 serviceOfferingReq) 340 }) 341 342 It("returns a ModelAlreadyExistsError if the plan is the same", func() { 343 err := repo.CreateServiceInstance("my-service", "plan-guid", nil, nil) 344 Expect(testHandler).To(HaveAllRequestsCalled()) 345 Expect(err).To(BeAssignableToTypeOf(&errors.ModelAlreadyExistsError{})) 346 }) 347 }) 348 349 Context("when the name is taken and no identical service instance exists", func() { 350 BeforeEach(func() { 351 setupTestServer( 352 apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 353 Method: "POST", 354 Path: "/v2/service_instances?accepts_incomplete=true", 355 Matcher: testnet.RequestBodyMatcher(`{"name":"my-service","service_plan_guid":"different-plan-guid","space_guid":"my-space-guid"}`), 356 Response: testnet.TestResponse{ 357 Status: http.StatusBadRequest, 358 Body: `{"code":60002,"description":"The service instance name is taken: my-service"}`, 359 }}), 360 findServiceInstanceReq, 361 serviceOfferingReq) 362 }) 363 364 It("fails if the plan is different", func() { 365 err := repo.CreateServiceInstance("my-service", "different-plan-guid", nil, nil) 366 367 Expect(testHandler).To(HaveAllRequestsCalled()) 368 Expect(err).To(HaveOccurred()) 369 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(400, "", ""))) 370 }) 371 }) 372 }) 373 374 Describe("UpdateServiceInstance", func() { 375 It("makes the right request", func() { 376 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 377 Method: "PUT", 378 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 379 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"plan-guid", "tags": null}`), 380 Response: testnet.TestResponse{Status: http.StatusOK}, 381 })) 382 383 err := repo.UpdateServiceInstance("instance-guid", "plan-guid", nil, nil) 384 Expect(testHandler).To(HaveAllRequestsCalled()) 385 Expect(err).NotTo(HaveOccurred()) 386 }) 387 388 Context("When the instance or plan is not found", func() { 389 It("fails", func() { 390 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 391 Method: "PUT", 392 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 393 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"plan-guid", "tags": null}`), 394 Response: testnet.TestResponse{Status: http.StatusNotFound}, 395 })) 396 397 err := repo.UpdateServiceInstance("instance-guid", "plan-guid", nil, nil) 398 Expect(testHandler).To(HaveAllRequestsCalled()) 399 Expect(err).To(HaveOccurred()) 400 }) 401 }) 402 403 Context("when the user passes arbitrary params", func() { 404 It("passes the parameters in the correct field for the request", func() { 405 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 406 Method: "PUT", 407 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 408 Matcher: testnet.RequestBodyMatcher(`{"parameters": {"foo": "bar"}, "tags": null}`), 409 Response: testnet.TestResponse{Status: http.StatusOK}, 410 })) 411 412 paramsMap := map[string]interface{}{"foo": "bar"} 413 414 err := repo.UpdateServiceInstance("instance-guid", "", paramsMap, nil) 415 Expect(testHandler).To(HaveAllRequestsCalled()) 416 Expect(err).NotTo(HaveOccurred()) 417 }) 418 419 Context("and there is a failure during serialization", func() { 420 It("returns the serialization error", func() { 421 paramsMap := make(map[string]interface{}) 422 paramsMap["data"] = make(chan bool) 423 424 err := repo.UpdateServiceInstance("instance-guid", "", paramsMap, nil) 425 Expect(err).To(MatchError("json: unsupported type: chan bool")) 426 }) 427 }) 428 }) 429 430 Context("when there are tags", func() { 431 It("sends the tags as part of the request body", func() { 432 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 433 Method: "PUT", 434 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 435 Matcher: testnet.RequestBodyMatcher(`{"tags": ["foo", "bar"]}`), 436 Response: testnet.TestResponse{Status: http.StatusOK}, 437 })) 438 439 tags := []string{"foo", "bar"} 440 441 err := repo.UpdateServiceInstance("instance-guid", "", nil, tags) 442 Expect(testHandler).To(HaveAllRequestsCalled()) 443 Expect(err).NotTo(HaveOccurred()) 444 }) 445 446 It("sends empty tags", func() { 447 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 448 Method: "PUT", 449 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 450 Matcher: testnet.RequestBodyMatcher(`{"tags": []}`), 451 Response: testnet.TestResponse{Status: http.StatusOK}, 452 })) 453 454 tags := []string{} 455 456 err := repo.UpdateServiceInstance("instance-guid", "", nil, tags) 457 Expect(testHandler).To(HaveAllRequestsCalled()) 458 Expect(err).NotTo(HaveOccurred()) 459 }) 460 }) 461 }) 462 463 Describe("finding service instances by name", func() { 464 It("returns the service instance", func() { 465 setupTestServer(findServiceInstanceReq, serviceOfferingReq) 466 467 instance, err := repo.FindInstanceByName("my-service") 468 469 Expect(testHandler).To(HaveAllRequestsCalled()) 470 Expect(err).NotTo(HaveOccurred()) 471 472 Expect(instance.Name).To(Equal("my-service")) 473 Expect(instance.GUID).To(Equal("my-service-instance-guid")) 474 Expect(instance.DashboardURL).To(Equal("my-dashboard-url")) 475 Expect(instance.ServiceOffering.Label).To(Equal("mysql")) 476 Expect(instance.ServiceOffering.DocumentationURL).To(Equal("http://info.example.com")) 477 Expect(instance.ServiceOffering.Description).To(Equal("MySQL database")) 478 Expect(instance.ServiceOffering.Requires).To(ContainElement("route_forwarding")) 479 Expect(instance.ServicePlan.Name).To(Equal("plan-name")) 480 Expect(len(instance.ServiceBindings)).To(Equal(2)) 481 482 binding := instance.ServiceBindings[0] 483 Expect(binding.URL).To(Equal("/v2/service_bindings/service-binding-1-guid")) 484 Expect(binding.GUID).To(Equal("service-binding-1-guid")) 485 Expect(binding.AppGUID).To(Equal("app-1-guid")) 486 }) 487 488 It("returns user provided services", func() { 489 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 490 Method: "GET", 491 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 492 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 493 { 494 "resources": [ 495 { 496 "metadata": { 497 "guid": "my-service-instance-guid" 498 }, 499 "entity": { 500 "name": "my-service", 501 "service_bindings": [ 502 { 503 "metadata": { 504 "guid": "service-binding-1-guid", 505 "url": "/v2/service_bindings/service-binding-1-guid" 506 }, 507 "entity": { 508 "app_guid": "app-1-guid" 509 } 510 }, 511 { 512 "metadata": { 513 "guid": "service-binding-2-guid", 514 "url": "/v2/service_bindings/service-binding-2-guid" 515 }, 516 "entity": { 517 "app_guid": "app-2-guid" 518 } 519 } 520 ], 521 "service_plan_guid": null 522 } 523 } 524 ] 525 }`}})) 526 527 instance, err := repo.FindInstanceByName("my-service") 528 529 Expect(testHandler).To(HaveAllRequestsCalled()) 530 Expect(err).NotTo(HaveOccurred()) 531 532 Expect(instance.Name).To(Equal("my-service")) 533 Expect(instance.GUID).To(Equal("my-service-instance-guid")) 534 Expect(instance.ServiceOffering.Label).To(Equal("")) 535 Expect(instance.ServicePlan.Name).To(Equal("")) 536 Expect(len(instance.ServiceBindings)).To(Equal(2)) 537 538 binding := instance.ServiceBindings[0] 539 Expect(binding.URL).To(Equal("/v2/service_bindings/service-binding-1-guid")) 540 Expect(binding.GUID).To(Equal("service-binding-1-guid")) 541 Expect(binding.AppGUID).To(Equal("app-1-guid")) 542 }) 543 544 It("returns a failure response when the instance doesn't exist", func() { 545 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 546 Method: "GET", 547 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 548 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [] }`}, 549 })) 550 551 _, err := repo.FindInstanceByName("my-service") 552 553 Expect(testHandler).To(HaveAllRequestsCalled()) 554 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 555 }) 556 557 It("should not fail to parse when extra is null", func() { 558 setupTestServer(findServiceInstanceReq, serviceOfferingNullExtraReq) 559 560 _, err := repo.FindInstanceByName("my-service") 561 562 Expect(testHandler).To(HaveAllRequestsCalled()) 563 Expect(err).NotTo(HaveOccurred()) 564 }) 565 }) 566 567 Describe("DeleteService", func() { 568 It("deletes the service when no apps and keys are bound", func() { 569 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 570 Method: "DELETE", 571 Path: "/v2/service_instances/my-service-instance-guid?accepts_incomplete=true&async=true", 572 Response: testnet.TestResponse{Status: http.StatusOK}, 573 })) 574 575 serviceInstance := models.ServiceInstance{} 576 serviceInstance.GUID = "my-service-instance-guid" 577 578 err := repo.DeleteService(serviceInstance) 579 Expect(testHandler).To(HaveAllRequestsCalled()) 580 Expect(err).NotTo(HaveOccurred()) 581 }) 582 583 It("doesn't delete the service when apps are bound", func() { 584 setupTestServer() 585 586 serviceInstance := models.ServiceInstance{} 587 serviceInstance.GUID = "my-service-instance-guid" 588 serviceInstance.ServiceBindings = []models.ServiceBindingFields{ 589 { 590 URL: "/v2/service_bindings/service-binding-1-guid", 591 AppGUID: "app-1-guid", 592 }, 593 { 594 URL: "/v2/service_bindings/service-binding-2-guid", 595 AppGUID: "app-2-guid", 596 }, 597 } 598 599 err := repo.DeleteService(serviceInstance) 600 Expect(err).To(HaveOccurred()) 601 Expect(err).To(BeAssignableToTypeOf(&errors.ServiceAssociationError{})) 602 }) 603 604 It("doesn't delete the service when keys are bound", func() { 605 setupTestServer() 606 607 serviceInstance := models.ServiceInstance{} 608 serviceInstance.GUID = "my-service-instance-guid" 609 serviceInstance.ServiceKeys = []models.ServiceKeyFields{ 610 { 611 Name: "fake-service-key-1", 612 URL: "/v2/service_keys/service-key-1-guid", 613 GUID: "service-key-1-guid", 614 }, 615 { 616 Name: "fake-service-key-2", 617 URL: "/v2/service_keys/service-key-2-guid", 618 GUID: "service-key-2-guid", 619 }, 620 } 621 622 err := repo.DeleteService(serviceInstance) 623 Expect(err).To(HaveOccurred()) 624 Expect(err).To(BeAssignableToTypeOf(&errors.ServiceAssociationError{})) 625 }) 626 }) 627 628 Describe("RenameService", func() { 629 Context("when the service is not user provided", func() { 630 631 BeforeEach(func() { 632 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 633 Method: "PUT", 634 Path: "/v2/service_instances/my-service-instance-guid?accepts_incomplete=true", 635 Matcher: testnet.RequestBodyMatcher(`{"name":"new-name"}`), 636 Response: testnet.TestResponse{Status: http.StatusCreated}, 637 })) 638 }) 639 640 It("renames the service", func() { 641 serviceInstance := models.ServiceInstance{} 642 serviceInstance.GUID = "my-service-instance-guid" 643 serviceInstance.ServicePlan = models.ServicePlanFields{ 644 GUID: "some-plan-guid", 645 } 646 647 err := repo.RenameService(serviceInstance, "new-name") 648 Expect(testHandler).To(HaveAllRequestsCalled()) 649 Expect(err).NotTo(HaveOccurred()) 650 }) 651 }) 652 653 Context("when the service is user provided", func() { 654 BeforeEach(func() { 655 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 656 Method: "PUT", 657 Path: "/v2/user_provided_service_instances/my-service-instance-guid", 658 Matcher: testnet.RequestBodyMatcher(`{"name":"new-name"}`), 659 Response: testnet.TestResponse{Status: http.StatusCreated}, 660 })) 661 }) 662 663 It("renames the service", func() { 664 serviceInstance := models.ServiceInstance{} 665 serviceInstance.GUID = "my-service-instance-guid" 666 serviceInstance.Type = "user_provided_service_instance" 667 668 err := repo.RenameService(serviceInstance, "new-name") 669 Expect(testHandler).To(HaveAllRequestsCalled()) 670 Expect(err).NotTo(HaveOccurred()) 671 }) 672 }) 673 }) 674 675 Describe("FindServiceOfferingByLabelAndProvider", func() { 676 Context("when the service offering can be found", func() { 677 BeforeEach(func() { 678 setupTestServer(testnet.TestRequest{ 679 Method: "GET", 680 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 681 Response: testnet.TestResponse{ 682 Status: 200, 683 Body: ` 684 { 685 "next_url": null, 686 "resources": [ 687 { 688 "metadata": { 689 "guid": "offering-1-guid" 690 }, 691 "entity": { 692 "label": "offering-1", 693 "provider": "provider-1", 694 "description": "offering 1 description", 695 "version" : "1.0", 696 "service_plans": [] 697 } 698 } 699 ] 700 }`}}) 701 }) 702 703 It("finds service offerings by label and provider", func() { 704 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 705 Expect(offering.GUID).To(Equal("offering-1-guid")) 706 Expect(err).NotTo(HaveOccurred()) 707 }) 708 }) 709 710 Context("when the service offering cannot be found", func() { 711 BeforeEach(func() { 712 setupTestServer(testnet.TestRequest{ 713 Method: "GET", 714 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 715 Response: testnet.TestResponse{ 716 Status: 200, 717 Body: ` 718 { 719 "next_url": null, 720 "resources": [] 721 }`, 722 }, 723 }) 724 }) 725 It("returns a ModelNotFoundError", func() { 726 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 727 728 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 729 Expect(offering.GUID).To(Equal("")) 730 }) 731 }) 732 733 It("handles api errors when finding service offerings", func() { 734 setupTestServer(testnet.TestRequest{ 735 Method: "GET", 736 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 737 Response: testnet.TestResponse{ 738 Status: 400, 739 Body: ` 740 { 741 "code": 10005, 742 "description": "The query parameter is invalid" 743 }`}}) 744 745 _, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 746 Expect(err).To(HaveOccurred()) 747 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 748 }) 749 }) 750 751 Describe("FindServiceOfferingsByLabel", func() { 752 Context("when the service offering can be found", func() { 753 BeforeEach(func() { 754 setupTestServer(testnet.TestRequest{ 755 Method: "GET", 756 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 757 Response: testnet.TestResponse{ 758 Status: 200, 759 Body: ` 760 { 761 "next_url": null, 762 "resources": [ 763 { 764 "metadata": { 765 "guid": "offering-1-guid" 766 }, 767 "entity": { 768 "label": "offering-1", 769 "provider": "provider-1", 770 "description": "offering 1 description", 771 "version" : "1.0", 772 "service_plans": [], 773 "service_broker_guid": "broker-1-guid" 774 } 775 } 776 ] 777 }`}}) 778 }) 779 780 It("finds service offerings by label", func() { 781 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 782 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 783 Expect(offerings[0].Label).To(Equal("offering-1")) 784 Expect(offerings[0].Provider).To(Equal("provider-1")) 785 Expect(offerings[0].Description).To(Equal("offering 1 description")) 786 Expect(offerings[0].Version).To(Equal("1.0")) 787 Expect(offerings[0].BrokerGUID).To(Equal("broker-1-guid")) 788 Expect(err).NotTo(HaveOccurred()) 789 }) 790 }) 791 792 Context("when the service offering cannot be found", func() { 793 BeforeEach(func() { 794 setupTestServer(testnet.TestRequest{ 795 Method: "GET", 796 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 797 Response: testnet.TestResponse{ 798 Status: 200, 799 Body: ` 800 { 801 "next_url": null, 802 "resources": [] 803 }`, 804 }, 805 }) 806 }) 807 808 It("returns a ModelNotFoundError", func() { 809 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 810 811 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 812 Expect(offerings).To(Equal(models.ServiceOfferings{})) 813 }) 814 }) 815 816 It("handles api errors when finding service offerings", func() { 817 setupTestServer(testnet.TestRequest{ 818 Method: "GET", 819 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 820 Response: testnet.TestResponse{ 821 Status: 400, 822 Body: ` 823 { 824 "code": 10005, 825 "description": "The query parameter is invalid" 826 }`}}) 827 828 _, err := repo.FindServiceOfferingsByLabel("offering-1") 829 Expect(err).To(HaveOccurred()) 830 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 831 }) 832 }) 833 834 Describe("GetServiceOfferingByGUID", func() { 835 Context("when the service offering can be found", func() { 836 BeforeEach(func() { 837 setupTestServer(testnet.TestRequest{ 838 Method: "GET", 839 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 840 Response: testnet.TestResponse{ 841 Status: 200, 842 Body: ` 843 { 844 "metadata": { 845 "guid": "offering-1-guid" 846 }, 847 "entity": { 848 "label": "offering-1", 849 "provider": "provider-1", 850 "description": "offering 1 description", 851 "version" : "1.0", 852 "service_plans": [], 853 "service_broker_guid": "broker-1-guid" 854 } 855 }`}}) 856 }) 857 858 It("finds service offerings by guid", func() { 859 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 860 Expect(offering.GUID).To(Equal("offering-1-guid")) 861 Expect(offering.Label).To(Equal("offering-1")) 862 Expect(offering.Provider).To(Equal("provider-1")) 863 Expect(offering.Description).To(Equal("offering 1 description")) 864 Expect(offering.Version).To(Equal("1.0")) 865 Expect(offering.BrokerGUID).To(Equal("broker-1-guid")) 866 Expect(err).NotTo(HaveOccurred()) 867 }) 868 }) 869 870 Context("when the service offering cannot be found", func() { 871 BeforeEach(func() { 872 setupTestServer(testnet.TestRequest{ 873 Method: "GET", 874 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 875 Response: testnet.TestResponse{ 876 Status: 404, 877 Body: ` 878 { 879 "code": 120003, 880 "description": "The service could not be found: offering-1-guid", 881 "error_code": "CF-ServiceNotFound" 882 }`, 883 }, 884 }) 885 }) 886 887 It("returns a ModelNotFoundError", func() { 888 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 889 890 Expect(err).To(BeAssignableToTypeOf(&errors.HTTPNotFoundError{})) 891 Expect(offering.GUID).To(Equal("")) 892 }) 893 }) 894 }) 895 896 Describe("PurgeServiceOffering", func() { 897 It("purges service offerings", func() { 898 setupTestServer(testnet.TestRequest{ 899 Method: "DELETE", 900 Path: "/v2/services/the-service-guid?purge=true", 901 Response: testnet.TestResponse{ 902 Status: 204, 903 }}) 904 905 offering := models.ServiceOffering{ServiceOfferingFields: models.ServiceOfferingFields{ 906 Label: "the-offering", 907 GUID: "the-service-guid", 908 Description: "some service description", 909 }} 910 offering.GUID = "the-service-guid" 911 912 err := repo.PurgeServiceOffering(offering) 913 Expect(err).NotTo(HaveOccurred()) 914 Expect(testHandler).To(HaveAllRequestsCalled()) 915 }) 916 }) 917 918 Describe("PurgeServiceInstance", func() { 919 It("purges service instances", func() { 920 setupTestServer(testnet.TestRequest{ 921 Method: "DELETE", 922 Path: "/v2/service_instances/instance-guid?purge=true", 923 Response: testnet.TestResponse{ 924 Status: 204, 925 }}) 926 927 instance := models.ServiceInstance{ServiceInstanceFields: models.ServiceInstanceFields{ 928 Name: "schrodinger", 929 GUID: "instance-guid", 930 }} 931 932 err := repo.PurgeServiceInstance(instance) 933 Expect(err).NotTo(HaveOccurred()) 934 Expect(testHandler).To(HaveAllRequestsCalled()) 935 }) 936 }) 937 938 Describe("getting the count of service instances for a service plan", func() { 939 var planGUID = "abc123" 940 941 It("returns the number of service instances", func() { 942 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 943 Method: "GET", 944 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 945 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 946 { 947 "total_results": 9, 948 "total_pages": 9, 949 "prev_url": null, 950 "next_url": "/v2/service_plans/abc123/service_instances?page=2&results-per-page=1", 951 "resources": [ 952 { 953 "metadata": { 954 "guid": "def456", 955 "url": "/v2/service_instances/def456", 956 "created_at": "2013-06-06T02:42:55+00:00", 957 "updated_at": null 958 }, 959 "entity": { 960 "name": "pet-db", 961 "credentials": { "name": "the_name" }, 962 "service_plan_guid": "abc123", 963 "space_guid": "ghi789", 964 "dashboard_url": "https://example.com/dashboard", 965 "type": "managed_service_instance", 966 "space_url": "/v2/spaces/ghi789", 967 "service_plan_url": "/v2/service_plans/abc123", 968 "service_bindings_url": "/v2/service_instances/def456/service_bindings" 969 } 970 } 971 ] 972 } 973 `}, 974 })) 975 976 count, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 977 Expect(count).To(Equal(9)) 978 Expect(err).NotTo(HaveOccurred()) 979 }) 980 981 It("returns the API error when one occurs", func() { 982 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 983 Method: "GET", 984 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 985 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 986 })) 987 988 _, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 989 Expect(err).To(HaveOccurred()) 990 }) 991 }) 992 993 Describe("finding a service plan", func() { 994 var planDescription resources.ServicePlanDescription 995 996 Context("when the service is a v1 service", func() { 997 BeforeEach(func() { 998 planDescription = resources.ServicePlanDescription{ 999 ServiceLabel: "v1-elephantsql", 1000 ServicePlanName: "v1-panda", 1001 ServiceProvider: "v1-elephantsql", 1002 } 1003 1004 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1005 Method: "GET", 1006 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v1-elephantsql;provider:v1-elephantsql")), 1007 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1008 { 1009 "resources": [ 1010 { 1011 "metadata": { 1012 "guid": "offering-1-guid" 1013 }, 1014 "entity": { 1015 "label": "v1-elephantsql", 1016 "provider": "v1-elephantsql", 1017 "description": "Offering 1 description", 1018 "version" : "1.0", 1019 "service_plans": [ 1020 { 1021 "metadata": {"guid": "offering-1-plan-1-guid"}, 1022 "entity": {"name": "not-the-plan-youre-looking-for"} 1023 }, 1024 { 1025 "metadata": {"guid": "offering-1-plan-2-guid"}, 1026 "entity": {"name": "v1-panda"} 1027 } 1028 ] 1029 } 1030 } 1031 ] 1032 }`}})) 1033 }) 1034 1035 It("returns the plan guid for a v1 plan", func() { 1036 guid, err := repo.FindServicePlanByDescription(planDescription) 1037 1038 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1039 Expect(err).NotTo(HaveOccurred()) 1040 }) 1041 }) 1042 1043 Context("when the service is a v2 service", func() { 1044 BeforeEach(func() { 1045 planDescription = resources.ServicePlanDescription{ 1046 ServiceLabel: "v2-elephantsql", 1047 ServicePlanName: "v2-panda", 1048 } 1049 1050 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1051 Method: "GET", 1052 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-elephantsql;provider:")), 1053 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1054 { 1055 "resources": [ 1056 { 1057 "metadata": { 1058 "guid": "offering-1-guid" 1059 }, 1060 "entity": { 1061 "label": "v2-elephantsql", 1062 "provider": null, 1063 "description": "Offering 1 description", 1064 "version" : "1.0", 1065 "service_plans": [ 1066 { 1067 "metadata": {"guid": "offering-1-plan-1-guid"}, 1068 "entity": {"name": "not-the-plan-youre-looking-for"} 1069 }, 1070 { 1071 "metadata": {"guid": "offering-1-plan-2-guid"}, 1072 "entity": {"name": "v2-panda"} 1073 } 1074 ] 1075 } 1076 } 1077 ] 1078 }`}})) 1079 }) 1080 1081 It("returns the plan guid for a v2 plan", func() { 1082 guid, err := repo.FindServicePlanByDescription(planDescription) 1083 Expect(err).NotTo(HaveOccurred()) 1084 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1085 }) 1086 }) 1087 1088 Context("when no service matches the description", func() { 1089 BeforeEach(func() { 1090 planDescription = resources.ServicePlanDescription{ 1091 ServiceLabel: "v2-service-label", 1092 ServicePlanName: "v2-plan-name", 1093 } 1094 1095 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1096 Method: "GET", 1097 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1098 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [] }`}, 1099 })) 1100 }) 1101 1102 It("returns an error", func() { 1103 _, err := repo.FindServicePlanByDescription(planDescription) 1104 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1105 Expect(err.Error()).To(ContainSubstring("Plan")) 1106 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1107 }) 1108 }) 1109 1110 Context("when the described service has no matching plan", func() { 1111 BeforeEach(func() { 1112 planDescription = resources.ServicePlanDescription{ 1113 ServiceLabel: "v2-service-label", 1114 ServicePlanName: "v2-plan-name", 1115 } 1116 1117 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1118 Method: "GET", 1119 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1120 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1121 { 1122 "resources": [ 1123 { 1124 "metadata": { 1125 "guid": "offering-1-guid" 1126 }, 1127 "entity": { 1128 "label": "v2-elephantsql", 1129 "provider": null, 1130 "description": "Offering 1 description", 1131 "version" : "1.0", 1132 "service_plans": [ 1133 { 1134 "metadata": {"guid": "offering-1-plan-1-guid"}, 1135 "entity": {"name": "not-the-plan-youre-looking-for"} 1136 }, 1137 { 1138 "metadata": {"guid": "offering-1-plan-2-guid"}, 1139 "entity": {"name": "also-not-the-plan-youre-looking-for"} 1140 } 1141 ] 1142 } 1143 } 1144 ] 1145 }`}})) 1146 }) 1147 1148 It("returns a ModelNotFoundError", func() { 1149 _, err := repo.FindServicePlanByDescription(planDescription) 1150 1151 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1152 Expect(err.Error()).To(ContainSubstring("Plan")) 1153 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1154 }) 1155 }) 1156 1157 Context("when we get an HTTP error", func() { 1158 BeforeEach(func() { 1159 planDescription = resources.ServicePlanDescription{ 1160 ServiceLabel: "v2-service-label", 1161 ServicePlanName: "v2-plan-name", 1162 } 1163 1164 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1165 Method: "GET", 1166 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1167 Response: testnet.TestResponse{ 1168 Status: http.StatusInternalServerError, 1169 }})) 1170 }) 1171 1172 It("returns an error", func() { 1173 _, err := repo.FindServicePlanByDescription(planDescription) 1174 1175 Expect(err).To(HaveOccurred()) 1176 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(500, "", ""))) 1177 }) 1178 }) 1179 }) 1180 1181 Describe("migrating service plans", func() { 1182 It("makes a request to CC to migrate the instances from v1 to v2", func() { 1183 setupTestServer(testnet.TestRequest{ 1184 Method: "PUT", 1185 Path: "/v2/service_plans/v1-guid/service_instances", 1186 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1187 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{"changed_count":3}`}, 1188 }) 1189 1190 changedCount, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1191 Expect(err).NotTo(HaveOccurred()) 1192 Expect(changedCount).To(Equal(3)) 1193 }) 1194 1195 It("returns an error when migrating fails", func() { 1196 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1197 Method: "PUT", 1198 Path: "/v2/service_plans/v1-guid/service_instances", 1199 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1200 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 1201 })) 1202 1203 _, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1204 Expect(err).To(HaveOccurred()) 1205 }) 1206 }) 1207 1208 Describe("FindServiceOfferingsForSpaceByLabel", func() { 1209 It("finds service offerings within a space by label", func() { 1210 setupTestServer( 1211 testnet.TestRequest{ 1212 Method: "GET", 1213 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1214 Response: testnet.TestResponse{ 1215 Status: 200, 1216 Body: ` 1217 { 1218 "next_url": "/v2/spaces/my-space-guid/services?q=label%3Aoffering-1&page=2", 1219 "resources": [ 1220 { 1221 "metadata": { 1222 "guid": "offering-1-guid" 1223 }, 1224 "entity": { 1225 "label": "offering-1", 1226 "provider": "provider-1", 1227 "description": "offering 1 description", 1228 "version" : "1.0" 1229 } 1230 } 1231 ] 1232 }`}}, 1233 testnet.TestRequest{ 1234 Method: "GET", 1235 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1236 Response: testnet.TestResponse{ 1237 Status: 200, 1238 Body: ` 1239 { 1240 "next_url": null, 1241 "resources": [ 1242 { 1243 "metadata": { 1244 "guid": "offering-2-guid" 1245 }, 1246 "entity": { 1247 "label": "offering-2", 1248 "provider": "provider-2", 1249 "description": "offering 2 description", 1250 "version" : "1.0" 1251 } 1252 } 1253 ] 1254 }`}}) 1255 1256 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1257 Expect(err).ToNot(HaveOccurred()) 1258 Expect(offerings).To(HaveLen(2)) 1259 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 1260 }) 1261 1262 It("returns an error if the offering cannot be found", func() { 1263 setupTestServer(testnet.TestRequest{ 1264 Method: "GET", 1265 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1266 Response: testnet.TestResponse{ 1267 Status: http.StatusOK, 1268 Body: `{ 1269 "next_url": null, 1270 "resources": [] 1271 }`, 1272 }, 1273 }) 1274 1275 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1276 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1277 Expect(offerings).To(HaveLen(0)) 1278 }) 1279 1280 It("handles api errors when finding service offerings", func() { 1281 setupTestServer(testnet.TestRequest{ 1282 Method: "GET", 1283 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1284 Response: testnet.TestResponse{ 1285 Status: http.StatusBadRequest, 1286 Body: `{ 1287 "code": 9001, 1288 "description": "Something Happened" 1289 }`, 1290 }, 1291 }) 1292 1293 _, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1294 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(400, "", ""))) 1295 }) 1296 1297 Describe("when api returns query by label is invalid", func() { 1298 It("makes a backwards-compatible request", func() { 1299 failedRequestByQueryLabel := testnet.TestRequest{ 1300 Method: "GET", 1301 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:my-service-offering")), 1302 Response: testnet.TestResponse{ 1303 Status: http.StatusBadRequest, 1304 Body: `{"code": 10005,"description": "The query parameter is invalid"}`, 1305 }, 1306 } 1307 1308 firstPaginatedRequest := testnet.TestRequest{ 1309 Method: "GET", 1310 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services"), 1311 Response: testnet.TestResponse{ 1312 Status: http.StatusOK, 1313 Body: `{ 1314 "next_url": "/v2/spaces/my-space-guid/services?page=2", 1315 "resources": [ 1316 { 1317 "metadata": { 1318 "guid": "my-service-offering-guid" 1319 }, 1320 "entity": { 1321 "label": "my-service-offering", 1322 "provider": "some-other-provider", 1323 "description": "a description that does not match your provider", 1324 "version" : "1.0" 1325 } 1326 } 1327 ] 1328 }`, 1329 }, 1330 } 1331 1332 secondPaginatedRequest := testnet.TestRequest{ 1333 Method: "GET", 1334 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?page=2"), 1335 Response: testnet.TestResponse{ 1336 Status: http.StatusOK, 1337 Body: `{"next_url": null, 1338 "resources": [ 1339 { 1340 "metadata": { 1341 "guid": "my-service-offering-guid" 1342 }, 1343 "entity": { 1344 "label": "my-service-offering", 1345 "provider": "my-provider", 1346 "description": "offering 1 description", 1347 "version" : "1.0" 1348 } 1349 } 1350 ]}`, 1351 }, 1352 } 1353 1354 setupTestServer(failedRequestByQueryLabel, firstPaginatedRequest, secondPaginatedRequest) 1355 1356 serviceOfferings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "my-service-offering") 1357 Expect(err).NotTo(HaveOccurred()) 1358 Expect(len(serviceOfferings)).To(Equal(2)) 1359 }) 1360 }) 1361 }) 1362 }) 1363 1364 var firstOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1365 { 1366 "next_url": "/v2/services?page=2", 1367 "resources": [ 1368 { 1369 "metadata": { 1370 "guid": "first-offering-1-guid" 1371 }, 1372 "entity": { 1373 "label": "first-Offering 1", 1374 "provider": "Offering 1 provider", 1375 "description": "first Offering 1 description", 1376 "version" : "1.0" 1377 } 1378 } 1379 ]}`, 1380 } 1381 1382 var firstOfferingsForSpaceResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1383 { 1384 "next_url": "/v2/spaces/my-space-guid/services?inline-relations-depth=1&page=2", 1385 "resources": [ 1386 { 1387 "metadata": { 1388 "guid": "first-offering-1-guid" 1389 }, 1390 "entity": { 1391 "label": "first-Offering 1", 1392 "provider": "Offering 1 provider", 1393 "description": "first Offering 1 description", 1394 "version" : "1.0" 1395 } 1396 } 1397 ]}`, 1398 } 1399 1400 var multipleOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1401 { 1402 "resources": [ 1403 { 1404 "metadata": { 1405 "guid": "offering-1-guid" 1406 }, 1407 "entity": { 1408 "label": "Offering 1", 1409 "provider": "Offering 1 provider", 1410 "description": "Offering 1 description", 1411 "version" : "1.0" 1412 } 1413 }, 1414 { 1415 "metadata": { 1416 "guid": "offering-2-guid" 1417 }, 1418 "entity": { 1419 "label": "Offering 2", 1420 "provider": "Offering 2 provider", 1421 "description": "Offering 2 description", 1422 "version" : "1.5" 1423 } 1424 } 1425 ]}`, 1426 } 1427 1428 var serviceOfferingReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1429 Method: "GET", 1430 Path: "/v2/services/the-service-guid", 1431 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1432 { 1433 "metadata": { 1434 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1435 }, 1436 "entity": { 1437 "label": "mysql", 1438 "provider": "mysql", 1439 "extra": "{\"documentationURL\":\"http://info.example.com\"}", 1440 "description": "MySQL database", 1441 "requires": ["route_forwarding"] 1442 } 1443 }`, 1444 }}) 1445 1446 var serviceOfferingNullExtraReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1447 Method: "GET", 1448 Path: "/v2/services/the-service-guid", 1449 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1450 { 1451 "metadata": { 1452 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1453 }, 1454 "entity": { 1455 "label": "mysql", 1456 "provider": "mysql", 1457 "extra": null, 1458 "description": "MySQL database" 1459 } 1460 }`, 1461 }}) 1462 1463 var findServiceInstanceReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1464 Method: "GET", 1465 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 1466 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1467 {"resources": [ 1468 { 1469 "metadata": { 1470 "guid": "my-service-instance-guid" 1471 }, 1472 "entity": { 1473 "name": "my-service", 1474 "dashboard_url":"my-dashboard-url", 1475 "service_bindings": [ 1476 { 1477 "metadata": { 1478 "guid": "service-binding-1-guid", 1479 "url": "/v2/service_bindings/service-binding-1-guid" 1480 }, 1481 "entity": { 1482 "app_guid": "app-1-guid" 1483 } 1484 }, 1485 { 1486 "metadata": { 1487 "guid": "service-binding-2-guid", 1488 "url": "/v2/service_bindings/service-binding-2-guid" 1489 }, 1490 "entity": { 1491 "app_guid": "app-2-guid" 1492 } 1493 } 1494 ], 1495 "service_plan": { 1496 "metadata": { 1497 "guid": "plan-guid" 1498 }, 1499 "entity": { 1500 "name": "plan-name", 1501 "service_guid": "the-service-guid" 1502 } 1503 } 1504 } 1505 } 1506 ]}`}})