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