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