github.com/cloudfoundry-attic/cli-with-i18n@v6.32.1-0.20171002233121-7401370d3b85+incompatible/api/cloudcontroller/ccv3/process_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/api/cloudcontroller/ccv3/constant" 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("Process", func() { 17 var client *Client 18 19 BeforeEach(func() { 20 client = NewTestClient() 21 }) 22 23 Describe("GetApplicationProcesses", func() { 24 Context("when the application exists", func() { 25 BeforeEach(func() { 26 response1 := fmt.Sprintf(` 27 { 28 "pagination": { 29 "next": { 30 "href": "%s/v3/apps/some-app-guid/processes?page=2" 31 } 32 }, 33 "resources": [ 34 { 35 "guid": "process-1-guid", 36 "type": "web", 37 "memory_in_mb": 32, 38 "health_check": { 39 "type": "port", 40 "data": { 41 "timeout": null, 42 "endpoint": null 43 } 44 } 45 }, 46 { 47 "guid": "process-2-guid", 48 "type": "worker", 49 "memory_in_mb": 64, 50 "health_check": { 51 "type": "http", 52 "data": { 53 "timeout": 60, 54 "endpoint": "/health" 55 } 56 } 57 } 58 ] 59 }`, server.URL()) 60 response2 := ` 61 { 62 "pagination": { 63 "next": null 64 }, 65 "resources": [ 66 { 67 "guid": "process-3-guid", 68 "type": "console", 69 "memory_in_mb": 128, 70 "health_check": { 71 "type": "process", 72 "data": { 73 "timeout": 90, 74 "endpoint": null 75 } 76 } 77 } 78 ] 79 }` 80 server.AppendHandlers( 81 CombineHandlers( 82 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 83 RespondWith(http.StatusOK, response1, http.Header{"X-Cf-Warnings": {"warning-1"}}), 84 ), 85 ) 86 server.AppendHandlers( 87 CombineHandlers( 88 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes", "page=2"), 89 RespondWith(http.StatusOK, response2, http.Header{"X-Cf-Warnings": {"warning-2"}}), 90 ), 91 ) 92 }) 93 94 It("returns a list of processes associated with the application and all warnings", func() { 95 processes, warnings, err := client.GetApplicationProcesses("some-app-guid") 96 Expect(err).ToNot(HaveOccurred()) 97 98 Expect(processes).To(ConsistOf( 99 Process{ 100 GUID: "process-1-guid", 101 Type: constant.ProcessTypeWeb, 102 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 103 HealthCheck: ProcessHealthCheck{Type: "port"}, 104 }, 105 Process{ 106 GUID: "process-2-guid", 107 Type: "worker", 108 MemoryInMB: types.NullUint64{Value: 64, IsSet: true}, 109 HealthCheck: ProcessHealthCheck{ 110 Type: "http", 111 Data: ProcessHealthCheckData{Endpoint: "/health"}, 112 }, 113 }, 114 Process{ 115 GUID: "process-3-guid", 116 Type: "console", 117 MemoryInMB: types.NullUint64{Value: 128, IsSet: true}, 118 HealthCheck: ProcessHealthCheck{Type: "process"}, 119 }, 120 )) 121 Expect(warnings).To(ConsistOf("warning-1", "warning-2")) 122 }) 123 }) 124 125 Context("when cloud controller returns an error", func() { 126 BeforeEach(func() { 127 response := `{ 128 "errors": [ 129 { 130 "code": 10010, 131 "detail": "App not found", 132 "title": "CF-ResourceNotFound" 133 } 134 ] 135 }` 136 server.AppendHandlers( 137 CombineHandlers( 138 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes"), 139 RespondWith(http.StatusNotFound, response), 140 ), 141 ) 142 }) 143 144 It("returns the error", func() { 145 _, _, err := client.GetApplicationProcesses("some-app-guid") 146 Expect(err).To(MatchError(ccerror.ApplicationNotFoundError{})) 147 }) 148 }) 149 }) 150 151 Describe("GetApplicationProcessByType", func() { 152 var ( 153 process Process 154 warnings []string 155 err error 156 ) 157 158 JustBeforeEach(func() { 159 process, warnings, err = client.GetApplicationProcessByType("some-app-guid", "some-type") 160 }) 161 162 Context("when the process exists", func() { 163 BeforeEach(func() { 164 response := `{ 165 "guid": "process-1-guid", 166 "type": "some-type", 167 "memory_in_mb": 32, 168 "health_check": { 169 "type": "http", 170 "data": { 171 "timeout": 90, 172 "endpoint": "/health" 173 } 174 } 175 }` 176 server.AppendHandlers( 177 CombineHandlers( 178 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 179 RespondWith(http.StatusOK, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 180 ), 181 ) 182 }) 183 184 It("returns the process and all warnings", func() { 185 Expect(err).NotTo(HaveOccurred()) 186 Expect(warnings).To(ConsistOf("this is a warning")) 187 Expect(process).To(Equal(Process{ 188 GUID: "process-1-guid", 189 Type: "some-type", 190 MemoryInMB: types.NullUint64{Value: 32, IsSet: true}, 191 HealthCheck: ProcessHealthCheck{ 192 Type: "http", 193 Data: ProcessHealthCheckData{Endpoint: "/health"}}, 194 })) 195 }) 196 }) 197 198 Context("when the application does not exist", func() { 199 BeforeEach(func() { 200 response := `{ 201 "errors": [ 202 { 203 "detail": "Application not found", 204 "title": "CF-ResourceNotFound", 205 "code": 10010 206 } 207 ] 208 }` 209 server.AppendHandlers( 210 CombineHandlers( 211 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 212 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 213 ), 214 ) 215 }) 216 217 It("returns a ResourceNotFoundError", func() { 218 Expect(warnings).To(ConsistOf("this is a warning")) 219 Expect(err).To(MatchError(ccerror.ResourceNotFoundError{Message: "Application not found"})) 220 }) 221 }) 222 223 Context("when the cloud controller returns errors and warnings", func() { 224 BeforeEach(func() { 225 response := `{ 226 "errors": [ 227 { 228 "code": 10008, 229 "detail": "The request is semantically invalid: command presence", 230 "title": "CF-UnprocessableEntity" 231 }, 232 { 233 "code": 10009, 234 "detail": "Some CC Error", 235 "title": "CF-SomeNewError" 236 } 237 ] 238 }` 239 server.AppendHandlers( 240 CombineHandlers( 241 VerifyRequest(http.MethodGet, "/v3/apps/some-app-guid/processes/some-type"), 242 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 243 ), 244 ) 245 }) 246 247 It("returns the error and all warnings", func() { 248 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 249 ResponseCode: http.StatusTeapot, 250 V3ErrorResponse: ccerror.V3ErrorResponse{ 251 Errors: []ccerror.V3Error{ 252 { 253 Code: 10008, 254 Detail: "The request is semantically invalid: command presence", 255 Title: "CF-UnprocessableEntity", 256 }, 257 { 258 Code: 10009, 259 Detail: "Some CC Error", 260 Title: "CF-SomeNewError", 261 }, 262 }, 263 }, 264 })) 265 Expect(warnings).To(ConsistOf("this is a warning")) 266 }) 267 }) 268 }) 269 270 Describe("PatchApplicationProcessHealthCheck", func() { 271 var ( 272 endpoint string 273 274 warnings []string 275 err error 276 ) 277 278 JustBeforeEach(func() { 279 warnings, err = client.PatchApplicationProcessHealthCheck("some-process-guid", "some-type", endpoint) 280 }) 281 282 Context("when patching the process succeeds", func() { 283 Context("and the endpoint is non-empty", func() { 284 BeforeEach(func() { 285 endpoint = "some-endpoint" 286 expectedBody := `{ 287 "health_check": { 288 "type": "some-type", 289 "data": { 290 "endpoint": "some-endpoint" 291 } 292 } 293 }` 294 server.AppendHandlers( 295 CombineHandlers( 296 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 297 VerifyJSON(expectedBody), 298 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 299 ), 300 ) 301 }) 302 303 It("patches this process's health check", func() { 304 Expect(err).ToNot(HaveOccurred()) 305 Expect(warnings).To(ConsistOf("this is a warning")) 306 }) 307 }) 308 309 Context("and the endpoint is empty", func() { 310 BeforeEach(func() { 311 endpoint = "" 312 expectedBody := `{ 313 "health_check": { 314 "type": "some-type", 315 "data": { 316 "endpoint": null 317 } 318 } 319 }` 320 server.AppendHandlers( 321 CombineHandlers( 322 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 323 VerifyJSON(expectedBody), 324 RespondWith(http.StatusOK, "", http.Header{"X-Cf-Warnings": {"this is a warning"}}), 325 ), 326 ) 327 }) 328 329 It("patches this process's health check", func() { 330 Expect(err).ToNot(HaveOccurred()) 331 Expect(warnings).To(ConsistOf("this is a warning")) 332 }) 333 }) 334 }) 335 336 Context("when the process does not exist", func() { 337 BeforeEach(func() { 338 endpoint = "some-endpoint" 339 response := `{ 340 "errors": [ 341 { 342 "detail": "Process not found", 343 "title": "CF-ResourceNotFound", 344 "code": 10010 345 } 346 ] 347 }` 348 349 server.AppendHandlers( 350 CombineHandlers( 351 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 352 RespondWith(http.StatusNotFound, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 353 ), 354 ) 355 }) 356 357 It("returns an error and warnings", func() { 358 Expect(err).To(MatchError(ccerror.ProcessNotFoundError{})) 359 Expect(warnings).To(ConsistOf("this is a warning")) 360 }) 361 }) 362 363 Context("when the cloud controller returns errors and warnings", func() { 364 BeforeEach(func() { 365 endpoint = "some-endpoint" 366 response := `{ 367 "errors": [ 368 { 369 "code": 10008, 370 "detail": "The request is semantically invalid: command presence", 371 "title": "CF-UnprocessableEntity" 372 }, 373 { 374 "code": 10009, 375 "detail": "Some CC Error", 376 "title": "CF-SomeNewError" 377 } 378 ] 379 }` 380 server.AppendHandlers( 381 CombineHandlers( 382 VerifyRequest(http.MethodPatch, "/v3/processes/some-process-guid"), 383 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 384 ), 385 ) 386 }) 387 388 It("returns the error and all warnings", func() { 389 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 390 ResponseCode: http.StatusTeapot, 391 V3ErrorResponse: ccerror.V3ErrorResponse{ 392 Errors: []ccerror.V3Error{ 393 { 394 Code: 10008, 395 Detail: "The request is semantically invalid: command presence", 396 Title: "CF-UnprocessableEntity", 397 }, 398 { 399 Code: 10009, 400 Detail: "Some CC Error", 401 Title: "CF-SomeNewError", 402 }, 403 }, 404 }, 405 })) 406 Expect(warnings).To(ConsistOf("this is a warning")) 407 }) 408 }) 409 }) 410 411 Describe("CreateApplicationProcessScale", func() { 412 var passedProcess Process 413 414 Context("when providing all scale options", func() { 415 BeforeEach(func() { 416 passedProcess = Process{ 417 Type: constant.ProcessTypeWeb, 418 Instances: types.NullInt{Value: 2, IsSet: true}, 419 MemoryInMB: types.NullUint64{Value: 100, IsSet: true}, 420 DiskInMB: types.NullUint64{Value: 200, IsSet: true}, 421 } 422 expectedBody := `{ 423 "instances": 2, 424 "memory_in_mb": 100, 425 "disk_in_mb": 200 426 }` 427 response := `{ 428 "guid": "some-process-guid" 429 }` 430 server.AppendHandlers( 431 CombineHandlers( 432 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 433 VerifyJSON(expectedBody), 434 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 435 ), 436 ) 437 }) 438 439 It("scales the application process; returns all warnings", func() { 440 warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 441 Expect(err).ToNot(HaveOccurred()) 442 Expect(warnings).To(ConsistOf("this is a warning")) 443 }) 444 }) 445 446 Context("when providing all scale options with 0 values", func() { 447 BeforeEach(func() { 448 passedProcess = Process{ 449 Type: constant.ProcessTypeWeb, 450 Instances: types.NullInt{Value: 0, IsSet: true}, 451 MemoryInMB: types.NullUint64{Value: 0, IsSet: true}, 452 DiskInMB: types.NullUint64{Value: 0, IsSet: true}, 453 } 454 expectedBody := `{ 455 "instances": 0, 456 "memory_in_mb": 0, 457 "disk_in_mb": 0 458 }` 459 response := `{ 460 "guid": "some-process-guid" 461 }` 462 server.AppendHandlers( 463 CombineHandlers( 464 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 465 VerifyJSON(expectedBody), 466 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 467 ), 468 ) 469 }) 470 471 It("scales the application process to 0 values; returns the scaled process and all warnings", func() { 472 warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 473 Expect(err).ToNot(HaveOccurred()) 474 Expect(warnings).To(ConsistOf("this is a warning")) 475 }) 476 }) 477 478 Context("when providing only one scale option", func() { 479 BeforeEach(func() { 480 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 481 expectedBody := `{ 482 "instances": 2 483 }` 484 response := `{ 485 "guid": "some-process-guid", 486 }` 487 server.AppendHandlers( 488 CombineHandlers( 489 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 490 VerifyJSON(expectedBody), 491 RespondWith(http.StatusAccepted, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 492 ), 493 ) 494 }) 495 496 It("scales the application process; returns all warnings", func() { 497 warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 498 Expect(err).ToNot(HaveOccurred()) 499 Expect(warnings).To(ConsistOf("this is a warning")) 500 }) 501 }) 502 503 Context("when an error is encountered", func() { 504 BeforeEach(func() { 505 passedProcess = Process{Type: constant.ProcessTypeWeb, Instances: types.NullInt{Value: 2, IsSet: true}} 506 response := `{ 507 "errors": [ 508 { 509 "code": 10008, 510 "detail": "The request is semantically invalid: command presence", 511 "title": "CF-UnprocessableEntity" 512 }, 513 { 514 "code": 10009, 515 "detail": "Some CC Error", 516 "title": "CF-SomeNewError" 517 } 518 ] 519 }` 520 server.AppendHandlers( 521 CombineHandlers( 522 VerifyRequest(http.MethodPost, "/v3/apps/some-app-guid/processes/web/actions/scale"), 523 RespondWith(http.StatusTeapot, response, http.Header{"X-Cf-Warnings": {"this is a warning"}}), 524 ), 525 ) 526 }) 527 528 It("returns the error and all warnings", func() { 529 warnings, err := client.CreateApplicationProcessScale("some-app-guid", passedProcess) 530 Expect(err).To(MatchError(ccerror.V3UnexpectedResponseError{ 531 ResponseCode: http.StatusTeapot, 532 V3ErrorResponse: ccerror.V3ErrorResponse{ 533 Errors: []ccerror.V3Error{ 534 { 535 Code: 10008, 536 Detail: "The request is semantically invalid: command presence", 537 Title: "CF-UnprocessableEntity", 538 }, 539 { 540 Code: 10009, 541 Detail: "Some CC Error", 542 Title: "CF-SomeNewError", 543 }, 544 }, 545 }, 546 })) 547 Expect(warnings).To(ConsistOf("this is a warning")) 548 }) 549 }) 550 }) 551 })