github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/service_broker_test.go (about) 1 package ccv3_test 2 3 import ( 4 "fmt" 5 "net/http" 6 7 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 8 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 9 "code.cloudfoundry.org/cli/resources" 10 "code.cloudfoundry.org/cli/types" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/gomega" 13 . "github.com/onsi/gomega/ghttp" 14 ) 15 16 var _ = Describe("ServiceBroker", func() { 17 var client *Client 18 19 BeforeEach(func() { 20 client, _ = NewTestClient() 21 }) 22 23 Describe("GetServiceBrokers", func() { 24 var ( 25 query []Query 26 serviceBrokers []resources.ServiceBroker 27 warnings Warnings 28 executeErr error 29 ) 30 31 JustBeforeEach(func() { 32 serviceBrokers, warnings, executeErr = client.GetServiceBrokers(query...) 33 }) 34 35 When("there are no service brokers", func() { 36 BeforeEach(func() { 37 response := ` 38 { 39 "pagination": { 40 "next": null 41 }, 42 "resources": [] 43 }` 44 45 server.AppendHandlers( 46 CombineHandlers( 47 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 48 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 49 ), 50 ) 51 }) 52 53 It("returns an empty list", func() { 54 Expect(executeErr).NotTo(HaveOccurred()) 55 Expect(serviceBrokers).To(HaveLen(0)) 56 Expect(warnings).To(ConsistOf("this is a warning")) 57 }) 58 }) 59 60 When("there is a service broker", func() { 61 BeforeEach(func() { 62 response := ` 63 { 64 "pagination": { 65 "next": null 66 }, 67 "resources": [ 68 { 69 "name": "service-broker-name-1", 70 "guid": "service-broker-guid-1", 71 "url": "service-broker-url-1" 72 } 73 ] 74 }` 75 76 server.AppendHandlers( 77 CombineHandlers( 78 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 79 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 80 ), 81 ) 82 }) 83 84 It("returns the service broker", func() { 85 Expect(executeErr).NotTo(HaveOccurred()) 86 Expect(serviceBrokers).To(ConsistOf(resources.ServiceBroker{ 87 Name: "service-broker-name-1", 88 GUID: "service-broker-guid-1", 89 URL: "service-broker-url-1", 90 Metadata: nil, 91 })) 92 Expect(warnings).To(ConsistOf("this is another warning")) 93 }) 94 }) 95 96 When("the service broker has labels", func() { 97 BeforeEach(func() { 98 response := ` 99 { 100 "pagination": { 101 "next": null 102 }, 103 "resources": [ 104 { 105 "name": "service-broker-name-1", 106 "guid": "service-broker-guid-1", 107 "url": "service-broker-url-1", 108 "metadata": { 109 "labels": { 110 "some-key":"some-value", 111 "other-key":"other-value" 112 } 113 } 114 } 115 ] 116 }` 117 118 server.AppendHandlers( 119 CombineHandlers( 120 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 121 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 122 ), 123 ) 124 }) 125 126 It("returns the service broker with the labels in Metadata", func() { 127 Expect(executeErr).NotTo(HaveOccurred()) 128 Expect(serviceBrokers).To(ConsistOf(resources.ServiceBroker{ 129 Name: "service-broker-name-1", 130 GUID: "service-broker-guid-1", 131 URL: "service-broker-url-1", 132 Metadata: &resources.Metadata{ 133 Labels: map[string]types.NullString{ 134 "some-key": types.NewNullString("some-value"), 135 "other-key": types.NewNullString("other-value"), 136 }, 137 }, 138 })) 139 }) 140 }) 141 142 When("there is more than one page of service brokers", func() { 143 BeforeEach(func() { 144 response1 := fmt.Sprintf(` 145 { 146 "pagination": { 147 "next": { 148 "href": "%s/v3/service_brokers?page=2&per_page=2" 149 } 150 }, 151 "resources": [ 152 { 153 "name": "service-broker-name-1", 154 "guid": "service-broker-guid-1", 155 "url": "service-broker-url-1", 156 "relationships": {} 157 }, 158 { 159 "name": "service-broker-name-2", 160 "guid": "service-broker-guid-2", 161 "url": "service-broker-url-2", 162 "relationships": {} 163 } 164 ] 165 }`, server.URL()) 166 167 response2 := ` 168 { 169 "pagination": { 170 "next": null 171 }, 172 "resources": [ 173 { 174 "name": "service-broker-name-3", 175 "guid": "service-broker-guid-3", 176 "url": "service-broker-url-3", 177 "relationships": {} 178 } 179 ] 180 }` 181 182 server.AppendHandlers( 183 CombineHandlers( 184 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 185 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 186 ), 187 ) 188 server.AppendHandlers( 189 CombineHandlers( 190 VerifyRequest(http.MethodGet, "/v3/service_brokers", "page=2&per_page=2"), 191 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 192 ), 193 ) 194 }) 195 196 It("returns the queried service-broker and all warnings", func() { 197 Expect(executeErr).NotTo(HaveOccurred()) 198 199 Expect(serviceBrokers).To(ConsistOf( 200 resources.ServiceBroker{Name: "service-broker-name-1", GUID: "service-broker-guid-1", URL: "service-broker-url-1"}, 201 resources.ServiceBroker{Name: "service-broker-name-2", GUID: "service-broker-guid-2", URL: "service-broker-url-2"}, 202 resources.ServiceBroker{Name: "service-broker-name-3", GUID: "service-broker-guid-3", URL: "service-broker-url-3"}, 203 )) 204 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 205 }) 206 }) 207 208 When("a filter is specified", func() { 209 BeforeEach(func() { 210 query = []Query{ 211 { 212 Key: NameFilter, 213 Values: []string{"special-unicorn-broker"}, 214 }, 215 } 216 217 response := ` 218 { 219 "pagination": { 220 "next": null 221 }, 222 "resources": [ 223 { 224 "name": "special-unicorn-broker", 225 "guid": "service-broker-guid-1", 226 "url": "service-broker-url-1" 227 } 228 ] 229 }` 230 231 server.AppendHandlers( 232 CombineHandlers( 233 VerifyRequest(http.MethodGet, "/v3/service_brokers", "names=special-unicorn-broker"), 234 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 235 ), 236 ) 237 }) 238 239 It("passes the filter in the query and returns the result", func() { 240 Expect(executeErr).NotTo(HaveOccurred()) 241 Expect(serviceBrokers[0].Name).To(Equal("special-unicorn-broker")) 242 Expect(warnings).To(ConsistOf("this is another warning")) 243 }) 244 }) 245 246 When("the cloud controller returns errors and warnings", func() { 247 BeforeEach(func() { 248 response := `{ 249 "errors": [ 250 { 251 "code": 10008, 252 "detail": "The request is semantically invalid: command presence", 253 "title": "CF-UnprocessableEntity" 254 }, 255 { 256 "code": 10010, 257 "detail": "Isolation segment not found", 258 "title": "CF-ResourceNotFound" 259 } 260 ] 261 }` 262 server.AppendHandlers( 263 CombineHandlers( 264 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 265 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 266 ), 267 ) 268 }) 269 270 It("returns the error and all warnings", func() { 271 Expect(executeErr).To(MatchError(ccerror.MultiError{ 272 ResponseCode: http.StatusTeapot, 273 Errors: []ccerror.V3Error{ 274 { 275 Code: 10008, 276 Detail: "The request is semantically invalid: command presence", 277 Title: "CF-UnprocessableEntity", 278 }, 279 { 280 Code: 10010, 281 Detail: "Isolation segment not found", 282 Title: "CF-ResourceNotFound", 283 }, 284 }, 285 })) 286 Expect(warnings).To(ConsistOf("this is a warning")) 287 }) 288 }) 289 }) 290 291 Describe("DeleteServiceBroker", func() { 292 var ( 293 warnings Warnings 294 executeErr error 295 serviceBrokerGUID string 296 jobURL JobURL 297 ) 298 299 BeforeEach(func() { 300 serviceBrokerGUID = "some-service-broker-guid" 301 }) 302 303 JustBeforeEach(func() { 304 jobURL, warnings, executeErr = client.DeleteServiceBroker(serviceBrokerGUID) 305 }) 306 307 When("the Cloud Controller successfully deletes the broker", func() { 308 BeforeEach(func() { 309 server.AppendHandlers( 310 CombineHandlers( 311 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 312 RespondWith(http.StatusOK, "", http.Header{ 313 "X-Cf-Warnings": {"this is a warning"}, 314 "Location": {"some-job-url"}, 315 }), 316 ), 317 ) 318 }) 319 320 It("succeeds and returns warnings", func() { 321 Expect(executeErr).NotTo(HaveOccurred()) 322 Expect(warnings).To(ConsistOf("this is a warning")) 323 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 324 }) 325 }) 326 327 When("the broker is space scoped", func() { 328 BeforeEach(func() { 329 server.AppendHandlers( 330 CombineHandlers( 331 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 332 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 333 ), 334 ) 335 }) 336 337 It("succeeds and returns warnings", func() { 338 Expect(executeErr).NotTo(HaveOccurred()) 339 Expect(warnings).To(ConsistOf("this is a warning")) 340 }) 341 }) 342 343 When("the Cloud Controller fails to delete the broker", func() { 344 BeforeEach(func() { 345 response := `{ 346 "errors": [ 347 { 348 "code": 10008, 349 "detail": "The request is semantically invalid: command presence", 350 "title": "CF-UnprocessableEntity" 351 }, 352 { 353 "code": 10010, 354 "detail": "Service broker not found", 355 "title": "CF-ResourceNotFound" 356 } 357 ] 358 }` 359 360 server.AppendHandlers( 361 CombineHandlers( 362 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 363 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 364 ), 365 ) 366 }) 367 368 It("returns parsed errors and warnings", func() { 369 Expect(executeErr).To(MatchError(ccerror.MultiError{ 370 ResponseCode: http.StatusTeapot, 371 Errors: []ccerror.V3Error{ 372 { 373 Code: 10008, 374 Detail: "The request is semantically invalid: command presence", 375 Title: "CF-UnprocessableEntity", 376 }, 377 { 378 Code: 10010, 379 Detail: "Service broker not found", 380 Title: "CF-ResourceNotFound", 381 }, 382 }, 383 })) 384 Expect(warnings).To(ConsistOf("this is a warning")) 385 }) 386 }) 387 }) 388 389 Describe("CreateServiceBroker", func() { 390 const ( 391 name = "name" 392 url = "url" 393 username = "username" 394 password = "password" 395 ) 396 397 var ( 398 jobURL JobURL 399 warnings Warnings 400 executeErr error 401 spaceGUID string 402 expectedBody map[string]interface{} 403 ) 404 405 BeforeEach(func() { 406 spaceGUID = "" 407 expectedBody = map[string]interface{}{ 408 "name": "name", 409 "url": "url", 410 "authentication": map[string]interface{}{ 411 "type": "basic", 412 "credentials": map[string]string{ 413 "username": "username", 414 "password": "password", 415 }, 416 }, 417 } 418 }) 419 420 JustBeforeEach(func() { 421 serviceBrokerRequest := resources.ServiceBroker{ 422 Name: name, 423 URL: url, 424 Username: username, 425 Password: password, 426 SpaceGUID: spaceGUID, 427 } 428 jobURL, warnings, executeErr = client.CreateServiceBroker(serviceBrokerRequest) 429 }) 430 431 When("the Cloud Controller successfully creates the broker", func() { 432 BeforeEach(func() { 433 server.AppendHandlers( 434 CombineHandlers( 435 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 436 VerifyJSONRepresenting(expectedBody), 437 RespondWith(http.StatusOK, "", http.Header{ 438 "X-Cf-Warnings": {"this is a warning"}, 439 "Location": {"some-job-url"}, 440 }), 441 ), 442 ) 443 }) 444 445 It("succeeds, returns warnings and job URL", func() { 446 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 447 Expect(executeErr).NotTo(HaveOccurred()) 448 Expect(warnings).To(ConsistOf("this is a warning")) 449 }) 450 }) 451 452 When("the broker is space scoped", func() { 453 BeforeEach(func() { 454 spaceGUID = "space-guid" 455 expectedBody["relationships"] = map[string]interface{}{ 456 "space": map[string]interface{}{ 457 "data": map[string]string{ 458 "guid": "space-guid", 459 }, 460 }, 461 } 462 server.AppendHandlers( 463 CombineHandlers( 464 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 465 VerifyJSONRepresenting(expectedBody), 466 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 467 ), 468 ) 469 }) 470 471 It("succeeds and returns warnings", func() { 472 Expect(executeErr).NotTo(HaveOccurred()) 473 Expect(warnings).To(ConsistOf("this is a warning")) 474 }) 475 }) 476 477 When("the Cloud Controller fails to create the broker", func() { 478 BeforeEach(func() { 479 response := `{ 480 "errors": [ 481 { 482 "code": 10008, 483 "detail": "The request is semantically invalid: command presence", 484 "title": "CF-UnprocessableEntity" 485 }, 486 { 487 "code": 10010, 488 "detail": "Isolation segment not found", 489 "title": "CF-ResourceNotFound" 490 } 491 ] 492 }` 493 494 server.AppendHandlers( 495 CombineHandlers( 496 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 497 VerifyJSONRepresenting(expectedBody), 498 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 499 ), 500 ) 501 }) 502 503 It("returns parsed errors and warnings", func() { 504 Expect(executeErr).To(MatchError(ccerror.MultiError{ 505 ResponseCode: http.StatusTeapot, 506 Errors: []ccerror.V3Error{ 507 { 508 Code: 10008, 509 Detail: "The request is semantically invalid: command presence", 510 Title: "CF-UnprocessableEntity", 511 }, 512 { 513 Code: 10010, 514 Detail: "Isolation segment not found", 515 Title: "CF-ResourceNotFound", 516 }, 517 }, 518 })) 519 Expect(warnings).To(ConsistOf("this is a warning")) 520 }) 521 }) 522 }) 523 524 Describe("UpdateServiceBroker", func() { 525 var ( 526 name, guid, url, username, password string 527 jobURL JobURL 528 warnings Warnings 529 executeErr error 530 expectedBody map[string]interface{} 531 ) 532 533 BeforeEach(func() { 534 expectedBody = map[string]interface{}{ 535 "url": "new-url", 536 "authentication": map[string]interface{}{ 537 "type": "basic", 538 "credentials": map[string]string{ 539 "username": "new-username", 540 "password": "new-password", 541 }, 542 }, 543 } 544 name = "" 545 guid = "broker-guid" 546 url = "new-url" 547 username = "new-username" 548 password = "new-password" 549 }) 550 551 When("the Cloud Controller successfully updates the broker", func() { 552 BeforeEach(func() { 553 server.AppendHandlers( 554 CombineHandlers( 555 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 556 VerifyJSONRepresenting(expectedBody), 557 RespondWith(http.StatusOK, "", http.Header{ 558 "X-Cf-Warnings": {"this is a warning"}, 559 "Location": {"some-job-url"}, 560 }), 561 ), 562 ) 563 }) 564 565 It("succeeds, returns warnings and job URL", func() { 566 jobURL, warnings, executeErr = client.UpdateServiceBroker( 567 guid, 568 resources.ServiceBroker{ 569 Name: name, 570 URL: url, 571 Username: username, 572 Password: password, 573 }) 574 575 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 576 Expect(executeErr).NotTo(HaveOccurred()) 577 Expect(warnings).To(ConsistOf("this is a warning")) 578 }) 579 }) 580 581 When("the Cloud Controller fails to update the broker", func() { 582 BeforeEach(func() { 583 response := `{ 584 "errors": [ 585 { 586 "code": 10008, 587 "detail": "The request is semantically invalid: command presence", 588 "title": "CF-UnprocessableEntity" 589 }, 590 { 591 "code": 10010, 592 "detail": "Isolation segment not found", 593 "title": "CF-ResourceNotFound" 594 } 595 ] 596 }` 597 598 server.AppendHandlers( 599 CombineHandlers( 600 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 601 VerifyJSONRepresenting(expectedBody), 602 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 603 ), 604 ) 605 }) 606 607 It("returns parsed errors and warnings", func() { 608 jobURL, warnings, executeErr = client.UpdateServiceBroker(guid, 609 resources.ServiceBroker{ 610 Name: name, 611 URL: url, 612 Username: username, 613 Password: password, 614 }) 615 616 Expect(executeErr).To(MatchError(ccerror.MultiError{ 617 ResponseCode: http.StatusTeapot, 618 Errors: []ccerror.V3Error{ 619 { 620 Code: 10008, 621 Detail: "The request is semantically invalid: command presence", 622 Title: "CF-UnprocessableEntity", 623 }, 624 { 625 Code: 10010, 626 Detail: "Isolation segment not found", 627 Title: "CF-ResourceNotFound", 628 }, 629 }, 630 })) 631 Expect(warnings).To(ConsistOf("this is a warning")) 632 }) 633 }) 634 635 When("only name is provided", func() { 636 BeforeEach(func() { 637 name = "some-name" 638 username = "" 639 password = "" 640 url = "" 641 642 expectedBody = map[string]interface{}{ 643 "name": name, 644 } 645 646 server.AppendHandlers( 647 CombineHandlers( 648 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 649 VerifyJSONRepresenting(expectedBody), 650 RespondWith(http.StatusOK, "", http.Header{ 651 "X-Cf-Warnings": {"this is a warning"}, 652 "Location": {"some-job-url"}, 653 }), 654 ), 655 ) 656 }) 657 658 It("includes only the name in the request body", func() { 659 jobURL, warnings, executeErr = client.UpdateServiceBroker( 660 guid, 661 resources.ServiceBroker{ 662 Name: name, 663 }) 664 Expect(executeErr).NotTo(HaveOccurred()) 665 }) 666 }) 667 668 When("partial authentication credentials are provided", func() { 669 It("errors without sending any request", func() { 670 _, _, executeErr = client.UpdateServiceBroker( 671 guid, 672 resources.ServiceBroker{Password: password}, 673 ) 674 Expect(executeErr).To(HaveOccurred()) 675 676 _, _, executeErr = client.UpdateServiceBroker( 677 guid, 678 resources.ServiceBroker{Username: username}, 679 ) 680 Expect(executeErr).To(HaveOccurred()) 681 Expect(executeErr).To(MatchError("Incorrect usage: both username and password must be defined in order to do an update")) 682 }) 683 }) 684 }) 685 })