github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/service_offering_test.go (about) 1 package ccv3_test 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 8 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 9 . "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3" 10 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal" 12 "code.cloudfoundry.org/cli/resources" 13 . "github.com/onsi/ginkgo" 14 . "github.com/onsi/gomega" 15 . "github.com/onsi/gomega/ghttp" 16 . "github.com/onsi/gomega/gstruct" 17 ) 18 19 var _ = Describe("Service Offering", func() { 20 Describe("GetServiceOfferings", func() { 21 var ( 22 client *Client 23 query []Query 24 25 offerings []resources.ServiceOffering 26 warnings Warnings 27 executeErr error 28 ) 29 30 BeforeEach(func() { 31 client, _ = NewTestClient() 32 }) 33 34 JustBeforeEach(func() { 35 offerings, warnings, executeErr = client.GetServiceOfferings(query...) 36 }) 37 38 When("service offerings exist", func() { 39 BeforeEach(func() { 40 response1 := fmt.Sprintf(` 41 { 42 "pagination": { 43 "next": { 44 "href": "%s/v3/service_offerings?names=myServiceOffering&service_broker_names=myServiceBroker&fields[service_broker]=name,guid&page=2" 45 } 46 }, 47 "resources": [ 48 { 49 "guid": "service-offering-1-guid", 50 "name": "service-offering-1-name", 51 "relationships": { 52 "service_broker": { 53 "data": { 54 "guid": "overview-broker-guid" 55 } 56 } 57 } 58 }, 59 { 60 "guid": "service-offering-2-guid", 61 "name": "service-offering-2-name", 62 "relationships": { 63 "service_broker": { 64 "data": { 65 "guid": "overview-broker-guid" 66 } 67 } 68 } 69 } 70 ], 71 "included": { 72 "service_brokers": [ 73 { 74 "guid": "overview-broker-guid", 75 "name": "overview-broker" 76 } 77 ] 78 } 79 }`, 80 server.URL()) 81 82 response2 := ` 83 { 84 "pagination": { 85 "next": { 86 "href": null 87 } 88 }, 89 "resources": [ 90 { 91 "guid": "service-offering-3-guid", 92 "name": "service-offering-3-name", 93 "relationships": { 94 "service_broker": { 95 "data": { 96 "guid": "other-broker-guid" 97 } 98 } 99 } 100 } 101 ], 102 "included": { 103 "service_brokers": [ 104 { 105 "guid": "other-broker-guid", 106 "name": "other-broker" 107 } 108 ] 109 } 110 }` 111 112 server.AppendHandlers( 113 CombineHandlers( 114 VerifyRequest(http.MethodGet, "/v3/service_offerings", "names=myServiceOffering&service_broker_names=myServiceBroker&fields[service_broker]=name,guid"), 115 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 116 ), 117 CombineHandlers( 118 VerifyRequest(http.MethodGet, "/v3/service_offerings", "names=myServiceOffering&service_broker_names=myServiceBroker&fields[service_broker]=name,guid&page=2"), 119 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 120 ), 121 ) 122 123 query = []Query{ 124 { 125 Key: NameFilter, 126 Values: []string{"myServiceOffering"}, 127 }, 128 { 129 Key: ServiceBrokerNamesFilter, 130 Values: []string{"myServiceBroker"}, 131 }, 132 } 133 }) 134 135 It("returns a list of service offerings with their associated warnings", func() { 136 Expect(executeErr).ToNot(HaveOccurred()) 137 138 Expect(offerings).To(ConsistOf( 139 resources.ServiceOffering{ 140 GUID: "service-offering-1-guid", 141 Name: "service-offering-1-name", 142 ServiceBrokerName: "overview-broker", 143 ServiceBrokerGUID: "overview-broker-guid", 144 }, 145 resources.ServiceOffering{ 146 GUID: "service-offering-2-guid", 147 Name: "service-offering-2-name", 148 ServiceBrokerName: "overview-broker", 149 ServiceBrokerGUID: "overview-broker-guid", 150 }, 151 resources.ServiceOffering{ 152 GUID: "service-offering-3-guid", 153 Name: "service-offering-3-name", 154 ServiceBrokerName: "other-broker", 155 ServiceBrokerGUID: "other-broker-guid", 156 }, 157 )) 158 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 159 }) 160 }) 161 162 When("the cloud controller returns errors and warnings", func() { 163 BeforeEach(func() { 164 response := `{ 165 "errors": [ 166 { 167 "code": 42424, 168 "detail": "Some detailed error message", 169 "title": "CF-SomeErrorTitle" 170 }, 171 { 172 "code": 11111, 173 "detail": "Some other detailed error message", 174 "title": "CF-SomeOtherErrorTitle" 175 } 176 ] 177 }` 178 179 server.AppendHandlers( 180 CombineHandlers( 181 VerifyRequest(http.MethodGet, "/v3/service_offerings"), 182 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 183 ), 184 ) 185 }) 186 187 It("returns the error and all warnings", func() { 188 Expect(executeErr).To(MatchError(ccerror.MultiError{ 189 ResponseCode: http.StatusTeapot, 190 Errors: []ccerror.V3Error{ 191 { 192 Code: 42424, 193 Detail: "Some detailed error message", 194 Title: "CF-SomeErrorTitle", 195 }, 196 { 197 Code: 11111, 198 Detail: "Some other detailed error message", 199 Title: "CF-SomeOtherErrorTitle", 200 }, 201 }, 202 })) 203 Expect(warnings).To(ConsistOf("this is a warning")) 204 }) 205 }) 206 }) 207 208 Describe("GetServiceOfferingByGUID", func() { 209 const guid = "fake-guid" 210 211 var ( 212 requester *ccv3fakes.FakeRequester 213 client *Client 214 ) 215 216 BeforeEach(func() { 217 requester = new(ccv3fakes.FakeRequester) 218 client, _ = NewFakeRequesterTestClient(requester) 219 }) 220 221 When("service offering exists", func() { 222 BeforeEach(func() { 223 requester.MakeRequestCalls(func(params RequestParams) (JobURL, Warnings, error) { 224 Expect(params.URIParams).To(BeEquivalentTo(map[string]string{"service_offering_guid": guid})) 225 Expect(params.RequestName).To(Equal(internal.GetServiceOfferingRequest)) 226 params.ResponseBody.(*resources.ServiceOffering).GUID = guid 227 return "", Warnings{"one", "two"}, nil 228 }) 229 }) 230 231 It("returns the service offering with warnings", func() { 232 offering, warnings, err := client.GetServiceOfferingByGUID(guid) 233 Expect(err).ToNot(HaveOccurred()) 234 235 Expect(offering).To(Equal(resources.ServiceOffering{ 236 GUID: guid, 237 })) 238 Expect(warnings).To(ConsistOf("one", "two")) 239 }) 240 }) 241 242 When("no guid was specified", func() { 243 It("fails saying the offering was not found", func() { 244 _, _, err := client.GetServiceOfferingByGUID("") 245 Expect(err).To(MatchError(ccerror.ServiceOfferingNotFoundError{})) 246 }) 247 }) 248 249 When("the cloud controller returns errors and warnings", func() { 250 BeforeEach(func() { 251 requester.MakeRequestReturns( 252 "", 253 Warnings{"one", "two"}, 254 ccerror.MultiError{ 255 ResponseCode: http.StatusTeapot, 256 Errors: []ccerror.V3Error{ 257 { 258 Code: 42424, 259 Detail: "Some detailed error message", 260 Title: "CF-SomeErrorTitle", 261 }, 262 { 263 Code: 11111, 264 Detail: "Some other detailed error message", 265 Title: "CF-SomeOtherErrorTitle", 266 }, 267 }, 268 }, 269 ) 270 }) 271 272 It("returns the error and all warnings", func() { 273 _, warnings, err := client.GetServiceOfferingByGUID(guid) 274 Expect(err).To(MatchError(ccerror.MultiError{ 275 ResponseCode: http.StatusTeapot, 276 Errors: []ccerror.V3Error{ 277 { 278 Code: 42424, 279 Detail: "Some detailed error message", 280 Title: "CF-SomeErrorTitle", 281 }, 282 { 283 Code: 11111, 284 Detail: "Some other detailed error message", 285 Title: "CF-SomeOtherErrorTitle", 286 }, 287 }, 288 })) 289 Expect(warnings).To(ConsistOf("one", "two")) 290 }) 291 }) 292 }) 293 294 Describe("GetServiceOfferingByNameAndBroker", func() { 295 const ( 296 serviceOfferingName = "myServiceOffering" 297 ) 298 299 var ( 300 client *Client 301 requester *ccv3fakes.FakeRequester 302 serviceBrokerName string 303 offering resources.ServiceOffering 304 warnings Warnings 305 executeErr error 306 ) 307 308 BeforeEach(func() { 309 requester = new(ccv3fakes.FakeRequester) 310 client, _ = NewFakeRequesterTestClient(requester) 311 312 serviceBrokerName = "" 313 }) 314 315 JustBeforeEach(func() { 316 offering, warnings, executeErr = client.GetServiceOfferingByNameAndBroker(serviceOfferingName, serviceBrokerName) 317 }) 318 319 When("there is a single match", func() { 320 BeforeEach(func() { 321 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 322 err := requestParams.AppendToList(resources.ServiceOffering{GUID: "service-offering-guid-1"}) 323 Expect(err).NotTo(HaveOccurred()) 324 return IncludedResources{}, Warnings{"this is a warning"}, nil 325 }) 326 }) 327 328 It("makes the correct request", func() { 329 Expect(requester.MakeListRequestCallCount()).To(Equal(1)) 330 Expect(requester.MakeListRequestArgsForCall(0)).To(MatchFields(IgnoreExtras, Fields{ 331 "RequestName": Equal(internal.GetServiceOfferingsRequest), 332 "Query": Equal([]Query{ 333 {Key: NameFilter, Values: []string{serviceOfferingName}}, 334 {Key: PerPage, Values: []string{"2"}}, 335 {Key: Page, Values: []string{"1"}}, 336 {Key: FieldsServiceBroker, Values: []string{"name", "guid"}}, 337 }), 338 "ResponseBody": Equal(resources.ServiceOffering{}), 339 })) 340 }) 341 342 It("returns the service offering and warnings", func() { 343 Expect(offering).To(Equal(resources.ServiceOffering{GUID: "service-offering-guid-1"})) 344 Expect(warnings).To(ConsistOf("this is a warning")) 345 Expect(executeErr).NotTo(HaveOccurred()) 346 }) 347 }) 348 349 When("there are no matches", func() { 350 BeforeEach(func() { 351 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 352 return IncludedResources{}, Warnings{"this is a warning"}, nil 353 }) 354 355 serviceBrokerName = "myServiceBroker" 356 }) 357 358 It("returns an error and warnings", func() { 359 Expect(warnings).To(ConsistOf("this is a warning")) 360 Expect(executeErr).To(MatchError(ccerror.ServiceOfferingNotFoundError{ 361 ServiceOfferingName: serviceOfferingName, 362 ServiceBrokerName: serviceBrokerName, 363 })) 364 }) 365 }) 366 367 When("there is more than one match", func() { 368 BeforeEach(func() { 369 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 370 err := requestParams.AppendToList(resources.ServiceOffering{ 371 GUID: "service-offering-guid-1", 372 Name: serviceOfferingName, 373 ServiceBrokerGUID: "broker-1-guid", 374 }) 375 Expect(err).NotTo(HaveOccurred()) 376 err = requestParams.AppendToList(resources.ServiceOffering{ 377 GUID: "service-offering-guid-2", 378 Name: serviceOfferingName, 379 ServiceBrokerGUID: "broker-2-guid", 380 }) 381 Expect(err).NotTo(HaveOccurred()) 382 return IncludedResources{ 383 ServiceBrokers: []resources.ServiceBroker{ 384 {GUID: "broker-1-guid", Name: "broker-1"}, 385 {GUID: "broker-2-guid", Name: "broker-2"}, 386 }}, 387 Warnings{"this is a warning"}, 388 nil 389 }) 390 }) 391 392 It("returns an error and warnings", func() { 393 Expect(warnings).To(ConsistOf("this is a warning")) 394 Expect(executeErr).To(MatchError(ccerror.ServiceOfferingNameAmbiguityError{ 395 ServiceOfferingName: serviceOfferingName, 396 ServiceBrokerNames: []string{"broker-1", "broker-2"}, 397 })) 398 }) 399 }) 400 401 When("the broker name is specified", func() { 402 BeforeEach(func() { 403 serviceBrokerName = "myServiceBroker" 404 }) 405 406 It("makes the correct request", func() { 407 Expect(requester.MakeListRequestCallCount()).To(Equal(1)) 408 Expect(requester.MakeListRequestArgsForCall(0).Query).To(ConsistOf( 409 Query{Key: NameFilter, Values: []string{serviceOfferingName}}, 410 Query{Key: ServiceBrokerNamesFilter, Values: []string{"myServiceBroker"}}, 411 Query{Key: PerPage, Values: []string{"2"}}, 412 Query{Key: Page, Values: []string{"1"}}, 413 Query{Key: FieldsServiceBroker, Values: []string{"name", "guid"}}, 414 )) 415 }) 416 }) 417 418 When("the requester returns an error", func() { 419 BeforeEach(func() { 420 requester.MakeListRequestReturns(IncludedResources{}, Warnings{"this is a warning"}, errors.New("bang")) 421 }) 422 423 It("returns an error and warnings", func() { 424 Expect(warnings).To(ConsistOf("this is a warning")) 425 Expect(executeErr).To(MatchError("bang")) 426 }) 427 }) 428 }) 429 430 Describe("PurgeServiceOffering", func() { 431 const serviceOfferingGUID = "fake-service-offering-guid" 432 433 var ( 434 client *Client 435 warnings Warnings 436 executeErr error 437 ) 438 439 BeforeEach(func() { 440 client, _ = NewTestClient() 441 }) 442 443 JustBeforeEach(func() { 444 warnings, executeErr = client.PurgeServiceOffering(serviceOfferingGUID) 445 }) 446 447 When("the Cloud Controller successfully purges the service offering", func() { 448 BeforeEach(func() { 449 server.AppendHandlers( 450 CombineHandlers( 451 VerifyRequest(http.MethodDelete, "/v3/service_offerings/fake-service-offering-guid", "purge=true"), 452 RespondWith(http.StatusNoContent, nil, http.Header{ 453 "X-Cf-Warnings": {"this is a warning"}, 454 }), 455 ), 456 ) 457 }) 458 459 It("succeeds and returns warnings", func() { 460 Expect(executeErr).NotTo(HaveOccurred()) 461 Expect(warnings).To(ConsistOf("this is a warning")) 462 }) 463 }) 464 465 When("the Cloud Controller fails to purge the service offering", func() { 466 BeforeEach(func() { 467 response := `{ 468 "errors": [ 469 { 470 "code": 10008, 471 "detail": "The request is semantically invalid: command presence", 472 "title": "CF-UnprocessableEntity" 473 }, 474 { 475 "code": 10010, 476 "detail": "Service offering not found", 477 "title": "CF-ResourceNotFound" 478 } 479 ] 480 }` 481 482 server.AppendHandlers( 483 CombineHandlers( 484 VerifyRequest(http.MethodDelete, "/v3/service_offerings/fake-service-offering-guid", "purge=true"), 485 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 486 ), 487 ) 488 }) 489 490 It("returns parsed errors and warnings", func() { 491 Expect(executeErr).To(MatchError(ccerror.MultiError{ 492 ResponseCode: http.StatusTeapot, 493 Errors: []ccerror.V3Error{ 494 { 495 Code: 10008, 496 Detail: "The request is semantically invalid: command presence", 497 Title: "CF-UnprocessableEntity", 498 }, 499 { 500 Code: 10010, 501 Detail: "Service offering not found", 502 Title: "CF-ResourceNotFound", 503 }, 504 }, 505 })) 506 Expect(warnings).To(ConsistOf("this is a warning")) 507 }) 508 }) 509 }) 510 })