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  })