github.com/swisscom/cloudfoundry-cli@v7.1.0+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"}`), 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"}`), 394 Response: testnet.TestResponse{Status: http.StatusNotFound}, 395 })) 396 397 err := repo.UpdateServiceInstance("instance-guid", "plan-guid", nil, nil) 398 399 Expect(testHandler).To(HaveAllRequestsCalled()) 400 Expect(err).To(HaveOccurred()) 401 }) 402 }) 403 404 Context("when the user passes arbitrary params", func() { 405 It("passes the parameters in the correct field for the request", func() { 406 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 407 Method: "PUT", 408 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 409 Matcher: testnet.RequestBodyMatcher(`{"parameters": {"foo": "bar"}}`), 410 Response: testnet.TestResponse{Status: http.StatusOK}, 411 })) 412 413 paramsMap := map[string]interface{}{"foo": "bar"} 414 415 err := repo.UpdateServiceInstance("instance-guid", "", paramsMap, nil) 416 Expect(testHandler).To(HaveAllRequestsCalled()) 417 Expect(err).NotTo(HaveOccurred()) 418 }) 419 420 Context("and there is a failure during serialization", func() { 421 It("returns the serialization error", func() { 422 paramsMap := make(map[string]interface{}) 423 paramsMap["data"] = make(chan bool) 424 425 err := repo.UpdateServiceInstance("instance-guid", "", paramsMap, nil) 426 Expect(err).To(MatchError("json: unsupported type: chan bool")) 427 }) 428 }) 429 }) 430 431 Context("when there are tags", func() { 432 It("sends the tags as part of the request body", func() { 433 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 434 Method: "PUT", 435 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 436 Matcher: testnet.RequestBodyMatcher(`{"tags": ["foo", "bar"]}`), 437 Response: testnet.TestResponse{Status: http.StatusOK}, 438 })) 439 440 tags := &[]string{"foo", "bar"} 441 442 err := repo.UpdateServiceInstance("instance-guid", "", nil, tags) 443 Expect(testHandler).To(HaveAllRequestsCalled()) 444 Expect(err).NotTo(HaveOccurred()) 445 }) 446 447 It("sends empty tags", func() { 448 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 449 Method: "PUT", 450 Path: "/v2/service_instances/instance-guid?accepts_incomplete=true", 451 Matcher: testnet.RequestBodyMatcher(`{"tags": []}`), 452 Response: testnet.TestResponse{Status: http.StatusOK}, 453 })) 454 455 tags := &[]string{} 456 457 err := repo.UpdateServiceInstance("instance-guid", "", nil, tags) 458 Expect(testHandler).To(HaveAllRequestsCalled()) 459 Expect(err).NotTo(HaveOccurred()) 460 }) 461 }) 462 }) 463 464 Describe("finding service instances by name", func() { 465 It("returns the service instance", func() { 466 setupTestServer(findServiceInstanceReq, serviceOfferingReq) 467 468 instance, err := repo.FindInstanceByName("my-service") 469 470 Expect(testHandler).To(HaveAllRequestsCalled()) 471 Expect(err).NotTo(HaveOccurred()) 472 473 Expect(instance.Name).To(Equal("my-service")) 474 Expect(instance.GUID).To(Equal("my-service-instance-guid")) 475 Expect(instance.DashboardURL).To(Equal("my-dashboard-url")) 476 Expect(instance.ServiceOffering.Label).To(Equal("mysql")) 477 Expect(instance.ServiceOffering.DocumentationURL).To(Equal("http://info.example.com")) 478 Expect(instance.ServiceOffering.Description).To(Equal("MySQL database")) 479 Expect(instance.ServiceOffering.Requires).To(ContainElement("route_forwarding")) 480 Expect(instance.ServicePlan.Name).To(Equal("plan-name")) 481 Expect(len(instance.ServiceBindings)).To(Equal(2)) 482 483 binding := instance.ServiceBindings[0] 484 Expect(binding.URL).To(Equal("/v2/service_bindings/service-binding-1-guid")) 485 Expect(binding.GUID).To(Equal("service-binding-1-guid")) 486 Expect(binding.AppGUID).To(Equal("app-1-guid")) 487 }) 488 489 It("returns user provided services", func() { 490 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 491 Method: "GET", 492 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 493 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 494 { 495 "resources": [ 496 { 497 "metadata": { 498 "guid": "my-service-instance-guid" 499 }, 500 "entity": { 501 "name": "my-service", 502 "service_bindings": [ 503 { 504 "metadata": { 505 "guid": "service-binding-1-guid", 506 "url": "/v2/service_bindings/service-binding-1-guid" 507 }, 508 "entity": { 509 "app_guid": "app-1-guid" 510 } 511 }, 512 { 513 "metadata": { 514 "guid": "service-binding-2-guid", 515 "url": "/v2/service_bindings/service-binding-2-guid" 516 }, 517 "entity": { 518 "app_guid": "app-2-guid" 519 } 520 } 521 ], 522 "service_plan_guid": null 523 } 524 } 525 ] 526 }`}})) 527 528 instance, err := repo.FindInstanceByName("my-service") 529 530 Expect(testHandler).To(HaveAllRequestsCalled()) 531 Expect(err).NotTo(HaveOccurred()) 532 533 Expect(instance.Name).To(Equal("my-service")) 534 Expect(instance.GUID).To(Equal("my-service-instance-guid")) 535 Expect(instance.ServiceOffering.Label).To(Equal("")) 536 Expect(instance.ServicePlan.Name).To(Equal("")) 537 Expect(len(instance.ServiceBindings)).To(Equal(2)) 538 539 binding := instance.ServiceBindings[0] 540 Expect(binding.URL).To(Equal("/v2/service_bindings/service-binding-1-guid")) 541 Expect(binding.GUID).To(Equal("service-binding-1-guid")) 542 Expect(binding.AppGUID).To(Equal("app-1-guid")) 543 }) 544 545 It("returns a failure response when the instance doesn't exist", func() { 546 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 547 Method: "GET", 548 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 549 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [] }`}, 550 })) 551 552 _, err := repo.FindInstanceByName("my-service") 553 554 Expect(testHandler).To(HaveAllRequestsCalled()) 555 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 556 }) 557 558 It("should not fail to parse when extra is null", func() { 559 setupTestServer(findServiceInstanceReq, serviceOfferingNullExtraReq) 560 561 _, err := repo.FindInstanceByName("my-service") 562 563 Expect(testHandler).To(HaveAllRequestsCalled()) 564 Expect(err).NotTo(HaveOccurred()) 565 }) 566 }) 567 568 Describe("DeleteService", func() { 569 It("deletes the service when no apps and keys are bound", func() { 570 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 571 Method: "DELETE", 572 Path: "/v2/service_instances/my-service-instance-guid?accepts_incomplete=true&async=true", 573 Response: testnet.TestResponse{Status: http.StatusOK}, 574 })) 575 576 serviceInstance := models.ServiceInstance{} 577 serviceInstance.GUID = "my-service-instance-guid" 578 579 err := repo.DeleteService(serviceInstance) 580 Expect(testHandler).To(HaveAllRequestsCalled()) 581 Expect(err).NotTo(HaveOccurred()) 582 }) 583 584 It("doesn't delete the service when apps are bound", func() { 585 setupTestServer() 586 587 serviceInstance := models.ServiceInstance{} 588 serviceInstance.GUID = "my-service-instance-guid" 589 serviceInstance.ServiceBindings = []models.ServiceBindingFields{ 590 { 591 URL: "/v2/service_bindings/service-binding-1-guid", 592 AppGUID: "app-1-guid", 593 }, 594 { 595 URL: "/v2/service_bindings/service-binding-2-guid", 596 AppGUID: "app-2-guid", 597 }, 598 } 599 600 err := repo.DeleteService(serviceInstance) 601 Expect(err).To(HaveOccurred()) 602 Expect(err).To(BeAssignableToTypeOf(&errors.ServiceAssociationError{})) 603 }) 604 605 It("doesn't delete the service when keys are bound", func() { 606 setupTestServer() 607 608 serviceInstance := models.ServiceInstance{} 609 serviceInstance.GUID = "my-service-instance-guid" 610 serviceInstance.ServiceKeys = []models.ServiceKeyFields{ 611 { 612 Name: "fake-service-key-1", 613 URL: "/v2/service_keys/service-key-1-guid", 614 GUID: "service-key-1-guid", 615 }, 616 { 617 Name: "fake-service-key-2", 618 URL: "/v2/service_keys/service-key-2-guid", 619 GUID: "service-key-2-guid", 620 }, 621 } 622 623 err := repo.DeleteService(serviceInstance) 624 Expect(err).To(HaveOccurred()) 625 Expect(err).To(BeAssignableToTypeOf(&errors.ServiceAssociationError{})) 626 }) 627 }) 628 629 Describe("RenameService", func() { 630 Context("when the service is not user provided", func() { 631 632 BeforeEach(func() { 633 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 634 Method: "PUT", 635 Path: "/v2/service_instances/my-service-instance-guid?accepts_incomplete=true", 636 Matcher: testnet.RequestBodyMatcher(`{"name":"new-name"}`), 637 Response: testnet.TestResponse{Status: http.StatusCreated}, 638 })) 639 }) 640 641 It("renames the service", func() { 642 serviceInstance := models.ServiceInstance{} 643 serviceInstance.GUID = "my-service-instance-guid" 644 serviceInstance.ServicePlan = models.ServicePlanFields{ 645 GUID: "some-plan-guid", 646 } 647 648 err := repo.RenameService(serviceInstance, "new-name") 649 Expect(testHandler).To(HaveAllRequestsCalled()) 650 Expect(err).NotTo(HaveOccurred()) 651 }) 652 }) 653 654 Context("when the service is user provided", func() { 655 BeforeEach(func() { 656 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 657 Method: "PUT", 658 Path: "/v2/user_provided_service_instances/my-service-instance-guid", 659 Matcher: testnet.RequestBodyMatcher(`{"name":"new-name"}`), 660 Response: testnet.TestResponse{Status: http.StatusCreated}, 661 })) 662 }) 663 664 It("renames the service", func() { 665 serviceInstance := models.ServiceInstance{} 666 serviceInstance.GUID = "my-service-instance-guid" 667 serviceInstance.Type = "user_provided_service_instance" 668 669 err := repo.RenameService(serviceInstance, "new-name") 670 Expect(testHandler).To(HaveAllRequestsCalled()) 671 Expect(err).NotTo(HaveOccurred()) 672 }) 673 }) 674 }) 675 676 Describe("FindServiceOfferingByLabelAndProvider", func() { 677 Context("when the service offering can be found", func() { 678 BeforeEach(func() { 679 setupTestServer(testnet.TestRequest{ 680 Method: "GET", 681 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 682 Response: testnet.TestResponse{ 683 Status: 200, 684 Body: ` 685 { 686 "next_url": null, 687 "resources": [ 688 { 689 "metadata": { 690 "guid": "offering-1-guid" 691 }, 692 "entity": { 693 "label": "offering-1", 694 "provider": "provider-1", 695 "description": "offering 1 description", 696 "version" : "1.0", 697 "service_plans": [] 698 } 699 } 700 ] 701 }`}}) 702 }) 703 704 It("finds service offerings by label and provider", func() { 705 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 706 Expect(offering.GUID).To(Equal("offering-1-guid")) 707 Expect(err).NotTo(HaveOccurred()) 708 }) 709 }) 710 711 Context("when the service offering cannot be found", func() { 712 BeforeEach(func() { 713 setupTestServer(testnet.TestRequest{ 714 Method: "GET", 715 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 716 Response: testnet.TestResponse{ 717 Status: 200, 718 Body: ` 719 { 720 "next_url": null, 721 "resources": [] 722 }`, 723 }, 724 }) 725 }) 726 It("returns a ModelNotFoundError", func() { 727 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 728 729 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 730 Expect(offering.GUID).To(Equal("")) 731 }) 732 }) 733 734 It("handles api errors when finding service offerings", func() { 735 setupTestServer(testnet.TestRequest{ 736 Method: "GET", 737 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 738 Response: testnet.TestResponse{ 739 Status: 400, 740 Body: ` 741 { 742 "code": 10005, 743 "description": "The query parameter is invalid" 744 }`}}) 745 746 _, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 747 Expect(err).To(HaveOccurred()) 748 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 749 }) 750 }) 751 752 Describe("FindServiceOfferingsByLabel", func() { 753 Context("when the service offering can be found", func() { 754 BeforeEach(func() { 755 setupTestServer(testnet.TestRequest{ 756 Method: "GET", 757 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 758 Response: testnet.TestResponse{ 759 Status: 200, 760 Body: ` 761 { 762 "next_url": null, 763 "resources": [ 764 { 765 "metadata": { 766 "guid": "offering-1-guid" 767 }, 768 "entity": { 769 "label": "offering-1", 770 "provider": "provider-1", 771 "description": "offering 1 description", 772 "version" : "1.0", 773 "service_plans": [], 774 "service_broker_guid": "broker-1-guid" 775 } 776 } 777 ] 778 }`}}) 779 }) 780 781 It("finds service offerings by label", func() { 782 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 783 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 784 Expect(offerings[0].Label).To(Equal("offering-1")) 785 Expect(offerings[0].Provider).To(Equal("provider-1")) 786 Expect(offerings[0].Description).To(Equal("offering 1 description")) 787 Expect(offerings[0].Version).To(Equal("1.0")) 788 Expect(offerings[0].BrokerGUID).To(Equal("broker-1-guid")) 789 Expect(err).NotTo(HaveOccurred()) 790 }) 791 }) 792 793 Context("when the service offering cannot be found", func() { 794 BeforeEach(func() { 795 setupTestServer(testnet.TestRequest{ 796 Method: "GET", 797 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 798 Response: testnet.TestResponse{ 799 Status: 200, 800 Body: ` 801 { 802 "next_url": null, 803 "resources": [] 804 }`, 805 }, 806 }) 807 }) 808 809 It("returns a ModelNotFoundError", func() { 810 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 811 812 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 813 Expect(offerings).To(Equal(models.ServiceOfferings{})) 814 }) 815 }) 816 817 It("handles api errors when finding service offerings", func() { 818 setupTestServer(testnet.TestRequest{ 819 Method: "GET", 820 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 821 Response: testnet.TestResponse{ 822 Status: 400, 823 Body: ` 824 { 825 "code": 10005, 826 "description": "The query parameter is invalid" 827 }`}}) 828 829 _, err := repo.FindServiceOfferingsByLabel("offering-1") 830 Expect(err).To(HaveOccurred()) 831 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 832 }) 833 }) 834 835 Describe("GetServiceOfferingByGUID", func() { 836 Context("when the service offering can be found", func() { 837 BeforeEach(func() { 838 setupTestServer(testnet.TestRequest{ 839 Method: "GET", 840 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 841 Response: testnet.TestResponse{ 842 Status: 200, 843 Body: ` 844 { 845 "metadata": { 846 "guid": "offering-1-guid" 847 }, 848 "entity": { 849 "label": "offering-1", 850 "provider": "provider-1", 851 "description": "offering 1 description", 852 "version" : "1.0", 853 "service_plans": [], 854 "service_broker_guid": "broker-1-guid" 855 } 856 }`}}) 857 }) 858 859 It("finds service offerings by guid", func() { 860 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 861 Expect(offering.GUID).To(Equal("offering-1-guid")) 862 Expect(offering.Label).To(Equal("offering-1")) 863 Expect(offering.Provider).To(Equal("provider-1")) 864 Expect(offering.Description).To(Equal("offering 1 description")) 865 Expect(offering.Version).To(Equal("1.0")) 866 Expect(offering.BrokerGUID).To(Equal("broker-1-guid")) 867 Expect(err).NotTo(HaveOccurred()) 868 }) 869 }) 870 871 Context("when the service offering cannot be found", func() { 872 BeforeEach(func() { 873 setupTestServer(testnet.TestRequest{ 874 Method: "GET", 875 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 876 Response: testnet.TestResponse{ 877 Status: 404, 878 Body: ` 879 { 880 "code": 120003, 881 "description": "The service could not be found: offering-1-guid", 882 "error_code": "CF-ServiceNotFound" 883 }`, 884 }, 885 }) 886 }) 887 888 It("returns a ModelNotFoundError", func() { 889 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 890 891 Expect(err).To(BeAssignableToTypeOf(&errors.HTTPNotFoundError{})) 892 Expect(offering.GUID).To(Equal("")) 893 }) 894 }) 895 }) 896 897 Describe("PurgeServiceOffering", func() { 898 It("purges service offerings", func() { 899 setupTestServer(testnet.TestRequest{ 900 Method: "DELETE", 901 Path: "/v2/services/the-service-guid?purge=true", 902 Response: testnet.TestResponse{ 903 Status: 204, 904 }}) 905 906 offering := models.ServiceOffering{ServiceOfferingFields: models.ServiceOfferingFields{ 907 Label: "the-offering", 908 GUID: "the-service-guid", 909 Description: "some service description", 910 }} 911 offering.GUID = "the-service-guid" 912 913 err := repo.PurgeServiceOffering(offering) 914 Expect(err).NotTo(HaveOccurred()) 915 Expect(testHandler).To(HaveAllRequestsCalled()) 916 }) 917 }) 918 919 Describe("PurgeServiceInstance", func() { 920 It("purges service instances", func() { 921 setupTestServer(testnet.TestRequest{ 922 Method: "DELETE", 923 Path: "/v2/service_instances/instance-guid?purge=true", 924 Response: testnet.TestResponse{ 925 Status: 204, 926 }}) 927 928 instance := models.ServiceInstance{ServiceInstanceFields: models.ServiceInstanceFields{ 929 Name: "schrodinger", 930 GUID: "instance-guid", 931 }} 932 933 err := repo.PurgeServiceInstance(instance) 934 Expect(err).NotTo(HaveOccurred()) 935 Expect(testHandler).To(HaveAllRequestsCalled()) 936 }) 937 }) 938 939 Describe("getting the count of service instances for a service plan", func() { 940 var planGUID = "abc123" 941 942 It("returns the number of service instances", func() { 943 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 944 Method: "GET", 945 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 946 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 947 { 948 "total_results": 9, 949 "total_pages": 9, 950 "prev_url": null, 951 "next_url": "/v2/service_plans/abc123/service_instances?page=2&results-per-page=1", 952 "resources": [ 953 { 954 "metadata": { 955 "guid": "def456", 956 "url": "/v2/service_instances/def456", 957 "created_at": "2013-06-06T02:42:55+00:00", 958 "updated_at": null 959 }, 960 "entity": { 961 "name": "pet-db", 962 "credentials": { "name": "the_name" }, 963 "service_plan_guid": "abc123", 964 "space_guid": "ghi789", 965 "dashboard_url": "https://example.com/dashboard", 966 "type": "managed_service_instance", 967 "space_url": "/v2/spaces/ghi789", 968 "service_plan_url": "/v2/service_plans/abc123", 969 "service_bindings_url": "/v2/service_instances/def456/service_bindings" 970 } 971 } 972 ] 973 } 974 `}, 975 })) 976 977 count, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 978 Expect(count).To(Equal(9)) 979 Expect(err).NotTo(HaveOccurred()) 980 }) 981 982 It("returns the API error when one occurs", func() { 983 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 984 Method: "GET", 985 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 986 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 987 })) 988 989 _, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 990 Expect(err).To(HaveOccurred()) 991 }) 992 }) 993 994 Describe("finding a service plan", func() { 995 var planDescription resources.ServicePlanDescription 996 997 Context("when the service is a v1 service", func() { 998 BeforeEach(func() { 999 planDescription = resources.ServicePlanDescription{ 1000 ServiceLabel: "v1-elephantsql", 1001 ServicePlanName: "v1-panda", 1002 ServiceProvider: "v1-elephantsql", 1003 } 1004 1005 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1006 Method: "GET", 1007 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v1-elephantsql;provider:v1-elephantsql")), 1008 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1009 { 1010 "resources": [ 1011 { 1012 "metadata": { 1013 "guid": "offering-1-guid" 1014 }, 1015 "entity": { 1016 "label": "v1-elephantsql", 1017 "provider": "v1-elephantsql", 1018 "description": "Offering 1 description", 1019 "version" : "1.0", 1020 "service_plans": [ 1021 { 1022 "metadata": {"guid": "offering-1-plan-1-guid"}, 1023 "entity": {"name": "not-the-plan-youre-looking-for"} 1024 }, 1025 { 1026 "metadata": {"guid": "offering-1-plan-2-guid"}, 1027 "entity": {"name": "v1-panda"} 1028 } 1029 ] 1030 } 1031 } 1032 ] 1033 }`}})) 1034 }) 1035 1036 It("returns the plan guid for a v1 plan", func() { 1037 guid, err := repo.FindServicePlanByDescription(planDescription) 1038 1039 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1040 Expect(err).NotTo(HaveOccurred()) 1041 }) 1042 }) 1043 1044 Context("when the service is a v2 service", func() { 1045 BeforeEach(func() { 1046 planDescription = resources.ServicePlanDescription{ 1047 ServiceLabel: "v2-elephantsql", 1048 ServicePlanName: "v2-panda", 1049 } 1050 1051 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1052 Method: "GET", 1053 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-elephantsql;provider:")), 1054 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1055 { 1056 "resources": [ 1057 { 1058 "metadata": { 1059 "guid": "offering-1-guid" 1060 }, 1061 "entity": { 1062 "label": "v2-elephantsql", 1063 "provider": null, 1064 "description": "Offering 1 description", 1065 "version" : "1.0", 1066 "service_plans": [ 1067 { 1068 "metadata": {"guid": "offering-1-plan-1-guid"}, 1069 "entity": {"name": "not-the-plan-youre-looking-for"} 1070 }, 1071 { 1072 "metadata": {"guid": "offering-1-plan-2-guid"}, 1073 "entity": {"name": "v2-panda"} 1074 } 1075 ] 1076 } 1077 } 1078 ] 1079 }`}})) 1080 }) 1081 1082 It("returns the plan guid for a v2 plan", func() { 1083 guid, err := repo.FindServicePlanByDescription(planDescription) 1084 Expect(err).NotTo(HaveOccurred()) 1085 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1086 }) 1087 }) 1088 1089 Context("when no service matches the description", func() { 1090 BeforeEach(func() { 1091 planDescription = resources.ServicePlanDescription{ 1092 ServiceLabel: "v2-service-label", 1093 ServicePlanName: "v2-plan-name", 1094 } 1095 1096 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1097 Method: "GET", 1098 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1099 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [] }`}, 1100 })) 1101 }) 1102 1103 It("returns an error", func() { 1104 _, err := repo.FindServicePlanByDescription(planDescription) 1105 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1106 Expect(err.Error()).To(ContainSubstring("Plan")) 1107 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1108 }) 1109 }) 1110 1111 Context("when the described service has no matching plan", func() { 1112 BeforeEach(func() { 1113 planDescription = resources.ServicePlanDescription{ 1114 ServiceLabel: "v2-service-label", 1115 ServicePlanName: "v2-plan-name", 1116 } 1117 1118 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1119 Method: "GET", 1120 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1121 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1122 { 1123 "resources": [ 1124 { 1125 "metadata": { 1126 "guid": "offering-1-guid" 1127 }, 1128 "entity": { 1129 "label": "v2-elephantsql", 1130 "provider": null, 1131 "description": "Offering 1 description", 1132 "version" : "1.0", 1133 "service_plans": [ 1134 { 1135 "metadata": {"guid": "offering-1-plan-1-guid"}, 1136 "entity": {"name": "not-the-plan-youre-looking-for"} 1137 }, 1138 { 1139 "metadata": {"guid": "offering-1-plan-2-guid"}, 1140 "entity": {"name": "also-not-the-plan-youre-looking-for"} 1141 } 1142 ] 1143 } 1144 } 1145 ] 1146 }`}})) 1147 }) 1148 1149 It("returns a ModelNotFoundError", func() { 1150 _, err := repo.FindServicePlanByDescription(planDescription) 1151 1152 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1153 Expect(err.Error()).To(ContainSubstring("Plan")) 1154 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1155 }) 1156 }) 1157 1158 Context("when we get an HTTP error", func() { 1159 BeforeEach(func() { 1160 planDescription = resources.ServicePlanDescription{ 1161 ServiceLabel: "v2-service-label", 1162 ServicePlanName: "v2-plan-name", 1163 } 1164 1165 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1166 Method: "GET", 1167 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1168 Response: testnet.TestResponse{ 1169 Status: http.StatusInternalServerError, 1170 }})) 1171 }) 1172 1173 It("returns an error", func() { 1174 _, err := repo.FindServicePlanByDescription(planDescription) 1175 1176 Expect(err).To(HaveOccurred()) 1177 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(500, "", ""))) 1178 }) 1179 }) 1180 }) 1181 1182 Describe("migrating service plans", func() { 1183 It("makes a request to CC to migrate the instances from v1 to v2", func() { 1184 setupTestServer(testnet.TestRequest{ 1185 Method: "PUT", 1186 Path: "/v2/service_plans/v1-guid/service_instances", 1187 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1188 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{"changed_count":3}`}, 1189 }) 1190 1191 changedCount, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1192 Expect(err).NotTo(HaveOccurred()) 1193 Expect(changedCount).To(Equal(3)) 1194 }) 1195 1196 It("returns an error when migrating fails", func() { 1197 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1198 Method: "PUT", 1199 Path: "/v2/service_plans/v1-guid/service_instances", 1200 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1201 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 1202 })) 1203 1204 _, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1205 Expect(err).To(HaveOccurred()) 1206 }) 1207 }) 1208 1209 Describe("FindServiceOfferingsForSpaceByLabel", func() { 1210 It("finds service offerings within a space by label", func() { 1211 setupTestServer( 1212 testnet.TestRequest{ 1213 Method: "GET", 1214 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1215 Response: testnet.TestResponse{ 1216 Status: 200, 1217 Body: ` 1218 { 1219 "next_url": "/v2/spaces/my-space-guid/services?q=label%3Aoffering-1&page=2", 1220 "resources": [ 1221 { 1222 "metadata": { 1223 "guid": "offering-1-guid" 1224 }, 1225 "entity": { 1226 "label": "offering-1", 1227 "provider": "provider-1", 1228 "description": "offering 1 description", 1229 "version" : "1.0" 1230 } 1231 } 1232 ] 1233 }`}}, 1234 testnet.TestRequest{ 1235 Method: "GET", 1236 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1237 Response: testnet.TestResponse{ 1238 Status: 200, 1239 Body: ` 1240 { 1241 "next_url": null, 1242 "resources": [ 1243 { 1244 "metadata": { 1245 "guid": "offering-2-guid" 1246 }, 1247 "entity": { 1248 "label": "offering-2", 1249 "provider": "provider-2", 1250 "description": "offering 2 description", 1251 "version" : "1.0" 1252 } 1253 } 1254 ] 1255 }`}}) 1256 1257 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1258 Expect(err).ToNot(HaveOccurred()) 1259 Expect(offerings).To(HaveLen(2)) 1260 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 1261 }) 1262 1263 It("returns an error if the offering cannot be found", func() { 1264 setupTestServer(testnet.TestRequest{ 1265 Method: "GET", 1266 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1267 Response: testnet.TestResponse{ 1268 Status: http.StatusOK, 1269 Body: `{ 1270 "next_url": null, 1271 "resources": [] 1272 }`, 1273 }, 1274 }) 1275 1276 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1277 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1278 Expect(offerings).To(HaveLen(0)) 1279 }) 1280 1281 It("handles api errors when finding service offerings", func() { 1282 setupTestServer(testnet.TestRequest{ 1283 Method: "GET", 1284 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1285 Response: testnet.TestResponse{ 1286 Status: http.StatusBadRequest, 1287 Body: `{ 1288 "code": 9001, 1289 "description": "Something Happened" 1290 }`, 1291 }, 1292 }) 1293 1294 _, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1295 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(400, "", ""))) 1296 }) 1297 1298 Describe("when api returns query by label is invalid", func() { 1299 It("makes a backwards-compatible request", func() { 1300 failedRequestByQueryLabel := testnet.TestRequest{ 1301 Method: "GET", 1302 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:my-service-offering")), 1303 Response: testnet.TestResponse{ 1304 Status: http.StatusBadRequest, 1305 Body: `{"code": 10005,"description": "The query parameter is invalid"}`, 1306 }, 1307 } 1308 1309 firstPaginatedRequest := testnet.TestRequest{ 1310 Method: "GET", 1311 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services"), 1312 Response: testnet.TestResponse{ 1313 Status: http.StatusOK, 1314 Body: `{ 1315 "next_url": "/v2/spaces/my-space-guid/services?page=2", 1316 "resources": [ 1317 { 1318 "metadata": { 1319 "guid": "my-service-offering-guid" 1320 }, 1321 "entity": { 1322 "label": "my-service-offering", 1323 "provider": "some-other-provider", 1324 "description": "a description that does not match your provider", 1325 "version" : "1.0" 1326 } 1327 } 1328 ] 1329 }`, 1330 }, 1331 } 1332 1333 secondPaginatedRequest := testnet.TestRequest{ 1334 Method: "GET", 1335 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?page=2"), 1336 Response: testnet.TestResponse{ 1337 Status: http.StatusOK, 1338 Body: `{"next_url": null, 1339 "resources": [ 1340 { 1341 "metadata": { 1342 "guid": "my-service-offering-guid" 1343 }, 1344 "entity": { 1345 "label": "my-service-offering", 1346 "provider": "my-provider", 1347 "description": "offering 1 description", 1348 "version" : "1.0" 1349 } 1350 } 1351 ]}`, 1352 }, 1353 } 1354 1355 setupTestServer(failedRequestByQueryLabel, firstPaginatedRequest, secondPaginatedRequest) 1356 1357 serviceOfferings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "my-service-offering") 1358 Expect(err).NotTo(HaveOccurred()) 1359 Expect(len(serviceOfferings)).To(Equal(2)) 1360 }) 1361 }) 1362 }) 1363 }) 1364 1365 var firstOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1366 { 1367 "next_url": "/v2/services?page=2", 1368 "resources": [ 1369 { 1370 "metadata": { 1371 "guid": "first-offering-1-guid" 1372 }, 1373 "entity": { 1374 "label": "first-Offering 1", 1375 "provider": "Offering 1 provider", 1376 "description": "first Offering 1 description", 1377 "version" : "1.0" 1378 } 1379 } 1380 ]}`, 1381 } 1382 1383 var firstOfferingsForSpaceResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1384 { 1385 "next_url": "/v2/spaces/my-space-guid/services?inline-relations-depth=1&page=2", 1386 "resources": [ 1387 { 1388 "metadata": { 1389 "guid": "first-offering-1-guid" 1390 }, 1391 "entity": { 1392 "label": "first-Offering 1", 1393 "provider": "Offering 1 provider", 1394 "description": "first Offering 1 description", 1395 "version" : "1.0" 1396 } 1397 } 1398 ]}`, 1399 } 1400 1401 var multipleOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1402 { 1403 "resources": [ 1404 { 1405 "metadata": { 1406 "guid": "offering-1-guid" 1407 }, 1408 "entity": { 1409 "label": "Offering 1", 1410 "provider": "Offering 1 provider", 1411 "description": "Offering 1 description", 1412 "version" : "1.0" 1413 } 1414 }, 1415 { 1416 "metadata": { 1417 "guid": "offering-2-guid" 1418 }, 1419 "entity": { 1420 "label": "Offering 2", 1421 "provider": "Offering 2 provider", 1422 "description": "Offering 2 description", 1423 "version" : "1.5" 1424 } 1425 } 1426 ]}`, 1427 } 1428 1429 var serviceOfferingReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1430 Method: "GET", 1431 Path: "/v2/services/the-service-guid", 1432 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1433 { 1434 "metadata": { 1435 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1436 }, 1437 "entity": { 1438 "label": "mysql", 1439 "provider": "mysql", 1440 "extra": "{\"documentationURL\":\"http://info.example.com\"}", 1441 "description": "MySQL database", 1442 "requires": ["route_forwarding"] 1443 } 1444 }`, 1445 }}) 1446 1447 var serviceOfferingNullExtraReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1448 Method: "GET", 1449 Path: "/v2/services/the-service-guid", 1450 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1451 { 1452 "metadata": { 1453 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1454 }, 1455 "entity": { 1456 "label": "mysql", 1457 "provider": "mysql", 1458 "extra": null, 1459 "description": "MySQL database" 1460 } 1461 }`, 1462 }}) 1463 1464 var findServiceInstanceReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1465 Method: "GET", 1466 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 1467 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1468 {"resources": [ 1469 { 1470 "metadata": { 1471 "guid": "my-service-instance-guid" 1472 }, 1473 "entity": { 1474 "name": "my-service", 1475 "dashboard_url":"my-dashboard-url", 1476 "service_bindings": [ 1477 { 1478 "metadata": { 1479 "guid": "service-binding-1-guid", 1480 "url": "/v2/service_bindings/service-binding-1-guid" 1481 }, 1482 "entity": { 1483 "app_guid": "app-1-guid" 1484 } 1485 }, 1486 { 1487 "metadata": { 1488 "guid": "service-binding-2-guid", 1489 "url": "/v2/service_bindings/service-binding-2-guid" 1490 }, 1491 "entity": { 1492 "app_guid": "app-2-guid" 1493 } 1494 } 1495 ], 1496 "service_plan": { 1497 "metadata": { 1498 "guid": "plan-guid" 1499 }, 1500 "entity": { 1501 "name": "plan-name", 1502 "service_guid": "the-service-guid" 1503 } 1504 } 1505 } 1506 } 1507 ]}`}})