github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/route_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("Route", func() {
    17  	var client *Client
    18  
    19  	BeforeEach(func() {
    20  		client, _ = NewTestClient()
    21  	})
    22  
    23  	Describe("CreateRoute", func() {
    24  		var (
    25  			route      resources.Route
    26  			warnings   Warnings
    27  			executeErr error
    28  			spaceGUID  string
    29  			domainGUID string
    30  			host       string
    31  			path       string
    32  			port       int
    33  			ccv3Route  resources.Route
    34  		)
    35  
    36  		BeforeEach(func() {
    37  			host = ""
    38  			path = ""
    39  			port = 0
    40  		})
    41  
    42  		JustBeforeEach(func() {
    43  			spaceGUID = "space-guid"
    44  			domainGUID = "domain-guid"
    45  			ccv3Route = resources.Route{SpaceGUID: spaceGUID, DomainGUID: domainGUID, Host: host, Path: path, Port: port}
    46  			route, warnings, executeErr = client.CreateRoute(ccv3Route)
    47  		})
    48  
    49  		When("the request succeeds", func() {
    50  			When("no additional flags", func() {
    51  				BeforeEach(func() {
    52  					host = ""
    53  					response := `{
    54    "guid": "some-route-guid",
    55    "relationships": {
    56      "space": {
    57  	  "data": { "guid": "space-guid" }
    58      },
    59      "domain": {
    60  	  "data": { "guid": "domain-guid" }
    61      }
    62    },
    63  	"host": ""
    64  }`
    65  
    66  					expectedBody := `{
    67    "relationships": {
    68    	"space": {
    69        "data": { "guid": "space-guid" }
    70      },
    71      "domain": {
    72  	  "data": { "guid": "domain-guid" }
    73      }
    74    }
    75  }`
    76  
    77  					server.AppendHandlers(
    78  						CombineHandlers(
    79  							VerifyRequest(http.MethodPost, "/v3/routes"),
    80  							VerifyJSON(expectedBody),
    81  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
    82  						),
    83  					)
    84  				})
    85  
    86  				It("returns the given route and all warnings", func() {
    87  					Expect(executeErr).ToNot(HaveOccurred())
    88  					Expect(warnings).To(ConsistOf("warning-1"))
    89  
    90  					Expect(route).To(Equal(resources.Route{
    91  						GUID:       "some-route-guid",
    92  						SpaceGUID:  "space-guid",
    93  						DomainGUID: "domain-guid",
    94  					}))
    95  				})
    96  			})
    97  
    98  			When("hostname is passed in", func() {
    99  
   100  				BeforeEach(func() {
   101  					host = "cheesecake"
   102  					response := `{
   103    "guid": "some-route-guid",
   104    "relationships": {
   105      "space": {
   106  			"data": { "guid": "space-guid" }
   107      },
   108      "domain": {
   109  			"data": { "guid": "domain-guid" }
   110      }
   111    },
   112  	"host": "cheesecake"
   113  }`
   114  
   115  					expectedBody := `{
   116    "relationships": {
   117    	"space": {
   118        "data": { "guid": "space-guid" }
   119      },
   120      "domain": {
   121  			"data": { "guid": "domain-guid" }
   122      }
   123    },
   124  	"host": "cheesecake"
   125  }`
   126  
   127  					server.AppendHandlers(
   128  						CombineHandlers(
   129  							VerifyRequest(http.MethodPost, "/v3/routes"),
   130  							VerifyJSON(expectedBody),
   131  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   132  						),
   133  					)
   134  				})
   135  
   136  				It("returns the given route and all warnings", func() {
   137  					Expect(executeErr).ToNot(HaveOccurred())
   138  					Expect(warnings).To(ConsistOf("warning-1"))
   139  
   140  					Expect(route).To(Equal(resources.Route{
   141  						GUID:       "some-route-guid",
   142  						SpaceGUID:  "space-guid",
   143  						DomainGUID: "domain-guid",
   144  						Host:       "cheesecake",
   145  					}))
   146  				})
   147  			})
   148  
   149  			When("path is passed in", func() {
   150  				BeforeEach(func() {
   151  					path = "lion"
   152  
   153  					response := `{
   154  	"guid": "this-route-guid",
   155  	"relationships": {
   156  		"space": {
   157  			"data": {
   158  				"guid": "space-guid"
   159  			}
   160  		},
   161  		"domain": {
   162  			"data": {
   163  				"guid": "domain-guid"
   164  			}
   165  		}
   166  	},
   167  	"path": "lion"
   168  }`
   169  					expectedRequestBody := `{
   170  	"relationships": {
   171  		"space": {
   172  			"data": {
   173  				"guid": "space-guid"
   174  			}
   175  		},
   176  		"domain": {
   177  			"data": {
   178  				"guid": "domain-guid"
   179  			}
   180  		}
   181  	},
   182  	"path": "lion"
   183  }`
   184  
   185  					server.AppendHandlers(
   186  						CombineHandlers(
   187  							VerifyRequest(http.MethodPost, "/v3/routes"),
   188  							VerifyJSON(expectedRequestBody),
   189  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   190  						),
   191  					)
   192  				})
   193  				When("the request succeeds", func() {
   194  					It("returns the given route and all warnings", func() {
   195  						Expect(executeErr).ToNot(HaveOccurred())
   196  						Expect(warnings).To(ConsistOf("warning-1"))
   197  
   198  						Expect(route).To(Equal(resources.Route{
   199  							GUID:       "this-route-guid",
   200  							SpaceGUID:  "space-guid",
   201  							DomainGUID: "domain-guid",
   202  							Path:       "lion",
   203  						}))
   204  					})
   205  				})
   206  			})
   207  
   208  			When("port is passed in", func() {
   209  				BeforeEach(func() {
   210  					port = 1234
   211  
   212  					response := `{
   213  	"guid": "this-route-guid",
   214  	"relationships": {
   215  		"space": {
   216  			"data": {
   217  				"guid": "space-guid"
   218  			}
   219  		},
   220  		"domain": {
   221  			"data": {
   222  				"guid": "domain-guid"
   223  			}
   224  		}
   225  	},
   226  	"port": 1234
   227  }`
   228  					expectedRequestBody := `{
   229  	"relationships": {
   230  		"space": {
   231  			"data": {
   232  				"guid": "space-guid"
   233  			}
   234  		},
   235  		"domain": {
   236  			"data": {
   237  				"guid": "domain-guid"
   238  			}
   239  		}
   240  	},
   241  	"port": 1234
   242  }`
   243  
   244  					server.AppendHandlers(
   245  						CombineHandlers(
   246  							VerifyRequest(http.MethodPost, "/v3/routes"),
   247  							VerifyJSON(expectedRequestBody),
   248  							RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   249  						),
   250  					)
   251  				})
   252  				When("the request succeeds", func() {
   253  					It("returns the given route and all warnings", func() {
   254  						Expect(executeErr).ToNot(HaveOccurred())
   255  						Expect(warnings).To(ConsistOf("warning-1"))
   256  
   257  						Expect(route).To(Equal(resources.Route{
   258  							GUID:       "this-route-guid",
   259  							SpaceGUID:  "space-guid",
   260  							DomainGUID: "domain-guid",
   261  							Port:       1234,
   262  						}))
   263  					})
   264  				})
   265  			})
   266  		})
   267  
   268  		When("the cloud controller returns errors and warnings", func() {
   269  			BeforeEach(func() {
   270  				response := `{
   271    "errors": [
   272      {
   273        "code": 10008,
   274        "detail": "The request is semantically invalid: command presence",
   275        "title": "CF-UnprocessableEntity"
   276      },
   277  		{
   278        "code": 10010,
   279        "detail": "Isolation segment not found",
   280        "title": "CF-ResourceNotFound"
   281      }
   282    ]
   283  }`
   284  				server.AppendHandlers(
   285  					CombineHandlers(
   286  						VerifyRequest(http.MethodPost, "/v3/routes"),
   287  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   288  					),
   289  				)
   290  			})
   291  
   292  			It("returns the error and all warnings", func() {
   293  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   294  					ResponseCode: http.StatusTeapot,
   295  					Errors: []ccerror.V3Error{
   296  						{
   297  							Code:   10008,
   298  							Detail: "The request is semantically invalid: command presence",
   299  							Title:  "CF-UnprocessableEntity",
   300  						},
   301  						{
   302  							Code:   10010,
   303  							Detail: "Isolation segment not found",
   304  							Title:  "CF-ResourceNotFound",
   305  						},
   306  					},
   307  				}))
   308  				Expect(warnings).To(ConsistOf("this is a warning"))
   309  			})
   310  		})
   311  	})
   312  
   313  	Describe("GetRoutes", func() {
   314  		var (
   315  			query      Query
   316  			routes     []resources.Route
   317  			warnings   Warnings
   318  			executeErr error
   319  		)
   320  
   321  		JustBeforeEach(func() {
   322  			routes, warnings, executeErr = client.GetRoutes(query)
   323  		})
   324  
   325  		When("the request succeeds", func() {
   326  			var (
   327  				response1 string
   328  				response2 string
   329  			)
   330  
   331  			BeforeEach(func() {
   332  				response1 = fmt.Sprintf(`
   333  				{
   334  					"pagination": {
   335  						"next": {
   336  							"href": "%s/v3/routes?page=2"
   337  						}
   338  					},
   339  					"resources": [
   340  						{
   341  							"guid": "route-1-guid",
   342  							"url": "hello",
   343  							"metadata": {
   344  								"labels": {
   345  									"key1": "value1"
   346  								}
   347  							}
   348  						},
   349  						{
   350  							"guid": "route-2-guid",
   351  							"url": "bye"
   352  						}
   353  					]
   354  				}`, server.URL())
   355  
   356  				response2 = `
   357  				{
   358  					"pagination": {
   359  						"next": null
   360  					},
   361  					"resources": [
   362  						{
   363  							"guid": "route-3-guid"
   364  						}
   365  					]
   366  				}`
   367  			})
   368  
   369  			When("not passing any filters", func() {
   370  				BeforeEach(func() {
   371  					query = Query{}
   372  
   373  					server.AppendHandlers(
   374  						CombineHandlers(
   375  							VerifyRequest(http.MethodGet, "/v3/routes"),
   376  							RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   377  						),
   378  					)
   379  					server.AppendHandlers(
   380  						CombineHandlers(
   381  							VerifyRequest(http.MethodGet, "/v3/routes", "page=2"),
   382  							RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   383  						),
   384  					)
   385  				})
   386  
   387  				It("returns the given route and all warnings", func() {
   388  					Expect(executeErr).ToNot(HaveOccurred())
   389  					Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   390  
   391  					Expect(routes).To(Equal([]resources.Route{
   392  						{
   393  							GUID: "route-1-guid",
   394  							URL:  "hello",
   395  							Metadata: &resources.Metadata{
   396  								Labels: map[string]types.NullString{
   397  									"key1": types.NewNullString("value1"),
   398  								},
   399  							},
   400  						},
   401  						{
   402  							GUID: "route-2-guid",
   403  							URL:  "bye",
   404  						},
   405  						{
   406  							GUID: "route-3-guid",
   407  						},
   408  					}))
   409  				})
   410  			})
   411  
   412  			When("passing in a query", func() {
   413  				BeforeEach(func() {
   414  					query = Query{Key: "space_guids", Values: []string{"guid1", "guid2"}}
   415  
   416  					server.AppendHandlers(
   417  						CombineHandlers(
   418  							VerifyRequest(http.MethodGet, "/v3/routes", "space_guids=guid1,guid2"),
   419  							RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   420  						),
   421  					)
   422  					server.AppendHandlers(
   423  						CombineHandlers(
   424  							VerifyRequest(http.MethodGet, "/v3/routes", "page=2", "space_guids=guid1,guid2"),
   425  							RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}),
   426  						),
   427  					)
   428  				})
   429  
   430  				It("passes query params", func() {
   431  					Expect(executeErr).ToNot(HaveOccurred())
   432  					Expect(warnings).To(ConsistOf("warning-1", "warning-2"))
   433  
   434  					Expect(routes).To(Equal([]resources.Route{
   435  						{
   436  							GUID: "route-1-guid",
   437  							URL:  "hello",
   438  							Metadata: &resources.Metadata{
   439  								Labels: map[string]types.NullString{
   440  									"key1": types.NewNullString("value1"),
   441  								},
   442  							},
   443  						},
   444  						{
   445  							GUID: "route-2-guid",
   446  							URL:  "bye",
   447  						},
   448  						{
   449  							GUID: "route-3-guid",
   450  						},
   451  					}))
   452  				})
   453  			})
   454  		})
   455  	})
   456  
   457  	Describe("DeleteRoute", func() {
   458  		var (
   459  			routeGUID    string
   460  			jobURLString string
   461  			jobURL       JobURL
   462  			warnings     Warnings
   463  			executeErr   error
   464  		)
   465  
   466  		JustBeforeEach(func() {
   467  			jobURL, warnings, executeErr = client.DeleteRoute(routeGUID)
   468  		})
   469  
   470  		When("route exists", func() {
   471  			routeGUID = "route-guid"
   472  			jobURLString = "https://api.test.com/v3/jobs/job-guid"
   473  
   474  			BeforeEach(func() {
   475  				server.AppendHandlers(
   476  					CombineHandlers(
   477  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid"),
   478  						RespondWith(http.StatusAccepted, nil, http.Header{
   479  							"X-Cf-Warnings": {"this is a warning"},
   480  							"Location":      {jobURLString},
   481  						}),
   482  					),
   483  				)
   484  			})
   485  
   486  			It("returns all warnings", func() {
   487  				Expect(executeErr).NotTo(HaveOccurred())
   488  				Expect(jobURL).To(Equal(JobURL(jobURLString)))
   489  				Expect(warnings).To(ConsistOf("this is a warning"))
   490  			})
   491  		})
   492  
   493  		When("the cloud controller returns errors and warnings", func() {
   494  			BeforeEach(func() {
   495  				response := `{
   496  	  "errors": [
   497  	    {
   498  	      "code": 10008,
   499  	      "detail": "The request is semantically invalid: command presence",
   500  	      "title": "CF-UnprocessableEntity"
   501  	    },
   502  			{
   503  	      "code": 10010,
   504  	      "detail": "Isolation segment not found",
   505  	      "title": "CF-ResourceNotFound"
   506  	    }
   507  	  ]
   508  	}`
   509  				server.AppendHandlers(
   510  					CombineHandlers(
   511  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid"),
   512  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   513  					),
   514  				)
   515  			})
   516  
   517  			It("returns the error and all warnings", func() {
   518  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   519  					ResponseCode: http.StatusTeapot,
   520  					Errors: []ccerror.V3Error{
   521  						{
   522  							Code:   10008,
   523  							Detail: "The request is semantically invalid: command presence",
   524  							Title:  "CF-UnprocessableEntity",
   525  						},
   526  						{
   527  							Code:   10010,
   528  							Detail: "Isolation segment not found",
   529  							Title:  "CF-ResourceNotFound",
   530  						},
   531  					},
   532  				}))
   533  				Expect(warnings).To(ConsistOf("this is a warning"))
   534  			})
   535  		})
   536  	})
   537  
   538  	Describe("MapRoute", func() {
   539  		var (
   540  			routeGUID           = "route-guid"
   541  			appGUID             = "app-guid"
   542  			destinationProtocol string
   543  			expectedBody        string
   544  			warnings            Warnings
   545  			executeErr          error
   546  		)
   547  
   548  		JustBeforeEach(func() {
   549  			response := `{}`
   550  			server.AppendHandlers(
   551  				CombineHandlers(
   552  					VerifyRequest(http.MethodPost, "/v3/routes/route-guid/destinations"),
   553  					VerifyJSON(expectedBody),
   554  					RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   555  				),
   556  			)
   557  			warnings, executeErr = client.MapRoute(routeGUID, appGUID, destinationProtocol)
   558  		})
   559  
   560  		When("the request is successful", func() {
   561  			BeforeEach(func() {
   562  				destinationProtocol = "http2"
   563  				expectedBody = fmt.Sprintf(`
   564  					{
   565  						"destinations": [
   566  						 {
   567  							"app": {
   568  								"guid": "%s"
   569  							},
   570  							"protocol": "%s"
   571  						 }
   572  						]
   573  					}
   574  				`, appGUID, destinationProtocol)
   575  			})
   576  
   577  			It("returns the warnings and no error", func() {
   578  				Expect(executeErr).ToNot(HaveOccurred())
   579  				Expect(warnings).To(ConsistOf("warning-1"))
   580  			})
   581  		})
   582  
   583  		Context("when destination protocol is not provided", func() {
   584  			BeforeEach(func() {
   585  				destinationProtocol = ""
   586  				expectedBody = fmt.Sprintf(`
   587  					{
   588  						"destinations": [
   589  						 {
   590  							"app": {
   591  								"guid": "%s"
   592  							}
   593  						 }
   594  						]
   595  					}
   596  				`, appGUID)
   597  			})
   598  
   599  			It("does not include it in the request", func() {
   600  				Expect(executeErr).ToNot(HaveOccurred())
   601  			})
   602  		})
   603  
   604  		When("the cloud controller returns errors and warnings", func() {
   605  			BeforeEach(func() {
   606  				response := `{
   607    "errors": [
   608      {
   609        "code": 10008,
   610        "detail": "The request is semantically invalid: command presence",
   611        "title": "CF-UnprocessableEntity"
   612      },
   613  		{
   614        "code": 10010,
   615        "detail": "Isolation segment not found",
   616        "title": "CF-ResourceNotFound"
   617      }
   618    ]
   619  }`
   620  				server.AppendHandlers(
   621  					CombineHandlers(
   622  						VerifyRequest(http.MethodPost, "/v3/routes/route-guid/destinations"),
   623  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   624  					),
   625  				)
   626  			})
   627  
   628  			It("returns the error and all warnings", func() {
   629  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   630  					ResponseCode: http.StatusTeapot,
   631  					Errors: []ccerror.V3Error{
   632  						{
   633  							Code:   10008,
   634  							Detail: "The request is semantically invalid: command presence",
   635  							Title:  "CF-UnprocessableEntity",
   636  						},
   637  						{
   638  							Code:   10010,
   639  							Detail: "Isolation segment not found",
   640  							Title:  "CF-ResourceNotFound",
   641  						},
   642  					},
   643  				}))
   644  				Expect(warnings).To(ConsistOf("this is a warning"))
   645  			})
   646  		})
   647  	})
   648  
   649  	Describe("GetRouteDestinations", func() {
   650  		var (
   651  			routeGUID    = "some-route-guid"
   652  			destinations []resources.RouteDestination
   653  			warnings     Warnings
   654  			executeErr   error
   655  		)
   656  
   657  		JustBeforeEach(func() {
   658  			destinations, warnings, executeErr = client.GetRouteDestinations(routeGUID)
   659  		})
   660  
   661  		When("the request succeeds", func() {
   662  			var (
   663  				response string
   664  			)
   665  
   666  			BeforeEach(func() {
   667  				response = `
   668  				{
   669  					"destinations": [
   670  						{
   671  							"guid": "destination-1-guid",
   672  							"app": {
   673  								"guid": "app-1-guid",
   674  								"process": {
   675  									"type": "web"
   676  								}
   677  							}
   678  						},
   679  						{
   680  							"guid": "destination-2-guid",
   681  							"app": {
   682  								"guid": "app-2-guid",
   683  								"process": {
   684  									"type": "worker"
   685  								}
   686  							}
   687  						}
   688  					]
   689  				}`
   690  			})
   691  
   692  			When("the request succeeds", func() {
   693  				BeforeEach(func() {
   694  					server.AppendHandlers(
   695  						CombineHandlers(
   696  							VerifyRequest(http.MethodGet, "/v3/routes/some-route-guid/destinations"),
   697  							RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"warning-1"}}),
   698  						),
   699  					)
   700  				})
   701  
   702  				It("returns destinations and all warnings", func() {
   703  					Expect(executeErr).ToNot(HaveOccurred())
   704  					Expect(warnings).To(ConsistOf("warning-1"))
   705  
   706  					Expect(destinations).To(Equal([]resources.RouteDestination{
   707  						{
   708  							GUID: "destination-1-guid",
   709  							App:  resources.RouteDestinationApp{GUID: "app-1-guid", Process: struct{ Type string }{Type: "web"}},
   710  						},
   711  						{
   712  							GUID: "destination-2-guid",
   713  							App:  resources.RouteDestinationApp{GUID: "app-2-guid", Process: struct{ Type string }{Type: "worker"}},
   714  						},
   715  					}))
   716  				})
   717  			})
   718  		})
   719  	})
   720  
   721  	Describe("UnmapRoute", func() {
   722  		var (
   723  			routeGUID       string
   724  			destinationGUID string
   725  			warnings        Warnings
   726  			executeErr      error
   727  		)
   728  
   729  		JustBeforeEach(func() {
   730  			warnings, executeErr = client.UnmapRoute(routeGUID, destinationGUID)
   731  		})
   732  
   733  		When("route exists", func() {
   734  			routeGUID = "route-guid"
   735  			destinationGUID = "destination-guid"
   736  
   737  			BeforeEach(func() {
   738  				server.AppendHandlers(
   739  					CombineHandlers(
   740  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/destinations/destination-guid"),
   741  						RespondWith(http.StatusNoContent, nil, http.Header{
   742  							"X-Cf-Warnings": {"this is a warning"},
   743  						}),
   744  					),
   745  				)
   746  			})
   747  
   748  			It("returns all warnings", func() {
   749  				Expect(executeErr).NotTo(HaveOccurred())
   750  				Expect(warnings).To(ConsistOf("this is a warning"))
   751  			})
   752  		})
   753  
   754  		When("the cloud controller returns errors and warnings", func() {
   755  			BeforeEach(func() {
   756  				response := `{
   757  	  "errors": [
   758  	    {
   759  	      "code": 10008,
   760  	      "detail": "The request is semantically invalid: command presence",
   761  	      "title": "CF-UnprocessableEntity"
   762  	    },
   763  			{
   764  	      "code": 10010,
   765  	      "detail": "Isolation segment not found",
   766  	      "title": "CF-ResourceNotFound"
   767  	    }
   768  	  ]
   769  	}`
   770  				server.AppendHandlers(
   771  					CombineHandlers(
   772  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/destinations/destination-guid"),
   773  						RespondWith(http.StatusTeapot, response, http.Header{
   774  							"X-Cf-Warnings": {"this is a warning"},
   775  						}),
   776  					),
   777  				)
   778  			})
   779  
   780  			It("returns the error and all warnings", func() {
   781  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   782  					ResponseCode: http.StatusTeapot,
   783  					Errors: []ccerror.V3Error{
   784  						{
   785  							Code:   10008,
   786  							Detail: "The request is semantically invalid: command presence",
   787  							Title:  "CF-UnprocessableEntity",
   788  						},
   789  						{
   790  							Code:   10010,
   791  							Detail: "Isolation segment not found",
   792  							Title:  "CF-ResourceNotFound",
   793  						},
   794  					},
   795  				}))
   796  				Expect(warnings).To(ConsistOf("this is a warning"))
   797  			})
   798  		})
   799  	})
   800  
   801  	Describe("UnshareRoute", func() {
   802  		var (
   803  			routeGUID  string
   804  			spaceGUID  string
   805  			warnings   Warnings
   806  			executeErr error
   807  		)
   808  
   809  		JustBeforeEach(func() {
   810  			warnings, executeErr = client.UnshareRoute(routeGUID, spaceGUID)
   811  		})
   812  
   813  		When("aroute exists and is shared with the space", func() {
   814  			routeGUID = "route-guid"
   815  			spaceGUID = "space-guid"
   816  
   817  			BeforeEach(func() {
   818  				server.AppendHandlers(
   819  					CombineHandlers(
   820  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/relationships/shared_spaces/space-guid"),
   821  						RespondWith(http.StatusNoContent, nil, http.Header{
   822  							"X-Cf-Warnings": {"this is a warning"},
   823  						}),
   824  					),
   825  				)
   826  			})
   827  
   828  			It("returns warnings", func() {
   829  				Expect(executeErr).NotTo(HaveOccurred())
   830  				Expect(warnings).To(ConsistOf("this is a warning"))
   831  			})
   832  		})
   833  
   834  		When("the cloud controller returns errors and warnings", func() {
   835  			BeforeEach(func() {
   836  				response := `{
   837  	  "errors": [
   838  	    {
   839  	      "code": 10008,
   840  	      "detail": "The request is semantically invalid: command presence",
   841  	      "title": "CF-UnprocessableEntity"
   842  	    },
   843  			{
   844  	      "code": 10010,
   845  	      "detail": "Route not found",
   846  	      "title": "CF-ResourceNotFound"
   847  	    }
   848  	  ]
   849  	}`
   850  				server.AppendHandlers(
   851  					CombineHandlers(
   852  						VerifyRequest(http.MethodDelete, "/v3/routes/route-guid/relationships/shared_spaces/space-guid"),
   853  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   854  					),
   855  				)
   856  			})
   857  
   858  			It("returns the error and all warnings", func() {
   859  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   860  					ResponseCode: http.StatusTeapot,
   861  					Errors: []ccerror.V3Error{
   862  						{
   863  							Code:   10008,
   864  							Detail: "The request is semantically invalid: command presence",
   865  							Title:  "CF-UnprocessableEntity",
   866  						},
   867  						{
   868  							Code:   10010,
   869  							Detail: "Route not found",
   870  							Title:  "CF-ResourceNotFound",
   871  						},
   872  					},
   873  				}))
   874  				Expect(warnings).To(ConsistOf("this is a warning"))
   875  			})
   876  		})
   877  	})
   878  
   879  	Describe("UpdateDestination", func() {
   880  		var (
   881  			routeGUID       string
   882  			destinationGUID string
   883  			protocol        string
   884  			warnings        Warnings
   885  			executeErr      error
   886  		)
   887  
   888  		JustBeforeEach(func() {
   889  			warnings, executeErr = client.UpdateDestination(routeGUID, destinationGUID, protocol)
   890  		})
   891  
   892  		When("the request succeeds", func() {
   893  			routeGUID = "route-guid"
   894  			destinationGUID = "destination-guid"
   895  			protocol = "http2"
   896  
   897  			expectedBody := fmt.Sprintf(`{
   898  									"protocol": "%s"
   899  								}`, protocol)
   900  
   901  			BeforeEach(func() {
   902  				server.AppendHandlers(
   903  					CombineHandlers(
   904  						VerifyRequest(http.MethodPatch, "/v3/routes/route-guid/destinations/destination-guid"),
   905  						VerifyJSON(expectedBody),
   906  						RespondWith(http.StatusNoContent, nil, http.Header{
   907  							"X-Cf-Warnings": {"this is a warning"},
   908  						}),
   909  					),
   910  				)
   911  			})
   912  
   913  			It("returns all warnings", func() {
   914  				Expect(executeErr).NotTo(HaveOccurred())
   915  				Expect(warnings).To(ConsistOf("this is a warning"))
   916  			})
   917  		})
   918  
   919  		When("the cloud controller returns errors and warnings", func() {
   920  			BeforeEach(func() {
   921  				response := `{
   922  	  "errors": [
   923  	    {
   924  	      "code": 10008,
   925  	      "detail": "The request is semantically invalid: command presence",
   926  	      "title": "CF-UnprocessableEntity"
   927  	    },
   928  			{
   929  	      "code": 10010,
   930  	      "detail": "Isolation segment not found",
   931  	      "title": "CF-ResourceNotFound"
   932  	    }
   933  	  ]
   934  	}`
   935  				server.AppendHandlers(
   936  					CombineHandlers(
   937  						VerifyRequest(http.MethodPatch, "/v3/routes/route-guid/destinations/destination-guid"),
   938  						RespondWith(http.StatusTeapot, response, http.Header{
   939  							"X-Cf-Warnings": {"this is a warning"},
   940  						}),
   941  					),
   942  				)
   943  			})
   944  
   945  			It("returns the error and all warnings", func() {
   946  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   947  					ResponseCode: http.StatusTeapot,
   948  					Errors: []ccerror.V3Error{
   949  						{
   950  							Code:   10008,
   951  							Detail: "The request is semantically invalid: command presence",
   952  							Title:  "CF-UnprocessableEntity",
   953  						},
   954  						{
   955  							Code:   10010,
   956  							Detail: "Isolation segment not found",
   957  							Title:  "CF-ResourceNotFound",
   958  						},
   959  					},
   960  				}))
   961  				Expect(warnings).To(ConsistOf("this is a warning"))
   962  			})
   963  		})
   964  	})
   965  
   966  	Describe("DeleteOrphanedRoutes", func() {
   967  		var (
   968  			spaceGUID  string
   969  			warnings   Warnings
   970  			executeErr error
   971  			jobURL     JobURL
   972  		)
   973  		JustBeforeEach(func() {
   974  			jobURL, warnings, executeErr = client.DeleteOrphanedRoutes(spaceGUID)
   975  		})
   976  
   977  		When("the API succeeds", func() {
   978  			BeforeEach(func() {
   979  				spaceGUID = "space-guid"
   980  				server.AppendHandlers(
   981  					CombineHandlers(
   982  						VerifyRequest(http.MethodDelete, "/v3/spaces/space-guid/routes", "unmapped=true"),
   983  						RespondWith(
   984  							http.StatusAccepted,
   985  							nil,
   986  							http.Header{"X-Cf-Warnings": {"orphaned-warning"}, "Location": {"job-url"}},
   987  						),
   988  					),
   989  				)
   990  			})
   991  
   992  			It("returns the warnings and a job", func() {
   993  				Expect(executeErr).ToNot(HaveOccurred())
   994  				Expect(warnings).To(ConsistOf("orphaned-warning"))
   995  				Expect(jobURL).To(Equal(JobURL("job-url")))
   996  			})
   997  		})
   998  
   999  		When("the API fails", func() {
  1000  			BeforeEach(func() {
  1001  				spaceGUID = "space-guid"
  1002  				response := `{
  1003  	  "errors": [
  1004  	    {
  1005  	      "code": 10008,
  1006  	      "detail": "The request is semantically invalid: command presence",
  1007  	      "title": "CF-UnprocessableEntity"
  1008  	    },
  1009  			{
  1010  	      "code": 10010,
  1011  	      "detail": "Isolation segment not found",
  1012  	      "title": "CF-ResourceNotFound"
  1013  	    }
  1014  	  ]
  1015  	}`
  1016  				server.AppendHandlers(
  1017  					CombineHandlers(
  1018  						VerifyRequest(http.MethodDelete, "/v3/spaces/space-guid/routes", "unmapped=true"),
  1019  						RespondWith(
  1020  							http.StatusTeapot,
  1021  							response,
  1022  							http.Header{"X-Cf-Warnings": {"orphaned-warning"}},
  1023  						),
  1024  					),
  1025  				)
  1026  			})
  1027  
  1028  			It("returns the warnings and a job", func() {
  1029  
  1030  				Expect(executeErr).To(MatchError(ccerror.MultiError{
  1031  					ResponseCode: http.StatusTeapot,
  1032  					Errors: []ccerror.V3Error{
  1033  						{
  1034  							Code:   10008,
  1035  							Detail: "The request is semantically invalid: command presence",
  1036  							Title:  "CF-UnprocessableEntity",
  1037  						},
  1038  						{
  1039  							Code:   10010,
  1040  							Detail: "Isolation segment not found",
  1041  							Title:  "CF-ResourceNotFound",
  1042  						},
  1043  					},
  1044  				}))
  1045  				Expect(warnings).To(ConsistOf("orphaned-warning"))
  1046  			})
  1047  		})
  1048  	})
  1049  
  1050  	Describe("GetApplicationRoutes", func() {
  1051  		var (
  1052  			appGUID string
  1053  
  1054  			routes     []resources.Route
  1055  			warnings   Warnings
  1056  			executeErr error
  1057  		)
  1058  
  1059  		BeforeEach(func() {
  1060  			appGUID = "some-app-guid"
  1061  		})
  1062  
  1063  		JustBeforeEach(func() {
  1064  			routes, warnings, executeErr = client.GetApplicationRoutes(appGUID)
  1065  		})
  1066  
  1067  		When("the request succeeds", func() {
  1068  			BeforeEach(func() {
  1069  				body := `{
  1070  	"resources": [
  1071  		{
  1072  			"guid": "route-guid",
  1073  			"host": "host",
  1074  			"path": "/path",
  1075  			"url": "host.domain.com/path",
  1076  			"relationships": {
  1077  				"space": {
  1078  					"data": {
  1079  						"guid": "space-guid"
  1080  					}
  1081  				},
  1082  				"domain": {
  1083  					"data": {
  1084  						"guid": "domain-guid"
  1085  					}
  1086  				}
  1087  			}
  1088  		}, {
  1089  			"guid": "route2-guid",
  1090  			"host": "",
  1091  			"path": "",
  1092  			"url": "domain.com",
  1093  			"relationships": {
  1094  				"space": {
  1095  					"data": {
  1096  						"guid": "space-guid"
  1097  					}
  1098  				},
  1099  				"domain": {
  1100  					"data": {
  1101  						"guid": "domain2-guid"
  1102  					}
  1103  				}
  1104  			}
  1105  		}
  1106  	]
  1107  }`
  1108  				server.AppendHandlers(
  1109  					CombineHandlers(
  1110  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/routes"),
  1111  						RespondWith(
  1112  							http.StatusOK,
  1113  							body,
  1114  							http.Header{"X-Cf-Warnings": {"get-app-routes-warning"}, "Location": {"job-url"}},
  1115  						),
  1116  					),
  1117  				)
  1118  			})
  1119  
  1120  			It("returns an array of routes", func() {
  1121  				Expect(executeErr).NotTo(HaveOccurred())
  1122  				Expect(warnings).To(ConsistOf("get-app-routes-warning"))
  1123  
  1124  				Expect(routes).To(ConsistOf(
  1125  					resources.Route{
  1126  						GUID:       "route-guid",
  1127  						DomainGUID: "domain-guid",
  1128  						SpaceGUID:  "space-guid",
  1129  						Host:       "host",
  1130  						Path:       "/path",
  1131  						URL:        "host.domain.com/path",
  1132  					},
  1133  					resources.Route{
  1134  						GUID:       "route2-guid",
  1135  						DomainGUID: "domain2-guid",
  1136  						SpaceGUID:  "space-guid",
  1137  						Host:       "",
  1138  						Path:       "",
  1139  						URL:        "domain.com",
  1140  					},
  1141  				))
  1142  			})
  1143  		})
  1144  
  1145  		When("there is a cc error", func() {
  1146  			BeforeEach(func() {
  1147  				response := `{
  1148    "errors": [
  1149      {
  1150        "code": 10008,
  1151        "detail": "The request is semantically invalid: command presence",
  1152        "title": "CF-UnprocessableEntity"
  1153      },
  1154  		{
  1155        "code": 10010,
  1156        "detail": "Isolation segment not found",
  1157        "title": "CF-ResourceNotFound"
  1158      }
  1159    ]
  1160  }`
  1161  				server.AppendHandlers(
  1162  					CombineHandlers(
  1163  						VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/routes"),
  1164  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"get-app-routes-warning"}, "Location": {"job-url"}}),
  1165  					),
  1166  				)
  1167  			})
  1168  
  1169  			It("returns the error", func() {
  1170  				Expect(executeErr).To(MatchError(ccerror.MultiError{
  1171  					ResponseCode: http.StatusTeapot,
  1172  					Errors: []ccerror.V3Error{
  1173  						{
  1174  							Code:   10008,
  1175  							Detail: "The request is semantically invalid: command presence",
  1176  							Title:  "CF-UnprocessableEntity",
  1177  						},
  1178  						{
  1179  							Code:   10010,
  1180  							Detail: "Isolation segment not found",
  1181  							Title:  "CF-ResourceNotFound",
  1182  						},
  1183  					},
  1184  				}))
  1185  				Expect(warnings).To(ConsistOf("get-app-routes-warning"))
  1186  			})
  1187  		})
  1188  	})
  1189  })