github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/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 . "github.com/onsi/ginkgo" 10 . "github.com/onsi/gomega" 11 . "github.com/onsi/gomega/ghttp" 12 ) 13 14 var _ = Describe("ServiceBroker", func() { 15 var client *Client 16 17 BeforeEach(func() { 18 client, _ = NewTestClient() 19 }) 20 21 Describe("GetServiceBrokers", func() { 22 var ( 23 serviceBrokers []ServiceBroker 24 warnings Warnings 25 executeErr error 26 ) 27 28 JustBeforeEach(func() { 29 serviceBrokers, warnings, executeErr = client.GetServiceBrokers() 30 }) 31 32 When("service brokers exist", func() { 33 BeforeEach(func() { 34 response1 := fmt.Sprintf(` 35 { 36 "pagination": { 37 "next": { 38 "href": "%s/v3/service_brokers?page=2&per_page=2" 39 } 40 }, 41 "resources": [ 42 { 43 "name": "service-broker-name-1", 44 "guid": "service-broker-guid-1", 45 "url": "service-broker-url-1", 46 "status": "synchronization in progress", 47 "relationships": {} 48 }, 49 { 50 "name": "service-broker-name-2", 51 "guid": "service-broker-guid-2", 52 "url": "service-broker-url-2", 53 "status": "synchronization failed", 54 "relationships": {} 55 } 56 ] 57 }`, server.URL()) 58 59 response2 := ` 60 { 61 "pagination": { 62 "next": null 63 }, 64 "resources": [ 65 { 66 "name": "service-broker-name-3", 67 "guid": "service-broker-guid-3", 68 "url": "service-broker-url-3", 69 "status": "available", 70 "relationships": {} 71 } 72 ] 73 }` 74 75 server.AppendHandlers( 76 CombineHandlers( 77 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 78 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 79 ), 80 ) 81 server.AppendHandlers( 82 CombineHandlers( 83 VerifyRequest(http.MethodGet, "/v3/service_brokers", "page=2&per_page=2"), 84 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}), 85 ), 86 ) 87 }) 88 89 It("returns the queried service-broker and all warnings", func() { 90 Expect(executeErr).NotTo(HaveOccurred()) 91 92 Expect(serviceBrokers).To(ConsistOf( 93 ServiceBroker{Name: "service-broker-name-1", GUID: "service-broker-guid-1", URL: "service-broker-url-1", Status: "synchronization in progress"}, 94 ServiceBroker{Name: "service-broker-name-2", GUID: "service-broker-guid-2", URL: "service-broker-url-2", Status: "synchronization failed"}, 95 ServiceBroker{Name: "service-broker-name-3", GUID: "service-broker-guid-3", URL: "service-broker-url-3", Status: "available"}, 96 )) 97 Expect(warnings).To(ConsistOf("this is a warning", "this is another warning")) 98 }) 99 }) 100 101 When("the cloud controller returns errors and warnings", func() { 102 BeforeEach(func() { 103 response := `{ 104 "errors": [ 105 { 106 "code": 10008, 107 "detail": "The request is semantically invalid: command presence", 108 "title": "CF-UnprocessableEntity" 109 }, 110 { 111 "code": 10010, 112 "detail": "Isolation segment not found", 113 "title": "CF-ResourceNotFound" 114 } 115 ] 116 }` 117 server.AppendHandlers( 118 CombineHandlers( 119 VerifyRequest(http.MethodGet, "/v3/service_brokers"), 120 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 121 ), 122 ) 123 }) 124 125 It("returns the error and all warnings", func() { 126 Expect(executeErr).To(MatchError(ccerror.MultiError{ 127 ResponseCode: http.StatusTeapot, 128 Errors: []ccerror.V3Error{ 129 { 130 Code: 10008, 131 Detail: "The request is semantically invalid: command presence", 132 Title: "CF-UnprocessableEntity", 133 }, 134 { 135 Code: 10010, 136 Detail: "Isolation segment not found", 137 Title: "CF-ResourceNotFound", 138 }, 139 }, 140 })) 141 Expect(warnings).To(ConsistOf("this is a warning")) 142 }) 143 }) 144 }) 145 146 Describe("DeleteServiceBroker", func() { 147 var ( 148 warnings Warnings 149 executeErr error 150 serviceBrokerGUID string 151 jobURL JobURL 152 ) 153 154 BeforeEach(func() { 155 serviceBrokerGUID = "some-service-broker-guid" 156 }) 157 158 JustBeforeEach(func() { 159 jobURL, warnings, executeErr = client.DeleteServiceBroker(serviceBrokerGUID) 160 }) 161 162 When("the Cloud Controller successfully deletes the broker", func() { 163 BeforeEach(func() { 164 server.AppendHandlers( 165 CombineHandlers( 166 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 167 RespondWith(http.StatusOK, "", http.Header{ 168 "X-Cf-Warnings": {"this is a warning"}, 169 "Location": {"some-job-url"}, 170 }), 171 ), 172 ) 173 }) 174 175 It("succeeds and returns warnings", func() { 176 Expect(executeErr).NotTo(HaveOccurred()) 177 Expect(warnings).To(ConsistOf("this is a warning")) 178 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 179 }) 180 }) 181 182 When("the broker is space scoped", func() { 183 BeforeEach(func() { 184 server.AppendHandlers( 185 CombineHandlers( 186 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 187 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 188 ), 189 ) 190 }) 191 192 It("succeeds and returns warnings", func() { 193 Expect(executeErr).NotTo(HaveOccurred()) 194 Expect(warnings).To(ConsistOf("this is a warning")) 195 }) 196 }) 197 198 When("the Cloud Controller fails to delete the broker", func() { 199 BeforeEach(func() { 200 response := `{ 201 "errors": [ 202 { 203 "code": 10008, 204 "detail": "The request is semantically invalid: command presence", 205 "title": "CF-UnprocessableEntity" 206 }, 207 { 208 "code": 10010, 209 "detail": "Service broker not found", 210 "title": "CF-ResourceNotFound" 211 } 212 ] 213 }` 214 215 server.AppendHandlers( 216 CombineHandlers( 217 VerifyRequest(http.MethodDelete, "/v3/service_brokers/some-service-broker-guid"), 218 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 219 ), 220 ) 221 }) 222 223 It("returns parsed errors and warnings", func() { 224 Expect(executeErr).To(MatchError(ccerror.MultiError{ 225 ResponseCode: http.StatusTeapot, 226 Errors: []ccerror.V3Error{ 227 { 228 Code: 10008, 229 Detail: "The request is semantically invalid: command presence", 230 Title: "CF-UnprocessableEntity", 231 }, 232 { 233 Code: 10010, 234 Detail: "Service broker not found", 235 Title: "CF-ResourceNotFound", 236 }, 237 }, 238 })) 239 Expect(warnings).To(ConsistOf("this is a warning")) 240 }) 241 }) 242 }) 243 244 Describe("CreateServiceBroker", func() { 245 const ( 246 name = "name" 247 url = "url" 248 username = "username" 249 password = "password" 250 ) 251 252 var ( 253 jobURL JobURL 254 warnings Warnings 255 executeErr error 256 spaceGUID string 257 expectedBody map[string]interface{} 258 ) 259 260 BeforeEach(func() { 261 spaceGUID = "" 262 expectedBody = map[string]interface{}{ 263 "name": "name", 264 "url": "url", 265 "authentication": map[string]interface{}{ 266 "type": "basic", 267 "credentials": map[string]string{ 268 "username": "username", 269 "password": "password", 270 }, 271 }, 272 } 273 }) 274 275 JustBeforeEach(func() { 276 serviceBrokerRequest := ServiceBrokerModel{ 277 Name: name, 278 URL: url, 279 Username: username, 280 Password: password, 281 SpaceGUID: spaceGUID, 282 } 283 jobURL, warnings, executeErr = client.CreateServiceBroker(serviceBrokerRequest) 284 }) 285 286 When("the Cloud Controller successfully creates the broker", func() { 287 BeforeEach(func() { 288 server.AppendHandlers( 289 CombineHandlers( 290 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 291 VerifyJSONRepresenting(expectedBody), 292 RespondWith(http.StatusOK, "", http.Header{ 293 "X-Cf-Warnings": {"this is a warning"}, 294 "Location": {"some-job-url"}, 295 }), 296 ), 297 ) 298 }) 299 300 It("succeeds, returns warnings and job URL", func() { 301 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 302 Expect(executeErr).NotTo(HaveOccurred()) 303 Expect(warnings).To(ConsistOf("this is a warning")) 304 }) 305 }) 306 307 When("the broker is space scoped", func() { 308 BeforeEach(func() { 309 spaceGUID = "space-guid" 310 expectedBody["relationships"] = map[string]interface{}{ 311 "space": map[string]interface{}{ 312 "data": map[string]string{ 313 "guid": "space-guid", 314 }, 315 }, 316 } 317 server.AppendHandlers( 318 CombineHandlers( 319 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 320 VerifyJSONRepresenting(expectedBody), 321 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 322 ), 323 ) 324 }) 325 326 It("succeeds and returns warnings", func() { 327 Expect(executeErr).NotTo(HaveOccurred()) 328 Expect(warnings).To(ConsistOf("this is a warning")) 329 }) 330 }) 331 332 When("the Cloud Controller fails to create the broker", func() { 333 BeforeEach(func() { 334 response := `{ 335 "errors": [ 336 { 337 "code": 10008, 338 "detail": "The request is semantically invalid: command presence", 339 "title": "CF-UnprocessableEntity" 340 }, 341 { 342 "code": 10010, 343 "detail": "Isolation segment not found", 344 "title": "CF-ResourceNotFound" 345 } 346 ] 347 }` 348 349 server.AppendHandlers( 350 CombineHandlers( 351 VerifyRequest(http.MethodPost, "/v3/service_brokers"), 352 VerifyJSONRepresenting(expectedBody), 353 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 354 ), 355 ) 356 }) 357 358 It("returns parsed errors and warnings", func() { 359 Expect(executeErr).To(MatchError(ccerror.MultiError{ 360 ResponseCode: http.StatusTeapot, 361 Errors: []ccerror.V3Error{ 362 { 363 Code: 10008, 364 Detail: "The request is semantically invalid: command presence", 365 Title: "CF-UnprocessableEntity", 366 }, 367 { 368 Code: 10010, 369 Detail: "Isolation segment not found", 370 Title: "CF-ResourceNotFound", 371 }, 372 }, 373 })) 374 Expect(warnings).To(ConsistOf("this is a warning")) 375 }) 376 }) 377 }) 378 379 Describe("UpdateServiceBroker", func() { 380 var ( 381 name, guid, url, username, password string 382 jobURL JobURL 383 warnings Warnings 384 executeErr error 385 expectedBody map[string]interface{} 386 ) 387 388 BeforeEach(func() { 389 expectedBody = map[string]interface{}{ 390 "url": "new-url", 391 "authentication": map[string]interface{}{ 392 "type": "basic", 393 "credentials": map[string]string{ 394 "username": "new-username", 395 "password": "new-password", 396 }, 397 }, 398 } 399 name = "" 400 guid = "broker-guid" 401 url = "new-url" 402 username = "new-username" 403 password = "new-password" 404 }) 405 406 When("the Cloud Controller successfully updates the broker", func() { 407 BeforeEach(func() { 408 server.AppendHandlers( 409 CombineHandlers( 410 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 411 VerifyJSONRepresenting(expectedBody), 412 RespondWith(http.StatusOK, "", http.Header{ 413 "X-Cf-Warnings": {"this is a warning"}, 414 "Location": {"some-job-url"}, 415 }), 416 ), 417 ) 418 }) 419 420 It("succeeds, returns warnings and job URL", func() { 421 jobURL, warnings, executeErr = client.UpdateServiceBroker( 422 guid, 423 ServiceBrokerModel{ 424 Name: name, 425 URL: url, 426 Username: username, 427 Password: password, 428 }) 429 430 Expect(jobURL).To(Equal(JobURL("some-job-url"))) 431 Expect(executeErr).NotTo(HaveOccurred()) 432 Expect(warnings).To(ConsistOf("this is a warning")) 433 }) 434 }) 435 436 When("the Cloud Controller fails to update the broker", func() { 437 BeforeEach(func() { 438 response := `{ 439 "errors": [ 440 { 441 "code": 10008, 442 "detail": "The request is semantically invalid: command presence", 443 "title": "CF-UnprocessableEntity" 444 }, 445 { 446 "code": 10010, 447 "detail": "Isolation segment not found", 448 "title": "CF-ResourceNotFound" 449 } 450 ] 451 }` 452 453 server.AppendHandlers( 454 CombineHandlers( 455 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 456 VerifyJSONRepresenting(expectedBody), 457 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 458 ), 459 ) 460 }) 461 462 It("returns parsed errors and warnings", func() { 463 jobURL, warnings, executeErr = client.UpdateServiceBroker(guid, 464 ServiceBrokerModel{ 465 Name: name, 466 URL: url, 467 Username: username, 468 Password: password, 469 }) 470 471 Expect(executeErr).To(MatchError(ccerror.MultiError{ 472 ResponseCode: http.StatusTeapot, 473 Errors: []ccerror.V3Error{ 474 { 475 Code: 10008, 476 Detail: "The request is semantically invalid: command presence", 477 Title: "CF-UnprocessableEntity", 478 }, 479 { 480 Code: 10010, 481 Detail: "Isolation segment not found", 482 Title: "CF-ResourceNotFound", 483 }, 484 }, 485 })) 486 Expect(warnings).To(ConsistOf("this is a warning")) 487 }) 488 }) 489 490 When("only name is provided", func() { 491 BeforeEach(func() { 492 name = "some-name" 493 username = "" 494 password = "" 495 url = "" 496 497 expectedBody = map[string]interface{}{ 498 "name": name, 499 } 500 501 server.AppendHandlers( 502 CombineHandlers( 503 VerifyRequest(http.MethodPatch, "/v3/service_brokers/"+guid), 504 VerifyJSONRepresenting(expectedBody), 505 RespondWith(http.StatusOK, "", http.Header{ 506 "X-Cf-Warnings": {"this is a warning"}, 507 "Location": {"some-job-url"}, 508 }), 509 ), 510 ) 511 }) 512 513 It("includes only the name in the request body", func() { 514 jobURL, warnings, executeErr = client.UpdateServiceBroker( 515 guid, 516 ServiceBrokerModel{ 517 Name: name, 518 }) 519 Expect(executeErr).NotTo(HaveOccurred()) 520 }) 521 }) 522 523 When("partial authentication credentials are provided", func() { 524 It("errors without sending any request", func() { 525 _, _, executeErr = client.UpdateServiceBroker( 526 guid, 527 ServiceBrokerModel{Password: password}, 528 ) 529 Expect(executeErr).To(HaveOccurred()) 530 531 _, _, executeErr = client.UpdateServiceBroker( 532 guid, 533 ServiceBrokerModel{Username: username}, 534 ) 535 Expect(executeErr).To(HaveOccurred()) 536 }) 537 }) 538 }) 539 })