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