github.com/sleungcy/cli@v7.1.0+incompatible/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("GetServiceOfferingByNameAndBroker", func() { 209 const ( 210 serviceOfferingName = "myServiceOffering" 211 ) 212 213 var ( 214 client *Client 215 requester *ccv3fakes.FakeRequester 216 serviceBrokerName string 217 offering resources.ServiceOffering 218 warnings Warnings 219 executeErr error 220 ) 221 222 BeforeEach(func() { 223 requester = new(ccv3fakes.FakeRequester) 224 client, _ = NewFakeRequesterTestClient(requester) 225 226 serviceBrokerName = "" 227 }) 228 229 JustBeforeEach(func() { 230 offering, warnings, executeErr = client.GetServiceOfferingByNameAndBroker(serviceOfferingName, serviceBrokerName) 231 }) 232 233 When("there is a single match", func() { 234 BeforeEach(func() { 235 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 236 err := requestParams.AppendToList(resources.ServiceOffering{GUID: "service-offering-guid-1"}) 237 Expect(err).NotTo(HaveOccurred()) 238 return IncludedResources{}, Warnings{"this is a warning"}, nil 239 }) 240 }) 241 242 It("makes the correct request", func() { 243 Expect(requester.MakeListRequestCallCount()).To(Equal(1)) 244 Expect(requester.MakeListRequestArgsForCall(0)).To(MatchFields(IgnoreExtras, Fields{ 245 "RequestName": Equal(internal.GetServiceOfferingsRequest), 246 "Query": Equal([]Query{ 247 {Key: NameFilter, Values: []string{serviceOfferingName}}, 248 {Key: FieldsServiceBroker, Values: []string{"name", "guid"}}, 249 }), 250 "ResponseBody": Equal(resources.ServiceOffering{}), 251 })) 252 }) 253 254 It("returns the service offering and warnings", func() { 255 Expect(offering).To(Equal(resources.ServiceOffering{GUID: "service-offering-guid-1"})) 256 Expect(warnings).To(ConsistOf("this is a warning")) 257 Expect(executeErr).NotTo(HaveOccurred()) 258 }) 259 }) 260 261 When("there are no matches", func() { 262 BeforeEach(func() { 263 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 264 return IncludedResources{}, Warnings{"this is a warning"}, nil 265 }) 266 267 serviceBrokerName = "myServiceBroker" 268 }) 269 270 It("returns an error and warnings", func() { 271 Expect(warnings).To(ConsistOf("this is a warning")) 272 Expect(executeErr).To(MatchError(ccerror.ServiceOfferingNotFoundError{ 273 ServiceOfferingName: serviceOfferingName, 274 ServiceBrokerName: serviceBrokerName, 275 })) 276 }) 277 }) 278 279 When("there is more than one match", func() { 280 BeforeEach(func() { 281 requester.MakeListRequestCalls(func(requestParams RequestParams) (IncludedResources, Warnings, error) { 282 err := requestParams.AppendToList(resources.ServiceOffering{ 283 GUID: "service-offering-guid-1", 284 Name: serviceOfferingName, 285 ServiceBrokerGUID: "broker-1-guid", 286 }) 287 Expect(err).NotTo(HaveOccurred()) 288 err = requestParams.AppendToList(resources.ServiceOffering{ 289 GUID: "service-offering-guid-2", 290 Name: serviceOfferingName, 291 ServiceBrokerGUID: "broker-2-guid", 292 }) 293 Expect(err).NotTo(HaveOccurred()) 294 return IncludedResources{ 295 ServiceBrokers: []resources.ServiceBroker{ 296 {GUID: "broker-1-guid", Name: "broker-1"}, 297 {GUID: "broker-2-guid", Name: "broker-2"}, 298 }}, 299 Warnings{"this is a warning"}, 300 nil 301 }) 302 }) 303 304 It("returns an error and warnings", func() { 305 Expect(warnings).To(ConsistOf("this is a warning")) 306 Expect(executeErr).To(MatchError(ccerror.ServiceOfferingNameAmbiguityError{ 307 ServiceOfferingName: serviceOfferingName, 308 ServiceBrokerNames: []string{"broker-1", "broker-2"}, 309 })) 310 }) 311 }) 312 313 When("the broker name is specified", func() { 314 BeforeEach(func() { 315 serviceBrokerName = "myServiceBroker" 316 }) 317 318 It("makes the correct request", func() { 319 Expect(requester.MakeListRequestCallCount()).To(Equal(1)) 320 Expect(requester.MakeListRequestArgsForCall(0).Query).To(ConsistOf( 321 Query{Key: NameFilter, Values: []string{serviceOfferingName}}, 322 Query{Key: ServiceBrokerNamesFilter, Values: []string{"myServiceBroker"}}, 323 Query{Key: FieldsServiceBroker, Values: []string{"name", "guid"}}, 324 )) 325 }) 326 }) 327 328 When("the requester returns an error", func() { 329 BeforeEach(func() { 330 requester.MakeListRequestReturns(IncludedResources{}, Warnings{"this is a warning"}, errors.New("bang")) 331 }) 332 333 It("returns an error and warnings", func() { 334 Expect(warnings).To(ConsistOf("this is a warning")) 335 Expect(executeErr).To(MatchError("bang")) 336 }) 337 }) 338 }) 339 340 Describe("PurgeServiceOffering", func() { 341 const serviceOfferingGUID = "fake-service-offering-guid" 342 343 var ( 344 client *Client 345 warnings Warnings 346 executeErr error 347 ) 348 349 BeforeEach(func() { 350 client, _ = NewTestClient() 351 }) 352 353 JustBeforeEach(func() { 354 warnings, executeErr = client.PurgeServiceOffering(serviceOfferingGUID) 355 }) 356 357 When("the Cloud Controller successfully purges the service offering", func() { 358 BeforeEach(func() { 359 server.AppendHandlers( 360 CombineHandlers( 361 VerifyRequest(http.MethodDelete, "/v3/service_offerings/fake-service-offering-guid", "purge=true"), 362 RespondWith(http.StatusNoContent, nil, http.Header{ 363 "X-Cf-Warnings": {"this is a warning"}, 364 }), 365 ), 366 ) 367 }) 368 369 It("succeeds and returns warnings", func() { 370 Expect(executeErr).NotTo(HaveOccurred()) 371 Expect(warnings).To(ConsistOf("this is a warning")) 372 }) 373 }) 374 375 When("the Cloud Controller fails to purge the service offering", func() { 376 BeforeEach(func() { 377 response := `{ 378 "errors": [ 379 { 380 "code": 10008, 381 "detail": "The request is semantically invalid: command presence", 382 "title": "CF-UnprocessableEntity" 383 }, 384 { 385 "code": 10010, 386 "detail": "Service offering not found", 387 "title": "CF-ResourceNotFound" 388 } 389 ] 390 }` 391 392 server.AppendHandlers( 393 CombineHandlers( 394 VerifyRequest(http.MethodDelete, "/v3/service_offerings/fake-service-offering-guid", "purge=true"), 395 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 396 ), 397 ) 398 }) 399 400 It("returns parsed errors and warnings", func() { 401 Expect(executeErr).To(MatchError(ccerror.MultiError{ 402 ResponseCode: http.StatusTeapot, 403 Errors: []ccerror.V3Error{ 404 { 405 Code: 10008, 406 Detail: "The request is semantically invalid: command presence", 407 Title: "CF-UnprocessableEntity", 408 }, 409 { 410 Code: 10010, 411 Detail: "Service offering not found", 412 Title: "CF-ResourceNotFound", 413 }, 414 }, 415 })) 416 Expect(warnings).To(ConsistOf("this is a warning")) 417 }) 418 }) 419 }) 420 421 })