github.com/sleungcy-sap/cli@v7.1.0+incompatible/integration/v7/isolated/curl_command_test.go (about) 1 package isolated 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "regexp" 9 "strings" 10 11 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 12 13 "code.cloudfoundry.org/cli/integration/helpers" 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 . "github.com/onsi/gomega/gbytes" 17 . "github.com/onsi/gomega/gexec" 18 ) 19 20 var _ = Describe("curl command", func() { 21 var ExpectHelpText = func(session *Session) { 22 Eventually(session).Should(Say(`NAME:\n`)) 23 Eventually(session).Should(Say(`curl - Executes a request to the targeted API endpoint\n`)) 24 Eventually(session).Should(Say(`\n`)) 25 26 Eventually(session).Should(Say(`USAGE:\n`)) 27 Eventually(session).Should(Say(`\s+cf curl PATH \[-iv\] \[-X METHOD\] \[-H HEADER\]\.\.\. \[-d DATA\] \[--output FILE\]`)) 28 Eventually(session).Should(Say(`\s+By default 'cf curl' will perform a GET to the specified PATH. If data`)) 29 Eventually(session).Should(Say(`\s+is provided via -d, a POST will be performed instead, and the Content-Type\n`)) 30 Eventually(session).Should(Say(`\s+will be set to application/json. You may override headers with -H and the\n`)) 31 Eventually(session).Should(Say(`\s+request method with -X.\n`)) 32 Eventually(session).Should(Say(`\s+For API documentation, please visit http://apidocs.cloudfoundry.org.\n`)) 33 Eventually(session).Should(Say(`\n`)) 34 35 Eventually(session).Should(Say(`EXAMPLES:\n`)) 36 Eventually(session).Should(Say(`\s+cf curl \"/v2/apps\" -X GET -H \"Content-Type: application/x-www-form-urlencoded\" -d 'q=name:myapp'`)) 37 Eventually(session).Should(Say(`\s+cf curl \"/v2/apps\" -d @/path/to/file`)) 38 Eventually(session).Should(Say(`\n`)) 39 40 Eventually(session).Should(Say(`OPTIONS:\n`)) 41 Eventually(session).Should(Say(`\s+-H\s+Custom headers to include in the request, flag can be specified multiple times`)) 42 Eventually(session).Should(Say(`\s+-X\s+HTTP method \(GET,POST,PUT,DELETE,etc\)`)) 43 Eventually(session).Should(Say(`\s+-d\s+HTTP data to include in the request body, or '@' followed by a file name to read the data from`)) 44 Eventually(session).Should(Say(`\s+--fail,\s+-f\s+Server errors return exit code 22`)) 45 Eventually(session).Should(Say(`\s+-i\s+Include response headers in the output`)) 46 Eventually(session).Should(Say(`\s+--output\s+Write curl body to FILE instead of stdout`)) 47 } 48 49 var ExpectRequestHeaders = func(session *Session) { 50 Eventually(session).Should(Say(`REQUEST: .+`)) 51 Eventually(session).Should(Say(`(GET|POST|PUT|DELETE) /v2/apps HTTP/1.1`)) 52 Eventually(session).Should(Say(`Host: .+`)) 53 Eventually(session).Should(Say(`Accept: .+`)) 54 Eventually(session).Should(Say(`Authorization:\s+\[PRIVATE DATA HIDDEN\]`)) 55 Eventually(session).Should(Say(`Content-Type: .+`)) 56 Eventually(session).Should(Say(`User-Agent: .+`)) 57 } 58 59 var ExpectResponseHeaders = func(session *Session) { 60 Eventually(session).Should(Say("HTTP/1.1 200 OK")) 61 Eventually(session).Should(Say(`Connection: .+`)) 62 Eventually(session).Should(Say(`Content-Length: .+`)) 63 Eventually(session).Should(Say(`Content-Type: .+`)) 64 Eventually(session).Should(Say(`Date: .+`)) 65 Eventually(session).Should(Say(`Server: .+`)) 66 Eventually(session).Should(Say(`X-Content-Type-Options: .+`)) 67 Eventually(session).Should(Say(`X-Vcap-Request-Id: .+`)) 68 } 69 70 Describe("Help Text", func() { 71 When("--help flag is set", func() { 72 It("appears in cf help -a", func() { 73 session := helpers.CF("help", "-a") 74 Eventually(session).Should(Exit(0)) 75 Expect(session).To(HaveCommandInCategoryWithDescription("curl", "ADVANCED", "Executes a request to the targeted API endpoint")) 76 }) 77 78 It("Displays command usage to the output", func() { 79 session := helpers.CF("curl", "--help") 80 ExpectHelpText(session) 81 Eventually(session).Should(Exit(0)) 82 }) 83 }) 84 }) 85 86 Describe("Incorrect Usage", func() { 87 When("no arguments are provided", func() { 88 It("fails and displays the help text", func() { 89 session := helpers.CF("curl") 90 Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `PATH` was not provided")) 91 ExpectHelpText(session) 92 Eventually(session).Should(Exit(1)) 93 }) 94 }) 95 96 When("unknown flag is specified", func() { 97 It("fails and displays the help text", func() { 98 session := helpers.CF("curl", "--test") 99 Eventually(session.Err).Should(Say("Incorrect Usage: unknown flag `test'")) 100 ExpectHelpText(session) 101 Eventually(session).Should(Exit(1)) 102 }) 103 }) 104 }) 105 106 When("the user is not logged in", func() { 107 It("makes the request and receives an unauthenticated error", func() { 108 session := helpers.CF("curl", "/v2/apps") 109 expectedJSON := `{ 110 "description": "Authentication error", 111 "error_code": "CF-NotAuthenticated", 112 "code": 10002 113 }` 114 Eventually(session).Should(Exit(0)) 115 Expect(session.Out.Contents()).To(MatchJSON(expectedJSON)) 116 }) 117 }) 118 119 Describe("User Agent", func() { 120 It("sets the User-Agent Header to contain the CLI version", func() { 121 getVersionNumber := func() string { 122 versionSession := helpers.CF("version") 123 Eventually(versionSession).Should(Exit(0)) 124 versionPattern := regexp.MustCompile("cf version (.+)") 125 version := versionPattern.FindStringSubmatch(string(versionSession.Out.Contents())) 126 return regexp.QuoteMeta(version[1]) 127 } 128 session := helpers.CF("curl", "/v2/info", "-v") 129 Eventually(session).Should(Exit(0)) 130 131 Expect(session).To(Say(`User-Agent: go-cli %s`, getVersionNumber())) 132 }) 133 }) 134 135 When("the user is logged in", func() { 136 var orgName string 137 138 BeforeEach(func() { 139 orgName = helpers.NewOrgName() 140 spaceName := helpers.NewSpaceName() 141 142 helpers.SetupCF(orgName, spaceName) 143 helpers.SwitchToOrgRole(orgName, "OrgManager") 144 helpers.TargetOrg(orgName) 145 }) 146 147 When("the path is valid", func() { 148 var expectedJSON string 149 150 BeforeEach(func() { 151 expectedJSON = `{ 152 "total_results": 0, 153 "total_pages": 1, 154 "prev_url": null, 155 "next_url": null, 156 "resources": [] 157 }` 158 }) 159 160 When("the path has multiple initial slashes", func() { 161 It("changes the path to use only one slash", func() { 162 session := helpers.CF("curl", "////v2/apps", "-v") 163 Eventually(session).Should(Exit(0)) 164 165 Eventually(session).Should(Say(`GET /v2/apps HTTP/1.1`)) 166 }) 167 }) 168 169 When("the path has no initial slashes", func() { 170 It("prepends a slash to the path", func() { 171 session := helpers.CF("curl", "v2/apps", "-v") 172 Eventually(session).Should(Exit(0)) 173 174 Eventually(session).Should(Say(`GET /v2/apps HTTP/1.1`)) 175 }) 176 }) 177 178 When("no flag is set", func() { 179 It("makes the request and displays the json response", func() { 180 session := helpers.CF("curl", "/v2/apps") 181 Eventually(session).Should(Exit(0)) 182 Expect(session.Out.Contents()).To(MatchJSON(expectedJSON)) 183 }) 184 }) 185 186 When("-i flag is set", func() { 187 It("displays the response headers", func() { 188 session := helpers.CF("curl", "/v2/apps", "-i") 189 Eventually(session).Should(Exit(0)) 190 191 ExpectResponseHeaders(session) 192 contents := string(session.Out.Contents()) 193 jsonStartsAt := strings.Index(contents, "{") 194 195 actualJSON := contents[jsonStartsAt:] 196 Expect(actualJSON).To(MatchJSON(expectedJSON)) 197 }) 198 }) 199 200 When("-v flag is set", func() { 201 It("displays the request headers and response headers", func() { 202 session := helpers.CF("curl", "/v2/apps", "-v") 203 Eventually(session).Should(Exit(0)) 204 205 ExpectRequestHeaders(session) 206 ExpectResponseHeaders(session) 207 208 contents := string(session.Out.Contents()) 209 jsonStartsAt := strings.Index(contents, "{") 210 211 actualJSON := contents[jsonStartsAt:] 212 Expect(actualJSON).To(MatchJSON(expectedJSON)) 213 }) 214 }) 215 216 When("-H is passed with a custom header", func() { 217 When("the custom header is valid", func() { 218 It("add the custom header to the request", func() { 219 session := helpers.CF("curl", "/v2/apps", "-H", "X-Foo: bar", "-v") 220 Eventually(session).Should(Exit(0)) 221 222 Expect(session).To(Say("REQUEST:")) 223 Expect(session).To(Say("X-Foo: bar")) 224 Expect(session).To(Say("RESPONSE:")) 225 }) 226 227 When("multiple headers are provided", func() { 228 It("adds all the custom headers to the request", func() { 229 session := helpers.CF("curl", "/v2/apps", "-H", "X-Bar: bar", "-H", "X-Foo: foo", "-v") 230 Eventually(session).Should(Exit(0)) 231 232 Expect(session).To(Say("REQUEST:")) 233 Expect(session).To(Say("X-Bar: bar")) 234 Expect(session).To(Say("X-Foo: foo")) 235 Expect(session).To(Say("RESPONSE:")) 236 }) 237 238 When("the same header field is passed", func() { 239 It("adds the same header field twice", func() { 240 session := helpers.CF("curl", "/v2/apps", "-H", "X-Foo: bar", "-H", "X-Foo: foo", "-v") 241 Eventually(session).Should(Exit(0)) 242 243 Expect(session).To(Say("REQUEST:")) 244 Expect(session).To(Say("X-Foo: bar")) 245 Expect(session).To(Say("X-Foo: foo")) 246 Expect(session).To(Say("RESPONSE:")) 247 }) 248 }) 249 }) 250 251 When("-H is provided with a default header", func() { 252 It("overrides the value of User-Agent header", func() { 253 session := helpers.CF("curl", "/v2/apps", "-H", "User-Agent: smith", "-v") 254 Eventually(session).Should(Exit(0)) 255 256 Expect(session).To(Say("REQUEST:")) 257 Expect(session).To(Say("User-Agent: smith")) 258 Expect(session).To(Say("RESPONSE:")) 259 }) 260 261 It("does not override the Host header", func() { 262 getHost := func() string { 263 apiSession := helpers.CF("api") 264 Eventually(apiSession).Should(Exit(0)) 265 output := string(apiSession.Out.Contents()) 266 lines := strings.Split(output, "\n") 267 Expect(len(lines)).To(BeNumerically(">=", 1)) 268 parts := strings.Split(lines[0], "//") 269 Expect(len(parts)).To(BeNumerically(">=", 2)) 270 return parts[1] 271 } 272 session := helpers.CF("curl", "/v2/apps", "-H", "Host: example.com", "-v") 273 Eventually(session).Should(Exit(0)) 274 Expect(session).To(Say("Host: " + getHost())) 275 }) 276 277 It("overrides the value of Accept header", func() { 278 session := helpers.CF("curl", "/v2/apps", "-H", "Accept: application/xml", "-v") 279 Eventually(session).Should(Exit(0)) 280 281 Expect(session).To(Say("REQUEST:")) 282 Expect(session).To(Say("Accept: application/xml")) 283 Expect(session).To(Say("RESPONSE:")) 284 }) 285 286 It("overrides the value of Content-Type header", func() { 287 session := helpers.CF("curl", "/v2/apps", "-H", "Content-Type: application/xml", "-v") 288 Eventually(session).Should(Exit(0)) 289 290 Expect(session).To(Say("REQUEST:")) 291 Expect(session).To(Say("Content-Type: application/xml")) 292 Expect(session).To(Say("RESPONSE:")) 293 }) 294 }) 295 }) 296 297 When("the custom header is not valid", func() { 298 It("tells the user that the header is not valid", func() { 299 session := helpers.CF("curl", "/v2/apps", "-H", "not-a-valid-header", "-v") 300 Eventually(session).Should(Exit(1)) 301 302 Expect(session).Should(Say("FAILED")) 303 Expect(session).Should(Say("Error creating request:")) 304 Expect(session).Should(Say(`Error parsing headers:.+not-a-valid-header`)) 305 }) 306 }) 307 }) 308 309 When("-d is passed with a request body", func() { 310 When("the request body is passed as a string", func() { 311 It("sets the method to POST and sends the body", func() { 312 orgGUID := helpers.GetOrgGUID(orgName) 313 spaceName := helpers.NewSpaceName() 314 jsonBody := fmt.Sprintf(`{"name":"%s", "organization_guid":"%s"}`, spaceName, orgGUID) 315 session := helpers.CF("curl", "/v2/spaces", "-d", jsonBody) 316 Eventually(session).Should(Exit(0)) 317 Eventually(helpers.CF("space", spaceName)).Should(Exit(0)) 318 }) 319 }) 320 321 When("the request body is passed as a file", func() { 322 var spaceName, filePath, dir string 323 324 BeforeEach(func() { 325 var err error 326 dir, err = ioutil.TempDir("", "curl-command") 327 Expect(err).ToNot(HaveOccurred()) 328 329 filePath = filepath.Join(dir, "request_body.json") 330 orgGUID := helpers.GetOrgGUID(orgName) 331 spaceName = helpers.NewSpaceName() 332 333 jsonBody := fmt.Sprintf(`{"name":"%s", "organization_guid":"%s"}`, spaceName, orgGUID) 334 err = ioutil.WriteFile(filePath, []byte(jsonBody), 0666) 335 Expect(err).ToNot(HaveOccurred()) 336 }) 337 338 AfterEach(func() { 339 os.RemoveAll(dir) 340 }) 341 342 It("sets the method to POST and sends the body", func() { 343 session := helpers.CF("curl", "/v2/spaces", "-d", "@"+filePath) 344 Eventually(session).Should(Exit(0)) 345 Eventually(helpers.CF("space", spaceName)).Should(Exit(0)) 346 }) 347 348 When("the file does not exist", func() { 349 It("fails and displays an error message", func() { 350 _, err := os.Stat("this-file-does-not-exist") 351 Expect(os.IsExist(err)).To(BeFalse()) 352 353 session := helpers.CF("curl", "/v2/spaces", "-d", "@this-file-does-not-exist") 354 Eventually(session).Should(Exit(1)) 355 Expect(session).To(Say("FAILED")) 356 }) 357 }) 358 359 When("the file is a symlink", func() { 360 It("follows the symlink", func() { 361 linkPath := filepath.Join(dir, "link-name.json") 362 Expect(os.Symlink(filePath, linkPath)).To(Succeed()) 363 session := helpers.CF("curl", "-d", "@"+linkPath, "/v2/spaces") 364 Eventually(session).Should(Exit(0)) 365 Eventually(helpers.CF("space", spaceName)).Should(Exit(0)) 366 }) 367 }) 368 }) 369 }) 370 371 When("-X is passed with the HTTP method", func() { 372 var spaceGUID, spaceName string 373 374 BeforeEach(func() { 375 spaceName = helpers.NewSpaceName() 376 helpers.CreateSpace(spaceName) 377 spaceGUID = helpers.GetSpaceGUID(spaceName) 378 }) 379 380 It("changes the HTTP method of the request", func() { 381 path := fmt.Sprintf("/v2/spaces/%s", spaceGUID) 382 session := helpers.CF("curl", path, "-X", "DELETE", "-v") 383 Eventually(session).Should(Exit(0)) 384 385 Eventually(helpers.CF("space", spaceName)).Should(Exit(1)) 386 }) 387 }) 388 389 When("--output is passed with a file name", func() { 390 It("writes the response body to the file but the other output to stdout", func() { 391 outFile, err := ioutil.TempFile("", "output*.json") 392 Expect(err).ToNot(HaveOccurred()) 393 session := helpers.CF("curl", "/v2/apps", "-i", "--output", outFile.Name()) 394 Eventually(session).Should(Exit(0)) 395 ExpectResponseHeaders(session) 396 body, err := ioutil.ReadFile(outFile.Name()) 397 Expect(err).ToNot(HaveOccurred()) 398 Expect(string(body)).To(MatchJSON(expectedJSON)) 399 }) 400 401 When("--output is passed and CF_TRACE is set to a file", func() { 402 var tempDir, traceFile, outFile string 403 BeforeEach(func() { 404 var err error 405 tempDir, err = ioutil.TempDir("", "") 406 Expect(err).ToNot(HaveOccurred()) 407 traceFile = filepath.Join(tempDir, "trace.log") 408 outFile = filepath.Join(tempDir, "output") 409 }) 410 411 AfterEach(func() { 412 Expect(os.RemoveAll(tempDir)).To(Succeed()) 413 }) 414 415 It("writes the response body to the --output file and everything to the trace file", func() { 416 session := helpers.CFWithEnv(map[string]string{"CF_TRACE": traceFile}, "curl", "/v2/apps", "--output", outFile) 417 Eventually(session).Should(Exit(0)) 418 419 outBytes, err := ioutil.ReadFile(outFile) 420 Expect(err).ToNot(HaveOccurred()) 421 Expect(string(outBytes)).To(MatchJSON(expectedJSON)) 422 423 traceBytes, err := ioutil.ReadFile(traceFile) 424 Expect(err).ToNot(HaveOccurred()) 425 Expect(traceBytes).To(ContainSubstring("REQUEST: ")) 426 Expect(traceBytes).To(ContainSubstring("HTTP/1.1 200 OK")) 427 }) 428 }) 429 }) 430 431 Describe("Flag combinations", func() { 432 When("-i and -v flags are set", func() { 433 It("prints both the request and response headers", func() { 434 session := helpers.CF("curl", "/v2/apps", "-v", "-i") 435 Eventually(session).Should(Exit(0)) 436 437 ExpectRequestHeaders(session) 438 ExpectResponseHeaders(session) 439 440 contents := string(session.Out.Contents()) 441 jsonStartsAt := strings.Index(contents, "{") 442 443 actualJSON := contents[jsonStartsAt:] 444 Expect(actualJSON).To(MatchJSON(expectedJSON)) 445 }) 446 }) 447 448 XWhen("-v and --output flags are passed", func() { 449 It("prints the headers to the terminal and the response to the file", func() { 450 // TODO This is a bug in the legacy CLI. Please write the test and fix the bug after refactor [#162432878] 451 }) 452 }) 453 454 When("-X, -H and -d flags are passed", func() { 455 var spaceName, filePath, dir, jsonBody string 456 457 BeforeEach(func() { 458 var err error 459 dir, err = ioutil.TempDir("", "curl-command") 460 Expect(err).ToNot(HaveOccurred()) 461 462 filePath = filepath.Join(dir, "request_body.json") 463 orgGUID := helpers.GetOrgGUID(orgName) 464 spaceName = helpers.NewSpaceName() 465 466 jsonBody = fmt.Sprintf(`{"name":"%s", "organization_guid":"%s"}`, spaceName, orgGUID) 467 err = ioutil.WriteFile(filePath, []byte(jsonBody), 0666) 468 Expect(err).ToNot(HaveOccurred()) 469 }) 470 471 AfterEach(func() { 472 os.RemoveAll(dir) 473 }) 474 475 It("sets the custom header and use the request body from -d", func() { 476 session := helpers.CF("curl", "/v2/spaces", "-X", "POST", "-H", "X-Foo: foo", "-H", "X-Bar: bar", "-d", "@"+filePath, "-v") 477 Eventually(session).Should(Exit(0)) 478 479 Expect(session).Should(Say("REQUEST:")) 480 Expect(session).Should(Say("POST")) 481 482 Expect(session).Should(Say("X-Bar: bar")) 483 Expect(session).Should(Say("X-Foo: foo")) 484 485 contents := string(session.Out.Contents()) 486 outputContents := contents[strings.Index(contents, "X-Foo: foo"):] 487 jsonStartsAt := strings.Index(outputContents, "{") 488 jsonEndsAt := strings.Index(outputContents[jsonStartsAt:], "}") 489 490 actualJSON := outputContents[jsonStartsAt : jsonStartsAt+jsonEndsAt+1] 491 Expect(actualJSON).To(MatchJSON(jsonBody)) 492 493 Expect(session).Should(Say("RESPONSE:")) 494 495 Eventually(helpers.CF("space", spaceName)).Should(Exit(0)) 496 }) 497 }) 498 }) 499 500 When("the auth token is invalid", func() { 501 var spaceGUID, spaceName string 502 503 BeforeEach(func() { 504 spaceName = helpers.NewSpaceName() 505 helpers.CreateSpace(spaceName) 506 spaceGUID = helpers.GetSpaceGUID(spaceName) 507 }) 508 509 It("generates a new auth token by using the refresh token", func() { 510 path := fmt.Sprintf("/v2/spaces/%s", spaceGUID) 511 authHeader := fmt.Sprintf("Authorization: %s", helpers.ExpiredAccessToken()) 512 session := helpers.CF("curl", path, "-H", authHeader, "-X", "DELETE", "-v") 513 Eventually(session).Should(Exit(0)) 514 515 Expect(session).To(Say("POST /oauth/token")) 516 517 Eventually(helpers.CF("space", spaceName)).Should(Exit(1)) 518 }) 519 }) 520 }) 521 522 When("the path is invalid", func() { 523 It("makes the request and displays the unknown request json", func() { 524 expectedJSON := `{ 525 "description": "Unknown request", 526 "error_code": "CF-NotFound", 527 "code": 10000 528 }` 529 session := helpers.CF("curl", "/some-random-path") 530 Eventually(session).Should(Exit(0)) 531 Expect(session.Out.Contents()).To(MatchJSON(expectedJSON)) 532 }) 533 }) 534 }) 535 })