github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+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/util/testhelpers/configuration" 19 testnet "code.cloudfoundry.org/cli/util/testhelpers/net" 20 21 . "code.cloudfoundry.org/cli/cf/api" 22 . "code.cloudfoundry.org/cli/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 667 err := repo.RenameService(serviceInstance, "new-name") 668 Expect(testHandler).To(HaveAllRequestsCalled()) 669 Expect(err).NotTo(HaveOccurred()) 670 }) 671 }) 672 }) 673 674 Describe("FindServiceOfferingByLabelAndProvider", func() { 675 Context("when the service offering can be found", func() { 676 BeforeEach(func() { 677 setupTestServer(testnet.TestRequest{ 678 Method: "GET", 679 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 680 Response: testnet.TestResponse{ 681 Status: 200, 682 Body: ` 683 { 684 "next_url": null, 685 "resources": [ 686 { 687 "metadata": { 688 "guid": "offering-1-guid" 689 }, 690 "entity": { 691 "label": "offering-1", 692 "provider": "provider-1", 693 "description": "offering 1 description", 694 "version" : "1.0", 695 "service_plans": [] 696 } 697 } 698 ] 699 }`}}) 700 }) 701 702 It("finds service offerings by label and provider", func() { 703 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 704 Expect(offering.GUID).To(Equal("offering-1-guid")) 705 Expect(err).NotTo(HaveOccurred()) 706 }) 707 }) 708 709 Context("when the service offering cannot be found", func() { 710 BeforeEach(func() { 711 setupTestServer(testnet.TestRequest{ 712 Method: "GET", 713 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 714 Response: testnet.TestResponse{ 715 Status: 200, 716 Body: ` 717 { 718 "next_url": null, 719 "resources": [] 720 }`, 721 }, 722 }) 723 }) 724 It("returns a ModelNotFoundError", func() { 725 offering, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 726 727 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 728 Expect(offering.GUID).To(Equal("")) 729 }) 730 }) 731 732 It("handles api errors when finding service offerings", func() { 733 setupTestServer(testnet.TestRequest{ 734 Method: "GET", 735 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1;provider:provider-1")), 736 Response: testnet.TestResponse{ 737 Status: 400, 738 Body: ` 739 { 740 "code": 10005, 741 "description": "The query parameter is invalid" 742 }`}}) 743 744 _, err := repo.FindServiceOfferingByLabelAndProvider("offering-1", "provider-1") 745 Expect(err).To(HaveOccurred()) 746 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 747 }) 748 }) 749 750 Describe("FindServiceOfferingsByLabel", func() { 751 Context("when the service offering can be found", func() { 752 BeforeEach(func() { 753 setupTestServer(testnet.TestRequest{ 754 Method: "GET", 755 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 756 Response: testnet.TestResponse{ 757 Status: 200, 758 Body: ` 759 { 760 "next_url": null, 761 "resources": [ 762 { 763 "metadata": { 764 "guid": "offering-1-guid" 765 }, 766 "entity": { 767 "label": "offering-1", 768 "provider": "provider-1", 769 "description": "offering 1 description", 770 "version" : "1.0", 771 "service_plans": [], 772 "service_broker_guid": "broker-1-guid" 773 } 774 } 775 ] 776 }`}}) 777 }) 778 779 It("finds service offerings by label", func() { 780 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 781 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 782 Expect(offerings[0].Label).To(Equal("offering-1")) 783 Expect(offerings[0].Provider).To(Equal("provider-1")) 784 Expect(offerings[0].Description).To(Equal("offering 1 description")) 785 Expect(offerings[0].Version).To(Equal("1.0")) 786 Expect(offerings[0].BrokerGUID).To(Equal("broker-1-guid")) 787 Expect(err).NotTo(HaveOccurred()) 788 }) 789 }) 790 791 Context("when the service offering cannot be found", func() { 792 BeforeEach(func() { 793 setupTestServer(testnet.TestRequest{ 794 Method: "GET", 795 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 796 Response: testnet.TestResponse{ 797 Status: 200, 798 Body: ` 799 { 800 "next_url": null, 801 "resources": [] 802 }`, 803 }, 804 }) 805 }) 806 807 It("returns a ModelNotFoundError", func() { 808 offerings, err := repo.FindServiceOfferingsByLabel("offering-1") 809 810 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 811 Expect(offerings).To(Equal(models.ServiceOfferings{})) 812 }) 813 }) 814 815 It("handles api errors when finding service offerings", func() { 816 setupTestServer(testnet.TestRequest{ 817 Method: "GET", 818 Path: fmt.Sprintf("/v2/services?q=%s", url.QueryEscape("label:offering-1")), 819 Response: testnet.TestResponse{ 820 Status: 400, 821 Body: ` 822 { 823 "code": 10005, 824 "description": "The query parameter is invalid" 825 }`}}) 826 827 _, err := repo.FindServiceOfferingsByLabel("offering-1") 828 Expect(err).To(HaveOccurred()) 829 Expect(err.(errors.HTTPError).ErrorCode()).To(Equal("10005")) 830 }) 831 }) 832 833 Describe("GetServiceOfferingByGUID", func() { 834 Context("when the service offering can be found", func() { 835 BeforeEach(func() { 836 setupTestServer(testnet.TestRequest{ 837 Method: "GET", 838 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 839 Response: testnet.TestResponse{ 840 Status: 200, 841 Body: ` 842 { 843 "metadata": { 844 "guid": "offering-1-guid" 845 }, 846 "entity": { 847 "label": "offering-1", 848 "provider": "provider-1", 849 "description": "offering 1 description", 850 "version" : "1.0", 851 "service_plans": [], 852 "service_broker_guid": "broker-1-guid" 853 } 854 }`}}) 855 }) 856 857 It("finds service offerings by guid", func() { 858 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 859 Expect(offering.GUID).To(Equal("offering-1-guid")) 860 Expect(offering.Label).To(Equal("offering-1")) 861 Expect(offering.Provider).To(Equal("provider-1")) 862 Expect(offering.Description).To(Equal("offering 1 description")) 863 Expect(offering.Version).To(Equal("1.0")) 864 Expect(offering.BrokerGUID).To(Equal("broker-1-guid")) 865 Expect(err).NotTo(HaveOccurred()) 866 }) 867 }) 868 869 Context("when the service offering cannot be found", func() { 870 BeforeEach(func() { 871 setupTestServer(testnet.TestRequest{ 872 Method: "GET", 873 Path: fmt.Sprintf("/v2/services/offering-1-guid"), 874 Response: testnet.TestResponse{ 875 Status: 404, 876 Body: ` 877 { 878 "code": 120003, 879 "description": "The service could not be found: offering-1-guid", 880 "error_code": "CF-ServiceNotFound" 881 }`, 882 }, 883 }) 884 }) 885 886 It("returns a ModelNotFoundError", func() { 887 offering, err := repo.GetServiceOfferingByGUID("offering-1-guid") 888 889 Expect(err).To(BeAssignableToTypeOf(&errors.HTTPNotFoundError{})) 890 Expect(offering.GUID).To(Equal("")) 891 }) 892 }) 893 }) 894 895 Describe("PurgeServiceOffering", func() { 896 It("purges service offerings", func() { 897 setupTestServer(testnet.TestRequest{ 898 Method: "DELETE", 899 Path: "/v2/services/the-service-guid?purge=true", 900 Response: testnet.TestResponse{ 901 Status: 204, 902 }}) 903 904 offering := models.ServiceOffering{ServiceOfferingFields: models.ServiceOfferingFields{ 905 Label: "the-offering", 906 GUID: "the-service-guid", 907 Description: "some service description", 908 }} 909 offering.GUID = "the-service-guid" 910 911 err := repo.PurgeServiceOffering(offering) 912 Expect(err).NotTo(HaveOccurred()) 913 Expect(testHandler).To(HaveAllRequestsCalled()) 914 }) 915 }) 916 917 Describe("PurgeServiceInstance", func() { 918 It("purges service instances", func() { 919 setupTestServer(testnet.TestRequest{ 920 Method: "DELETE", 921 Path: "/v2/service_instances/instance-guid?purge=true", 922 Response: testnet.TestResponse{ 923 Status: 204, 924 }}) 925 926 instance := models.ServiceInstance{ServiceInstanceFields: models.ServiceInstanceFields{ 927 Name: "schrodinger", 928 GUID: "instance-guid", 929 }} 930 931 err := repo.PurgeServiceInstance(instance) 932 Expect(err).NotTo(HaveOccurred()) 933 Expect(testHandler).To(HaveAllRequestsCalled()) 934 }) 935 }) 936 937 Describe("getting the count of service instances for a service plan", func() { 938 var planGUID = "abc123" 939 940 It("returns the number of service instances", func() { 941 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 942 Method: "GET", 943 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 944 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 945 { 946 "total_results": 9, 947 "total_pages": 9, 948 "prev_url": null, 949 "next_url": "/v2/service_plans/abc123/service_instances?page=2&results-per-page=1", 950 "resources": [ 951 { 952 "metadata": { 953 "guid": "def456", 954 "url": "/v2/service_instances/def456", 955 "created_at": "2013-06-06T02:42:55+00:00", 956 "updated_at": null 957 }, 958 "entity": { 959 "name": "pet-db", 960 "credentials": { "name": "the_name" }, 961 "service_plan_guid": "abc123", 962 "space_guid": "ghi789", 963 "dashboard_url": "https://example.com/dashboard", 964 "type": "managed_service_instance", 965 "space_url": "/v2/spaces/ghi789", 966 "service_plan_url": "/v2/service_plans/abc123", 967 "service_bindings_url": "/v2/service_instances/def456/service_bindings" 968 } 969 } 970 ] 971 } 972 `}, 973 })) 974 975 count, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 976 Expect(count).To(Equal(9)) 977 Expect(err).NotTo(HaveOccurred()) 978 }) 979 980 It("returns the API error when one occurs", func() { 981 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 982 Method: "GET", 983 Path: fmt.Sprintf("/v2/service_plans/%s/service_instances?results-per-page=1", planGUID), 984 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 985 })) 986 987 _, err := repo.GetServiceInstanceCountForServicePlan(planGUID) 988 Expect(err).To(HaveOccurred()) 989 }) 990 }) 991 992 Describe("finding a service plan", func() { 993 var planDescription resources.ServicePlanDescription 994 995 Context("when the service is a v1 service", func() { 996 BeforeEach(func() { 997 planDescription = resources.ServicePlanDescription{ 998 ServiceLabel: "v1-elephantsql", 999 ServicePlanName: "v1-panda", 1000 ServiceProvider: "v1-elephantsql", 1001 } 1002 1003 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1004 Method: "GET", 1005 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v1-elephantsql;provider:v1-elephantsql")), 1006 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1007 { 1008 "resources": [ 1009 { 1010 "metadata": { 1011 "guid": "offering-1-guid" 1012 }, 1013 "entity": { 1014 "label": "v1-elephantsql", 1015 "provider": "v1-elephantsql", 1016 "description": "Offering 1 description", 1017 "version" : "1.0", 1018 "service_plans": [ 1019 { 1020 "metadata": {"guid": "offering-1-plan-1-guid"}, 1021 "entity": {"name": "not-the-plan-youre-looking-for"} 1022 }, 1023 { 1024 "metadata": {"guid": "offering-1-plan-2-guid"}, 1025 "entity": {"name": "v1-panda"} 1026 } 1027 ] 1028 } 1029 } 1030 ] 1031 }`}})) 1032 }) 1033 1034 It("returns the plan guid for a v1 plan", func() { 1035 guid, err := repo.FindServicePlanByDescription(planDescription) 1036 1037 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1038 Expect(err).NotTo(HaveOccurred()) 1039 }) 1040 }) 1041 1042 Context("when the service is a v2 service", func() { 1043 BeforeEach(func() { 1044 planDescription = resources.ServicePlanDescription{ 1045 ServiceLabel: "v2-elephantsql", 1046 ServicePlanName: "v2-panda", 1047 } 1048 1049 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1050 Method: "GET", 1051 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-elephantsql;provider:")), 1052 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1053 { 1054 "resources": [ 1055 { 1056 "metadata": { 1057 "guid": "offering-1-guid" 1058 }, 1059 "entity": { 1060 "label": "v2-elephantsql", 1061 "provider": null, 1062 "description": "Offering 1 description", 1063 "version" : "1.0", 1064 "service_plans": [ 1065 { 1066 "metadata": {"guid": "offering-1-plan-1-guid"}, 1067 "entity": {"name": "not-the-plan-youre-looking-for"} 1068 }, 1069 { 1070 "metadata": {"guid": "offering-1-plan-2-guid"}, 1071 "entity": {"name": "v2-panda"} 1072 } 1073 ] 1074 } 1075 } 1076 ] 1077 }`}})) 1078 }) 1079 1080 It("returns the plan guid for a v2 plan", func() { 1081 guid, err := repo.FindServicePlanByDescription(planDescription) 1082 Expect(err).NotTo(HaveOccurred()) 1083 Expect(guid).To(Equal("offering-1-plan-2-guid")) 1084 }) 1085 }) 1086 1087 Context("when no service matches the description", func() { 1088 BeforeEach(func() { 1089 planDescription = resources.ServicePlanDescription{ 1090 ServiceLabel: "v2-service-label", 1091 ServicePlanName: "v2-plan-name", 1092 } 1093 1094 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1095 Method: "GET", 1096 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1097 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{ "resources": [] }`}, 1098 })) 1099 }) 1100 1101 It("returns an error", func() { 1102 _, err := repo.FindServicePlanByDescription(planDescription) 1103 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1104 Expect(err.Error()).To(ContainSubstring("Plan")) 1105 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1106 }) 1107 }) 1108 1109 Context("when the described service has no matching plan", func() { 1110 BeforeEach(func() { 1111 planDescription = resources.ServicePlanDescription{ 1112 ServiceLabel: "v2-service-label", 1113 ServicePlanName: "v2-plan-name", 1114 } 1115 1116 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1117 Method: "GET", 1118 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1119 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1120 { 1121 "resources": [ 1122 { 1123 "metadata": { 1124 "guid": "offering-1-guid" 1125 }, 1126 "entity": { 1127 "label": "v2-elephantsql", 1128 "provider": null, 1129 "description": "Offering 1 description", 1130 "version" : "1.0", 1131 "service_plans": [ 1132 { 1133 "metadata": {"guid": "offering-1-plan-1-guid"}, 1134 "entity": {"name": "not-the-plan-youre-looking-for"} 1135 }, 1136 { 1137 "metadata": {"guid": "offering-1-plan-2-guid"}, 1138 "entity": {"name": "also-not-the-plan-youre-looking-for"} 1139 } 1140 ] 1141 } 1142 } 1143 ] 1144 }`}})) 1145 }) 1146 1147 It("returns a ModelNotFoundError", func() { 1148 _, err := repo.FindServicePlanByDescription(planDescription) 1149 1150 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1151 Expect(err.Error()).To(ContainSubstring("Plan")) 1152 Expect(err.Error()).To(ContainSubstring("v2-service-label v2-plan-name")) 1153 }) 1154 }) 1155 1156 Context("when we get an HTTP error", func() { 1157 BeforeEach(func() { 1158 planDescription = resources.ServicePlanDescription{ 1159 ServiceLabel: "v2-service-label", 1160 ServicePlanName: "v2-plan-name", 1161 } 1162 1163 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1164 Method: "GET", 1165 Path: fmt.Sprintf("/v2/services?inline-relations-depth=1&q=%s", url.QueryEscape("label:v2-service-label;provider:")), 1166 Response: testnet.TestResponse{ 1167 Status: http.StatusInternalServerError, 1168 }})) 1169 }) 1170 1171 It("returns an error", func() { 1172 _, err := repo.FindServicePlanByDescription(planDescription) 1173 1174 Expect(err).To(HaveOccurred()) 1175 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(500, "", ""))) 1176 }) 1177 }) 1178 }) 1179 1180 Describe("migrating service plans", func() { 1181 It("makes a request to CC to migrate the instances from v1 to v2", func() { 1182 setupTestServer(testnet.TestRequest{ 1183 Method: "PUT", 1184 Path: "/v2/service_plans/v1-guid/service_instances", 1185 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1186 Response: testnet.TestResponse{Status: http.StatusOK, Body: `{"changed_count":3}`}, 1187 }) 1188 1189 changedCount, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1190 Expect(err).NotTo(HaveOccurred()) 1191 Expect(changedCount).To(Equal(3)) 1192 }) 1193 1194 It("returns an error when migrating fails", func() { 1195 setupTestServer(apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1196 Method: "PUT", 1197 Path: "/v2/service_plans/v1-guid/service_instances", 1198 Matcher: testnet.RequestBodyMatcher(`{"service_plan_guid":"v2-guid"}`), 1199 Response: testnet.TestResponse{Status: http.StatusInternalServerError}, 1200 })) 1201 1202 _, err := repo.MigrateServicePlanFromV1ToV2("v1-guid", "v2-guid") 1203 Expect(err).To(HaveOccurred()) 1204 }) 1205 }) 1206 1207 Describe("FindServiceOfferingsForSpaceByLabel", func() { 1208 It("finds service offerings within a space by label", func() { 1209 setupTestServer( 1210 testnet.TestRequest{ 1211 Method: "GET", 1212 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1213 Response: testnet.TestResponse{ 1214 Status: 200, 1215 Body: ` 1216 { 1217 "next_url": "/v2/spaces/my-space-guid/services?q=label%3Aoffering-1&page=2", 1218 "resources": [ 1219 { 1220 "metadata": { 1221 "guid": "offering-1-guid" 1222 }, 1223 "entity": { 1224 "label": "offering-1", 1225 "provider": "provider-1", 1226 "description": "offering 1 description", 1227 "version" : "1.0" 1228 } 1229 } 1230 ] 1231 }`}}, 1232 testnet.TestRequest{ 1233 Method: "GET", 1234 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1235 Response: testnet.TestResponse{ 1236 Status: 200, 1237 Body: ` 1238 { 1239 "next_url": null, 1240 "resources": [ 1241 { 1242 "metadata": { 1243 "guid": "offering-2-guid" 1244 }, 1245 "entity": { 1246 "label": "offering-2", 1247 "provider": "provider-2", 1248 "description": "offering 2 description", 1249 "version" : "1.0" 1250 } 1251 } 1252 ] 1253 }`}}) 1254 1255 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1256 Expect(err).ToNot(HaveOccurred()) 1257 Expect(offerings).To(HaveLen(2)) 1258 Expect(offerings[0].GUID).To(Equal("offering-1-guid")) 1259 }) 1260 1261 It("returns an error if the offering cannot be found", func() { 1262 setupTestServer(testnet.TestRequest{ 1263 Method: "GET", 1264 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1265 Response: testnet.TestResponse{ 1266 Status: http.StatusOK, 1267 Body: `{ 1268 "next_url": null, 1269 "resources": [] 1270 }`, 1271 }, 1272 }) 1273 1274 offerings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1275 Expect(err).To(BeAssignableToTypeOf(&errors.ModelNotFoundError{})) 1276 Expect(offerings).To(HaveLen(0)) 1277 }) 1278 1279 It("handles api errors when finding service offerings", func() { 1280 setupTestServer(testnet.TestRequest{ 1281 Method: "GET", 1282 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:offering-1")), 1283 Response: testnet.TestResponse{ 1284 Status: http.StatusBadRequest, 1285 Body: `{ 1286 "code": 9001, 1287 "description": "Something Happened" 1288 }`, 1289 }, 1290 }) 1291 1292 _, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "offering-1") 1293 Expect(err).To(BeAssignableToTypeOf(errors.NewHTTPError(400, "", ""))) 1294 }) 1295 1296 Describe("when api returns query by label is invalid", func() { 1297 It("makes a backwards-compatible request", func() { 1298 failedRequestByQueryLabel := testnet.TestRequest{ 1299 Method: "GET", 1300 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services?q=%s", url.QueryEscape("label:my-service-offering")), 1301 Response: testnet.TestResponse{ 1302 Status: http.StatusBadRequest, 1303 Body: `{"code": 10005,"description": "The query parameter is invalid"}`, 1304 }, 1305 } 1306 1307 firstPaginatedRequest := testnet.TestRequest{ 1308 Method: "GET", 1309 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services"), 1310 Response: testnet.TestResponse{ 1311 Status: http.StatusOK, 1312 Body: `{ 1313 "next_url": "/v2/spaces/my-space-guid/services?page=2", 1314 "resources": [ 1315 { 1316 "metadata": { 1317 "guid": "my-service-offering-guid" 1318 }, 1319 "entity": { 1320 "label": "my-service-offering", 1321 "provider": "some-other-provider", 1322 "description": "a description that does not match your provider", 1323 "version" : "1.0" 1324 } 1325 } 1326 ] 1327 }`, 1328 }, 1329 } 1330 1331 secondPaginatedRequest := testnet.TestRequest{ 1332 Method: "GET", 1333 Path: fmt.Sprintf("/v2/spaces/my-space-guid/services"), 1334 Response: testnet.TestResponse{ 1335 Status: http.StatusOK, 1336 Body: `{"next_url": null, 1337 "resources": [ 1338 { 1339 "metadata": { 1340 "guid": "my-service-offering-guid" 1341 }, 1342 "entity": { 1343 "label": "my-service-offering", 1344 "provider": "my-provider", 1345 "description": "offering 1 description", 1346 "version" : "1.0" 1347 } 1348 } 1349 ]}`, 1350 }, 1351 } 1352 1353 setupTestServer(failedRequestByQueryLabel, firstPaginatedRequest, secondPaginatedRequest) 1354 1355 serviceOfferings, err := repo.FindServiceOfferingsForSpaceByLabel("my-space-guid", "my-service-offering") 1356 Expect(err).NotTo(HaveOccurred()) 1357 Expect(len(serviceOfferings)).To(Equal(2)) 1358 }) 1359 }) 1360 }) 1361 }) 1362 1363 var firstOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1364 { 1365 "next_url": "/v2/services?page=2", 1366 "resources": [ 1367 { 1368 "metadata": { 1369 "guid": "first-offering-1-guid" 1370 }, 1371 "entity": { 1372 "label": "first-Offering 1", 1373 "provider": "Offering 1 provider", 1374 "description": "first Offering 1 description", 1375 "version" : "1.0" 1376 } 1377 } 1378 ]}`, 1379 } 1380 1381 var firstOfferingsForSpaceResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1382 { 1383 "next_url": "/v2/spaces/my-space-guid/services?inline-relations-depth=1&page=2", 1384 "resources": [ 1385 { 1386 "metadata": { 1387 "guid": "first-offering-1-guid" 1388 }, 1389 "entity": { 1390 "label": "first-Offering 1", 1391 "provider": "Offering 1 provider", 1392 "description": "first Offering 1 description", 1393 "version" : "1.0" 1394 } 1395 } 1396 ]}`, 1397 } 1398 1399 var multipleOfferingsResponse = testnet.TestResponse{Status: http.StatusOK, Body: ` 1400 { 1401 "resources": [ 1402 { 1403 "metadata": { 1404 "guid": "offering-1-guid" 1405 }, 1406 "entity": { 1407 "label": "Offering 1", 1408 "provider": "Offering 1 provider", 1409 "description": "Offering 1 description", 1410 "version" : "1.0" 1411 } 1412 }, 1413 { 1414 "metadata": { 1415 "guid": "offering-2-guid" 1416 }, 1417 "entity": { 1418 "label": "Offering 2", 1419 "provider": "Offering 2 provider", 1420 "description": "Offering 2 description", 1421 "version" : "1.5" 1422 } 1423 } 1424 ]}`, 1425 } 1426 1427 var serviceOfferingReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1428 Method: "GET", 1429 Path: "/v2/services/the-service-guid", 1430 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1431 { 1432 "metadata": { 1433 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1434 }, 1435 "entity": { 1436 "label": "mysql", 1437 "provider": "mysql", 1438 "extra": "{\"documentationURL\":\"http://info.example.com\"}", 1439 "description": "MySQL database", 1440 "requires": ["route_forwarding"] 1441 } 1442 }`, 1443 }}) 1444 1445 var serviceOfferingNullExtraReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1446 Method: "GET", 1447 Path: "/v2/services/the-service-guid", 1448 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1449 { 1450 "metadata": { 1451 "guid": "15790581-a293-489b-9efc-847ecf1b1339" 1452 }, 1453 "entity": { 1454 "label": "mysql", 1455 "provider": "mysql", 1456 "extra": null, 1457 "description": "MySQL database" 1458 } 1459 }`, 1460 }}) 1461 1462 var findServiceInstanceReq = apifakes.NewCloudControllerTestRequest(testnet.TestRequest{ 1463 Method: "GET", 1464 Path: "/v2/spaces/my-space-guid/service_instances?return_user_provided_service_instances=true&q=name%3Amy-service", 1465 Response: testnet.TestResponse{Status: http.StatusOK, Body: ` 1466 {"resources": [ 1467 { 1468 "metadata": { 1469 "guid": "my-service-instance-guid" 1470 }, 1471 "entity": { 1472 "name": "my-service", 1473 "dashboard_url":"my-dashboard-url", 1474 "service_bindings": [ 1475 { 1476 "metadata": { 1477 "guid": "service-binding-1-guid", 1478 "url": "/v2/service_bindings/service-binding-1-guid" 1479 }, 1480 "entity": { 1481 "app_guid": "app-1-guid" 1482 } 1483 }, 1484 { 1485 "metadata": { 1486 "guid": "service-binding-2-guid", 1487 "url": "/v2/service_bindings/service-binding-2-guid" 1488 }, 1489 "entity": { 1490 "app_guid": "app-2-guid" 1491 } 1492 } 1493 ], 1494 "service_plan": { 1495 "metadata": { 1496 "guid": "plan-guid" 1497 }, 1498 "entity": { 1499 "name": "plan-name", 1500 "service_guid": "the-service-guid" 1501 } 1502 } 1503 } 1504 } 1505 ]}`}})