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