github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/api/cloudcontroller/ccv3/application_test.go (about)

     1  package ccv3_test
     2  
     3  import (
     4  	"encoding/json"
     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/constant"
    11  	"code.cloudfoundry.org/cli/types"
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  	. "github.com/onsi/gomega/ghttp"
    15  )
    16  
    17  var _ = Describe("Application", func() {
    18  	var client *Client
    19  
    20  	BeforeEach(func() {
    21  		client, _ = NewTestClient()
    22  	})
    23  
    24  	Describe("Application", func() {
    25  		Describe("MarshalJSON", func() {
    26  			var (
    27  				app      Application
    28  				appBytes []byte
    29  				err      error
    30  			)
    31  
    32  			BeforeEach(func() {
    33  				app = Application{}
    34  			})
    35  
    36  			JustBeforeEach(func() {
    37  				appBytes, err = app.MarshalJSON()
    38  				Expect(err).ToNot(HaveOccurred())
    39  			})
    40  
    41  			When("no lifecycle is provided", func() {
    42  				BeforeEach(func() {
    43  					app = Application{}
    44  				})
    45  
    46  				It("omits the lifecycle from the JSON", func() {
    47  					Expect(string(appBytes)).To(Equal("{}"))
    48  				})
    49  			})
    50  
    51  			When("lifecycle type docker is provided", func() {
    52  				BeforeEach(func() {
    53  					app = Application{
    54  						LifecycleType: constant.AppLifecycleTypeDocker,
    55  					}
    56  				})
    57  
    58  				It("sets lifecycle type to docker with empty data", func() {
    59  					Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"type":"docker","data":{}}}`))
    60  				})
    61  			})
    62  
    63  			When("lifecycle type buildpack is provided", func() {
    64  				BeforeEach(func() {
    65  					app.LifecycleType = constant.AppLifecycleTypeBuildpack
    66  				})
    67  
    68  				When("no buildpacks are provided", func() {
    69  					It("omits the lifecycle from the JSON", func() {
    70  						Expect(string(appBytes)).To(MatchJSON("{}"))
    71  					})
    72  
    73  					When("but you do specify a stack", func() {
    74  						BeforeEach(func() {
    75  							app.StackName = "cflinuxfs9000"
    76  						})
    77  
    78  						It("does, in fact, send the stack in the json", func() {
    79  							Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"stack":"cflinuxfs9000"},"type":"buildpack"}}`))
    80  						})
    81  					})
    82  				})
    83  
    84  				When("default buildpack is provided", func() {
    85  					BeforeEach(func() {
    86  						app.LifecycleBuildpacks = []string{"default"}
    87  					})
    88  
    89  					It("sets the lifecycle buildpack to be empty in the JSON", func() {
    90  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`))
    91  					})
    92  				})
    93  
    94  				When("null buildpack is provided", func() {
    95  					BeforeEach(func() {
    96  						app.LifecycleBuildpacks = []string{"null"}
    97  					})
    98  
    99  					It("sets the Lifecycle buildpack to be empty in the JSON", func() {
   100  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":null},"type":"buildpack"}}`))
   101  					})
   102  				})
   103  
   104  				When("other buildpacks are provided", func() {
   105  					BeforeEach(func() {
   106  						app.LifecycleBuildpacks = []string{"some-buildpack"}
   107  					})
   108  
   109  					It("sets them in the JSON", func() {
   110  						Expect(string(appBytes)).To(MatchJSON(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`))
   111  					})
   112  				})
   113  			})
   114  
   115  			When("metadata is provided", func() {
   116  				BeforeEach(func() {
   117  					app = Application{
   118  						Metadata: &Metadata{
   119  							Labels: map[string]types.NullString{
   120  								"some-key":  types.NewNullString("some-value"),
   121  								"other-key": types.NewNullString("other-value\nwith a newline & a \" quote")},
   122  						},
   123  					}
   124  				})
   125  
   126  				It("should include the labels in the JSON", func() {
   127  					Expect(string(appBytes)).To(MatchJSON(`{
   128  						"metadata": {
   129  							"labels": {
   130  								"some-key":"some-value",
   131  								"other-key":"other-value\nwith a newline & a \" quote"
   132  							}
   133  						}
   134  					}`))
   135  				})
   136  
   137  				When("labels need to be removed", func() {
   138  					BeforeEach(func() {
   139  						app = Application{
   140  							Metadata: &Metadata{
   141  								Labels: map[string]types.NullString{
   142  									"some-key":      types.NewNullString("some-value"),
   143  									"other-key":     types.NewNullString("other-value\nwith a newline & a \" quote"),
   144  									"key-to-delete": types.NewNullString(),
   145  								},
   146  							},
   147  						}
   148  					})
   149  
   150  					It("should send nulls for those labels", func() {
   151  						Expect(string(appBytes)).To(MatchJSON(`{
   152  						"metadata": {
   153  							"labels": {
   154  								"some-key":"some-value",
   155  								"other-key":"other-value\nwith a newline & a \" quote",
   156  								"key-to-delete":null
   157  							}
   158  						}
   159  					}`))
   160  					})
   161  				})
   162  			})
   163  		})
   164  
   165  		Describe("UnmarshalJSON", func() {
   166  			var (
   167  				app      Application
   168  				appBytes []byte
   169  				err      error
   170  			)
   171  
   172  			BeforeEach(func() {
   173  				appBytes = []byte("{}")
   174  				app = Application{}
   175  			})
   176  
   177  			JustBeforeEach(func() {
   178  				err = json.Unmarshal(appBytes, &app)
   179  				Expect(err).ToNot(HaveOccurred())
   180  			})
   181  
   182  			When("no lifecycle is provided", func() {
   183  				BeforeEach(func() {
   184  					appBytes = []byte("{}")
   185  				})
   186  
   187  				It("omits the lifecycle from the JSON", func() {
   188  					Expect(app).To(Equal(Application{}))
   189  				})
   190  			})
   191  
   192  			When("lifecycle type docker is provided", func() {
   193  				BeforeEach(func() {
   194  					appBytes = []byte(`{"lifecycle":{"type":"docker","data":{}}}`)
   195  				})
   196  				It("sets the lifecycle type to docker with empty data", func() {
   197  					Expect(app).To(Equal(Application{
   198  						LifecycleType: constant.AppLifecycleTypeDocker,
   199  					}))
   200  				})
   201  			})
   202  
   203  			When("lifecycle type buildpack is provided", func() {
   204  
   205  				When("other buildpacks are provided", func() {
   206  					BeforeEach(func() {
   207  						appBytes = []byte(`{"lifecycle":{"data":{"buildpacks":["some-buildpack"]},"type":"buildpack"}}`)
   208  					})
   209  
   210  					It("sets them in the JSON", func() {
   211  						Expect(app).To(Equal(Application{
   212  							LifecycleType:       constant.AppLifecycleTypeBuildpack,
   213  							LifecycleBuildpacks: []string{"some-buildpack"},
   214  						}))
   215  					})
   216  				})
   217  			})
   218  
   219  			When("Labels are provided", func() {
   220  				BeforeEach(func() {
   221  					appBytes = []byte(`{"metadata":{"labels":{"some-key":"some-value"}}}`)
   222  				})
   223  
   224  				It("sets the labels", func() {
   225  					Expect(app).To(Equal(Application{
   226  						Metadata: &Metadata{
   227  							Labels: map[string]types.NullString{
   228  								"some-key": types.NewNullString("some-value"),
   229  							},
   230  						},
   231  					}))
   232  				})
   233  			})
   234  		})
   235  	})
   236  
   237  	Describe("CreateApplication", func() {
   238  		var (
   239  			appToCreate Application
   240  
   241  			createdApp Application
   242  			warnings   Warnings
   243  			executeErr error
   244  		)
   245  
   246  		JustBeforeEach(func() {
   247  			createdApp, warnings, executeErr = client.CreateApplication(appToCreate)
   248  		})
   249  
   250  		When("the application successfully is created", func() {
   251  			BeforeEach(func() {
   252  				response := `{
   253  					"guid": "some-app-guid",
   254  					"name": "some-app-name"
   255  				}`
   256  
   257  				expectedBody := map[string]interface{}{
   258  					"name": "some-app-name",
   259  					"relationships": map[string]interface{}{
   260  						"space": map[string]interface{}{
   261  							"data": map[string]string{
   262  								"guid": "some-space-guid",
   263  							},
   264  						},
   265  					},
   266  				}
   267  				server.AppendHandlers(
   268  					CombineHandlers(
   269  						VerifyRequest(http.MethodPost, "/v3/apps"),
   270  						VerifyJSONRepresenting(expectedBody),
   271  						RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   272  					),
   273  				)
   274  
   275  				appToCreate = Application{
   276  					Name: "some-app-name",
   277  					Relationships: Relationships{
   278  						constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"},
   279  					},
   280  				}
   281  			})
   282  
   283  			It("returns the created app and warnings", func() {
   284  				Expect(executeErr).NotTo(HaveOccurred())
   285  				Expect(warnings).To(ConsistOf("this is a warning"))
   286  
   287  				Expect(createdApp).To(Equal(Application{
   288  					Name: "some-app-name",
   289  					GUID: "some-app-guid",
   290  				}))
   291  			})
   292  		})
   293  
   294  		When("the caller specifies a buildpack", func() {
   295  			BeforeEach(func() {
   296  				response := `{
   297  					"guid": "some-app-guid",
   298  					"name": "some-app-name",
   299  					"lifecycle": {
   300  						"type": "buildpack",
   301  						"data": {
   302  							"buildpacks": ["some-buildpack"]
   303  					  }
   304  					}
   305  				}`
   306  
   307  				expectedBody := map[string]interface{}{
   308  					"name": "some-app-name",
   309  					"lifecycle": map[string]interface{}{
   310  						"type": "buildpack",
   311  						"data": map[string]interface{}{
   312  							"buildpacks": []string{"some-buildpack"},
   313  						},
   314  					},
   315  					"relationships": map[string]interface{}{
   316  						"space": map[string]interface{}{
   317  							"data": map[string]string{
   318  								"guid": "some-space-guid",
   319  							},
   320  						},
   321  					},
   322  				}
   323  				server.AppendHandlers(
   324  					CombineHandlers(
   325  						VerifyRequest(http.MethodPost, "/v3/apps"),
   326  						VerifyJSONRepresenting(expectedBody),
   327  						RespondWith(http.StatusCreated, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   328  					),
   329  				)
   330  
   331  				appToCreate = Application{
   332  					Name:                "some-app-name",
   333  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   334  					LifecycleBuildpacks: []string{"some-buildpack"},
   335  					Relationships: Relationships{
   336  						constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"},
   337  					},
   338  				}
   339  			})
   340  
   341  			It("returns the created app and warnings", func() {
   342  				Expect(executeErr).NotTo(HaveOccurred())
   343  				Expect(warnings).To(ConsistOf("this is a warning"))
   344  
   345  				Expect(createdApp).To(Equal(Application{
   346  					Name:                "some-app-name",
   347  					GUID:                "some-app-guid",
   348  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   349  					LifecycleBuildpacks: []string{"some-buildpack"},
   350  				}))
   351  			})
   352  		})
   353  
   354  		When("cc returns back an error or warnings", func() {
   355  			BeforeEach(func() {
   356  				response := `{
   357    "errors": [
   358      {
   359        "code": 10008,
   360        "detail": "The request is semantically invalid: command presence",
   361        "title": "CF-UnprocessableEntity"
   362      },
   363      {
   364        "code": 10010,
   365        "detail": "App not found",
   366        "title": "CF-ResourceNotFound"
   367      }
   368    ]
   369  }`
   370  				server.AppendHandlers(
   371  					CombineHandlers(
   372  						VerifyRequest(http.MethodPost, "/v3/apps"),
   373  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   374  					),
   375  				)
   376  			})
   377  
   378  			It("returns the error and all warnings", func() {
   379  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   380  					ResponseCode: http.StatusTeapot,
   381  					Errors: []ccerror.V3Error{
   382  						{
   383  							Code:   10008,
   384  							Detail: "The request is semantically invalid: command presence",
   385  							Title:  "CF-UnprocessableEntity",
   386  						},
   387  						{
   388  							Code:   10010,
   389  							Detail: "App not found",
   390  							Title:  "CF-ResourceNotFound",
   391  						},
   392  					},
   393  				}))
   394  				Expect(warnings).To(ConsistOf("this is a warning"))
   395  			})
   396  		})
   397  	})
   398  
   399  	Describe("GetApplications", func() {
   400  		var (
   401  			filters []Query
   402  
   403  			apps       []Application
   404  			warnings   Warnings
   405  			executeErr error
   406  		)
   407  
   408  		JustBeforeEach(func() {
   409  			apps, warnings, executeErr = client.GetApplications(filters...)
   410  		})
   411  
   412  		When("applications exist", func() {
   413  			BeforeEach(func() {
   414  				response1 := fmt.Sprintf(`{
   415  	"pagination": {
   416  		"next": {
   417  			"href": "%s/v3/apps?space_guids=some-space-guid&names=some-app-name&page=2&per_page=2"
   418  		}
   419  	},
   420    "resources": [
   421      {
   422        "name": "app-name-1",
   423        "guid": "app-guid-1",
   424  			"lifecycle": {
   425  				"type": "buildpack",
   426  				"data": {
   427  					"buildpacks": ["some-buildpack"],
   428  					"stack": "some-stack"
   429  				}
   430  			}
   431      },
   432      {
   433        "name": "app-name-2",
   434        "guid": "app-guid-2"
   435      }
   436    ]
   437  }`, server.URL())
   438  				response2 := `{
   439  	"pagination": {
   440  		"next": null
   441  	},
   442  	"resources": [
   443  	  {
   444        "name": "app-name-3",
   445  		  "guid": "app-guid-3"
   446  		}
   447  	]
   448  }`
   449  				server.AppendHandlers(
   450  					CombineHandlers(
   451  						VerifyRequest(http.MethodGet, "/v3/apps", "space_guids=some-space-guid&names=some-app-name"),
   452  						RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   453  					),
   454  				)
   455  				server.AppendHandlers(
   456  					CombineHandlers(
   457  						VerifyRequest(http.MethodGet, "/v3/apps", "space_guids=some-space-guid&names=some-app-name&page=2&per_page=2"),
   458  						RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"this is another warning"}}),
   459  					),
   460  				)
   461  
   462  				filters = []Query{
   463  					{Key: SpaceGUIDFilter, Values: []string{"some-space-guid"}},
   464  					{Key: NameFilter, Values: []string{"some-app-name"}},
   465  				}
   466  			})
   467  
   468  			It("returns the queried applications and all warnings", func() {
   469  				Expect(executeErr).NotTo(HaveOccurred())
   470  				Expect(warnings).To(ConsistOf("this is a warning", "this is another warning"))
   471  
   472  				Expect(apps).To(ConsistOf(
   473  					Application{
   474  						Name:                "app-name-1",
   475  						GUID:                "app-guid-1",
   476  						StackName:           "some-stack",
   477  						LifecycleType:       constant.AppLifecycleTypeBuildpack,
   478  						LifecycleBuildpacks: []string{"some-buildpack"},
   479  					},
   480  					Application{Name: "app-name-2", GUID: "app-guid-2"},
   481  					Application{Name: "app-name-3", GUID: "app-guid-3"},
   482  				))
   483  			})
   484  		})
   485  
   486  		When("the cloud controller returns errors and warnings", func() {
   487  			BeforeEach(func() {
   488  				response := `{
   489    "errors": [
   490      {
   491        "code": 10008,
   492        "detail": "The request is semantically invalid: command presence",
   493        "title": "CF-UnprocessableEntity"
   494      },
   495      {
   496        "code": 10010,
   497        "detail": "App not found",
   498        "title": "CF-ResourceNotFound"
   499      }
   500    ]
   501  }`
   502  				server.AppendHandlers(
   503  					CombineHandlers(
   504  						VerifyRequest(http.MethodGet, "/v3/apps"),
   505  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   506  					),
   507  				)
   508  			})
   509  
   510  			It("returns the error and all warnings", func() {
   511  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   512  					ResponseCode: http.StatusTeapot,
   513  					Errors: []ccerror.V3Error{
   514  						{
   515  							Code:   10008,
   516  							Detail: "The request is semantically invalid: command presence",
   517  							Title:  "CF-UnprocessableEntity",
   518  						},
   519  						{
   520  							Code:   10010,
   521  							Detail: "App not found",
   522  							Title:  "CF-ResourceNotFound",
   523  						},
   524  					},
   525  				}))
   526  				Expect(warnings).To(ConsistOf("this is a warning"))
   527  			})
   528  		})
   529  	})
   530  
   531  	Describe("UpdateApplication", func() {
   532  		var (
   533  			appToUpdate Application
   534  
   535  			updatedApp Application
   536  			warnings   Warnings
   537  			executeErr error
   538  		)
   539  
   540  		JustBeforeEach(func() {
   541  			updatedApp, warnings, executeErr = client.UpdateApplication(appToUpdate)
   542  		})
   543  
   544  		When("the application successfully is updated", func() {
   545  			BeforeEach(func() {
   546  				response := `{
   547  					"guid": "some-app-guid",
   548  					"name": "some-app-name",
   549  					"lifecycle": {
   550  						"type": "buildpack",
   551  						"data": {
   552  							"buildpacks": ["some-buildpack"],
   553  							"stack": "some-stack-name"
   554  						}
   555  					}
   556  				}`
   557  
   558  				expectedBody := map[string]interface{}{
   559  					"name": "some-app-name",
   560  					"lifecycle": map[string]interface{}{
   561  						"type": "buildpack",
   562  						"data": map[string]interface{}{
   563  							"buildpacks": []string{"some-buildpack"},
   564  							"stack":      "some-stack-name",
   565  						},
   566  					},
   567  					"relationships": map[string]interface{}{
   568  						"space": map[string]interface{}{
   569  							"data": map[string]string{
   570  								"guid": "some-space-guid",
   571  							},
   572  						},
   573  					},
   574  				}
   575  				server.AppendHandlers(
   576  					CombineHandlers(
   577  						VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid"),
   578  						VerifyJSONRepresenting(expectedBody),
   579  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   580  					),
   581  				)
   582  
   583  				appToUpdate = Application{
   584  					GUID:                "some-app-guid",
   585  					Name:                "some-app-name",
   586  					StackName:           "some-stack-name",
   587  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   588  					LifecycleBuildpacks: []string{"some-buildpack"},
   589  					Relationships: Relationships{
   590  						constant.RelationshipTypeSpace: Relationship{GUID: "some-space-guid"},
   591  					},
   592  				}
   593  			})
   594  
   595  			It("returns the updated app and warnings", func() {
   596  				Expect(executeErr).NotTo(HaveOccurred())
   597  				Expect(warnings).To(ConsistOf("this is a warning"))
   598  
   599  				Expect(updatedApp).To(Equal(Application{
   600  					GUID:                "some-app-guid",
   601  					StackName:           "some-stack-name",
   602  					LifecycleBuildpacks: []string{"some-buildpack"},
   603  					LifecycleType:       constant.AppLifecycleTypeBuildpack,
   604  					Name:                "some-app-name",
   605  				}))
   606  			})
   607  		})
   608  
   609  		When("cc returns back an error or warnings", func() {
   610  			BeforeEach(func() {
   611  				response := `{
   612    "errors": [
   613      {
   614        "code": 10008,
   615        "detail": "The request is semantically invalid: command presence",
   616        "title": "CF-UnprocessableEntity"
   617      },
   618      {
   619        "code": 10010,
   620        "detail": "App not found",
   621        "title": "CF-ResourceNotFound"
   622      }
   623    ]
   624  }`
   625  				server.AppendHandlers(
   626  					CombineHandlers(
   627  						VerifyRequest(http.MethodPatch, "/v3/apps/some-app-guid"),
   628  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   629  					),
   630  				)
   631  
   632  				appToUpdate = Application{
   633  					GUID: "some-app-guid",
   634  				}
   635  			})
   636  
   637  			It("returns the error and all warnings", func() {
   638  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   639  					ResponseCode: http.StatusTeapot,
   640  					Errors: []ccerror.V3Error{
   641  						{
   642  							Code:   10008,
   643  							Detail: "The request is semantically invalid: command presence",
   644  							Title:  "CF-UnprocessableEntity",
   645  						},
   646  						{
   647  							Code:   10010,
   648  							Detail: "App not found",
   649  							Title:  "CF-ResourceNotFound",
   650  						},
   651  					},
   652  				}))
   653  				Expect(warnings).To(ConsistOf("this is a warning"))
   654  			})
   655  		})
   656  	})
   657  
   658  	Describe("UpdateApplicationStop", func() {
   659  		var (
   660  			responseApp Application
   661  			warnings    Warnings
   662  			executeErr  error
   663  		)
   664  
   665  		JustBeforeEach(func() {
   666  			responseApp, warnings, executeErr = client.UpdateApplicationStop("some-app-guid")
   667  		})
   668  
   669  		When("the response succeeds", func() {
   670  			BeforeEach(func() {
   671  				response := `
   672  {
   673  	"guid": "some-app-guid",
   674  	"name": "some-app",
   675  	"state": "STOPPED"
   676  }`
   677  				server.AppendHandlers(
   678  					CombineHandlers(
   679  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/stop"),
   680  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   681  					),
   682  				)
   683  			})
   684  
   685  			It("returns the application, warnings, and no error", func() {
   686  				Expect(responseApp).To(Equal(Application{
   687  					GUID:  "some-app-guid",
   688  					Name:  "some-app",
   689  					State: constant.ApplicationStopped,
   690  				}))
   691  				Expect(executeErr).ToNot(HaveOccurred())
   692  				Expect(warnings).To(ConsistOf("this is a warning"))
   693  			})
   694  		})
   695  
   696  		When("the CC returns an error", func() {
   697  			BeforeEach(func() {
   698  				response := `{
   699    "errors": [
   700      {
   701        "code": 10008,
   702        "detail": "The request is semantically invalid: command presence",
   703        "title": "CF-UnprocessableEntity"
   704      },
   705      {
   706        "code": 10010,
   707        "detail": "App not found",
   708        "title": "CF-ResourceNotFound"
   709      }
   710    ]
   711  }`
   712  				server.AppendHandlers(
   713  					CombineHandlers(
   714  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/stop"),
   715  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   716  					),
   717  				)
   718  			})
   719  
   720  			It("returns no app, the error and all warnings", func() {
   721  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   722  					ResponseCode: http.StatusTeapot,
   723  					Errors: []ccerror.V3Error{
   724  						{
   725  							Code:   10008,
   726  							Detail: "The request is semantically invalid: command presence",
   727  							Title:  "CF-UnprocessableEntity",
   728  						},
   729  						{
   730  							Code:   10010,
   731  							Detail: "App not found",
   732  							Title:  "CF-ResourceNotFound",
   733  						},
   734  					},
   735  				}))
   736  				Expect(warnings).To(ConsistOf("this is a warning"))
   737  			})
   738  		})
   739  	})
   740  
   741  	Describe("UpdateApplicationStart", func() {
   742  		var (
   743  			app        Application
   744  			warnings   Warnings
   745  			executeErr error
   746  		)
   747  
   748  		JustBeforeEach(func() {
   749  			app, warnings, executeErr = client.UpdateApplicationStart("some-app-guid")
   750  		})
   751  
   752  		When("the response succeeds", func() {
   753  			BeforeEach(func() {
   754  				response := `
   755  {
   756  	"guid": "some-app-guid",
   757  	"name": "some-app"
   758  }`
   759  				server.AppendHandlers(
   760  					CombineHandlers(
   761  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/start"),
   762  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   763  					),
   764  				)
   765  			})
   766  
   767  			It("returns warnings and no error", func() {
   768  				Expect(executeErr).ToNot(HaveOccurred())
   769  				Expect(warnings).To(ConsistOf("this is a warning"))
   770  				Expect(app.GUID).To(Equal("some-app-guid"))
   771  			})
   772  		})
   773  
   774  		When("cc returns back an error or warnings", func() {
   775  			BeforeEach(func() {
   776  				response := `{
   777    "errors": [
   778      {
   779        "code": 10008,
   780        "detail": "The request is semantically invalid: command presence",
   781        "title": "CF-UnprocessableEntity"
   782      },
   783      {
   784        "code": 10010,
   785        "detail": "App not found",
   786        "title": "CF-ResourceNotFound"
   787      }
   788    ]
   789  }`
   790  				server.AppendHandlers(
   791  					CombineHandlers(
   792  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/start"),
   793  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   794  					),
   795  				)
   796  			})
   797  
   798  			It("returns the error and all warnings", func() {
   799  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   800  					ResponseCode: http.StatusTeapot,
   801  					Errors: []ccerror.V3Error{
   802  						{
   803  							Code:   10008,
   804  							Detail: "The request is semantically invalid: command presence",
   805  							Title:  "CF-UnprocessableEntity",
   806  						},
   807  						{
   808  							Code:   10010,
   809  							Detail: "App not found",
   810  							Title:  "CF-ResourceNotFound",
   811  						},
   812  					},
   813  				}))
   814  				Expect(warnings).To(ConsistOf("this is a warning"))
   815  			})
   816  		})
   817  	})
   818  
   819  	Describe("UpdateApplicationRestart", func() {
   820  		var (
   821  			responseApp Application
   822  			warnings    Warnings
   823  			executeErr  error
   824  		)
   825  
   826  		JustBeforeEach(func() {
   827  			responseApp, warnings, executeErr = client.UpdateApplicationRestart("some-app-guid")
   828  		})
   829  
   830  		When("the response succeeds", func() {
   831  			BeforeEach(func() {
   832  				response := `
   833  {
   834  	"guid": "some-app-guid",
   835  	"name": "some-app",
   836  	"state": "STARTED"
   837  }`
   838  				server.AppendHandlers(
   839  					CombineHandlers(
   840  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/restart"),
   841  						RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   842  					),
   843  				)
   844  			})
   845  
   846  			It("returns the application, warnings, and no error", func() {
   847  				Expect(responseApp).To(Equal(Application{
   848  					GUID:  "some-app-guid",
   849  					Name:  "some-app",
   850  					State: constant.ApplicationStarted,
   851  				}))
   852  				Expect(executeErr).ToNot(HaveOccurred())
   853  				Expect(warnings).To(ConsistOf("this is a warning"))
   854  			})
   855  		})
   856  
   857  		When("the CC returns an error", func() {
   858  			BeforeEach(func() {
   859  				response := `{
   860    "errors": [
   861      {
   862        "code": 10008,
   863        "detail": "The request is semantically invalid: command presence",
   864        "title": "CF-UnprocessableEntity"
   865      },
   866      {
   867        "code": 10010,
   868        "detail": "App not found",
   869        "title": "CF-ResourceNotFound"
   870      }
   871    ]
   872  }`
   873  				server.AppendHandlers(
   874  					CombineHandlers(
   875  						VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/actions/restart"),
   876  						RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}),
   877  					),
   878  				)
   879  			})
   880  
   881  			It("returns no app, the error and all warnings", func() {
   882  				Expect(executeErr).To(MatchError(ccerror.MultiError{
   883  					ResponseCode: http.StatusTeapot,
   884  					Errors: []ccerror.V3Error{
   885  						{
   886  							Code:   10008,
   887  							Detail: "The request is semantically invalid: command presence",
   888  							Title:  "CF-UnprocessableEntity",
   889  						},
   890  						{
   891  							Code:   10010,
   892  							Detail: "App not found",
   893  							Title:  "CF-ResourceNotFound",
   894  						},
   895  					},
   896  				}))
   897  				Expect(warnings).To(ConsistOf("this is a warning"))
   898  			})
   899  		})
   900  	})
   901  })