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  })