From 8006048f5bb94dcc24272ab0ed6058179cf6cad9 Mon Sep 17 00:00:00 2001 From: Joe Bellus Date: Sun, 13 Aug 2017 01:41:03 -0400 Subject: [PATCH] Revert "Develop (#3)" This reverts commit f36d73d671fb7c69a151b095df75780e8f705d11. --- .gitignore | 1 - README.md | 44 ++-------------------- cmd/hammer.go | 77 -------------------------------------- cmd/request.go | 4 +- config/config.go | 19 +--------- endpoint/config.local.json | 7 ---- endpoint/endpoint.go | 41 ++++++++------------ endpoint/endpoint_test.go | 10 ++--- endpoint/faker.go | 52 ------------------------- explorer/explorer.go | 17 +++------ main.go | 14 +++++++ output/output.go | 5 +-- output/progress.go | 53 -------------------------- request/complete_test.go | 36 ------------------ request/engine.go | 70 +++++++++++----------------------- request/engine_test.go | 33 ++++++++-------- request/request.go | 61 +++++++++++++++++------------- request/request_test.go | 61 ------------------------------ request/response.go | 26 ------------- request/transform_test.go | 42 --------------------- 20 files changed, 120 insertions(+), 553 deletions(-) delete mode 100644 cmd/hammer.go delete mode 100755 endpoint/config.local.json delete mode 100644 endpoint/faker.go delete mode 100644 output/progress.go delete mode 100644 request/complete_test.go delete mode 100644 request/request_test.go delete mode 100644 request/response.go delete mode 100644 request/transform_test.go diff --git a/.gitignore b/.gitignore index 0e6ce41..224f6c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ vendor/ dist/ -testdata/ diff --git a/README.md b/README.md index 81900e5..3e66beb 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,9 @@ [![Build Status](https://travis-ci.org/5Sigma/spyder.svg?branch=master)](https://travis-ci.org/5Sigma/spyder) -# Spyder +# spyder API Testing and Request Framework -## Installation - -### OSX - -On OSX, spyder can be install with brew: - -``` -brew install 5sigma/tap/spyder -``` - -### Linux - -Download the linux package from for the latest release: - -https://github.com/5Sigma/spyder/releases/latest - -### Windows - -Windows binaries can be found in the release: - -https://github.com/5Sigma/spyder/releases/latest ## API Testing and Requests @@ -96,7 +75,7 @@ For POST requests the node is submitted as stringified JSON in the post body. } ``` -For more information about endpoints check out the [Endpoint Configuration Reference](https://github.com/5Sigma/spyder/wiki/Endpoint-Configuration-Reference) +## Handling dynamic data The easiest way of handling dynamic data is by using variables directly inside the configuration. There are two configuration files: @@ -196,21 +175,6 @@ This request uses a transform script located at `scripts/signRequest.js`. That could look like: ```js -signature = $hmac($variables.get('session_token_secret'), $request.body); -$request.headers.set('Authorization', $variables.get('session_token_id') + ':' + signature) +signature = $hmac($variables.get('session_token_secret'), $payload.get()); +$headers.set('Authorization', $variables.get('session_token_id') + ':' + signature) ``` - -For more information on scripting see the [Scripting Reference](https://github.com/5Sigma/spyder/wiki/Script-Reference) - - -# Stress testing - -Endpoints can be rapidly requested for stress testing using the `hammer` -command. The request will be made a number of times specified by the count -flag, or 100 times by default. - -``` -spyder hammer --count 1000 myEndpoint -``` - -For more information on scripting see the [Scripting Reference](https://github.com/5Sigma/spyder/wiki/Script-Reference) diff --git a/cmd/hammer.go b/cmd/hammer.go deleted file mode 100644 index 432de26..0000000 --- a/cmd/hammer.go +++ /dev/null @@ -1,77 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "github.com/5sigma/spyder/config" - "github.com/5sigma/spyder/endpoint" - "github.com/5sigma/spyder/output" - "github.com/5sigma/spyder/request" - "github.com/dustin/go-humanize" - "github.com/spf13/cobra" - "path" - "time" -) - -// hammerCmd represents the hammer command -var hammerCmd = &cobra.Command{ - Use: "hammer", - Short: "Makes an endpoint request a number of times rapidly.", - Long: `Make a number of request to an endpoint very rapidly and record the request timing. The hammer command expects an endpoint to be passed in the same manner as the 'request' command: - -spyder hammer --count 100 myEndpoint`, - Run: func(cmd *cobra.Command, args []string) { - var ( - count int - totalTime time.Duration - maxTime time.Duration - minTime time.Duration - totalBytes int64 - ) - - count, _ = cmd.Flags().GetInt("count") - if len(args) == 0 { - output.PrintFatal(errors.New("No endpoint specified")) - } - - configPath := path.Join(config.ProjectPath, "endpoints", args[0]+".json") - config, err := endpoint.Load(configPath) - if err != nil { - output.PrintFatal(err) - } - - bar := output.NewProgress(count) - for i := 0; i <= count; i++ { - res, err := request.Do(config) - if err != nil { - output.PrintFatal(err) - } - totalTime += res.RequestTime - bar.Inc() - if minTime == 0 { - minTime = res.RequestTime - } - if res.RequestTime > maxTime { - maxTime = res.RequestTime - } - if res.RequestTime < minTime { - minTime = res.RequestTime - } - totalBytes += res.Response.ContentLength - } - - avgTime := totalTime / time.Duration(count) - - output.PrintProperty("Number of requests", fmt.Sprintf("%d", count)) - output.PrintProperty("Average time", fmt.Sprintf("%s", avgTime)) - output.PrintProperty("Fastest", fmt.Sprintf("%s", minTime)) - output.PrintProperty("Slowest", fmt.Sprintf("%s", maxTime)) - output.PrintProperty("Total data transfer", - humanize.Bytes(uint64(totalBytes))) - }, -} - -func init() { - RootCmd.AddCommand(hammerCmd) - hammerCmd.PersistentFlags().Int("count", 100, "Request count") -} diff --git a/cmd/request.go b/cmd/request.go index 46ab516..ace511e 100644 --- a/cmd/request.go +++ b/cmd/request.go @@ -1,7 +1,6 @@ package cmd import ( - "github.com/5sigma/spyder/config" "github.com/5sigma/spyder/endpoint" "github.com/5sigma/spyder/explorer" "github.com/5sigma/spyder/output" @@ -25,7 +24,7 @@ requested using: $ spyder request sessions/auth `, Run: func(cmd *cobra.Command, args []string) { - config, err := endpoint.Load(path.Join(config.ProjectPath, "endpoints", args[0]+".json")) + config, err := endpoint.Load(path.Join("endpoints", args[0]+".json")) if err != nil { output.PrintFatal(err) } @@ -34,6 +33,7 @@ $ spyder request sessions/auth if err != nil { output.PrintFatal(err) } + explorer.Start(args[0], config, res) }, diff --git a/config/config.go b/config/config.go index 1a1e13a..ba8fa39 100644 --- a/config/config.go +++ b/config/config.go @@ -23,9 +23,6 @@ var LocalConfig = loadConfigFile("config.local.json") // GlobalConfig - The global configuration read from config.json var GlobalConfig = loadConfigFile("config.json") -// The path to the project root -var ProjectPath = "." - // InMemory - When true the config will not write to the disk. This is used for // testing. var InMemory = false @@ -64,27 +61,15 @@ func ExpandString(str string) string { // LoadConfigFile - Loads a config from a file on the disk. func loadConfigFile(filename string) *Config { - var ( - c *Config - ) if InMemory { return loadDefaultConfig() } if _, err := os.Stat(filename); !os.IsNotExist(err) { bytes, _ := ioutil.ReadFile(filename) - if strings.TrimSpace(string(bytes)) == "" { - c = loadDefaultConfig() - c.Filename = filename - return c - } - c = LoadConfig(bytes) - c.Filename = filename - return c + return LoadConfig(bytes) } - c = loadDefaultConfig() - c.Filename = filename - return c + return loadDefaultConfig() } // Loads a config from a byte array. diff --git a/endpoint/config.local.json b/endpoint/config.local.json deleted file mode 100755 index 82d23ba..0000000 --- a/endpoint/config.local.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "variables": { - "host": "127.0.0.1", - "method": "POST", - "var": "123" - } -} \ No newline at end of file diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index d6c2343..e29e2d8 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -17,18 +17,9 @@ type ( Url string OnComplete []string Transform []string - Headers map[string][]string } ) -func New() *EndpointConfig { - return &EndpointConfig{ - json: &gabs.Container{}, - OnComplete: []string{}, - Transform: []string{}, - } -} - // Load - Loads a confugruation from a file on the disk. func Load(filename string) (*EndpointConfig, error) { var ( @@ -60,19 +51,12 @@ func LoadBytes(fileBytes []byte) (*EndpointConfig, error) { method, _ := jsonObject.Path("method").Data().(string) url, _ := jsonObject.Path("url").Data().(string) - headerMap := make(map[string][]string) - children, _ := jsonObject.S("headers").ChildrenMap() - for key, child := range children { - headerMap[key] = []string{config.ExpandString(child.Data().(string))} - } - epConfig = &EndpointConfig{ json: jsonObject, Method: method, Url: url, OnComplete: []string{}, Transform: []string{}, - Headers: headerMap, } transformNodes, _ := jsonObject.S("transform").Children() @@ -96,10 +80,7 @@ func (ep *EndpointConfig) GetString(path string) string { // GetJSONString - returns the inner JSON at the path as a string. func (ep *EndpointConfig) GetJSONString(path string) string { - if ep.json.Exists("data") { - return ep.json.Path("data").String() - } - return "" + return ep.json.Path("data").String() } // GetJSONBytes - returns the inner JSON at the path as a byte array. @@ -107,6 +88,16 @@ func (ep *EndpointConfig) GetJSONBytes(path string) []byte { return ep.json.Path("data").Bytes() } +// Headers - returns the configured request headers as a string map +func (ep *EndpointConfig) Headers() map[string][]string { + headerMap := make(map[string][]string) + children, _ := ep.json.S("headers").ChildrenMap() + for key, child := range children { + headerMap[key] = []string{config.ExpandString(child.Data().(string))} + } + return headerMap +} + // RequestMethod - returns the request method. func (ep *EndpointConfig) RequestMethod() string { method := strings.ToUpper(ep.GetString("method")) @@ -116,8 +107,8 @@ func (ep *EndpointConfig) RequestMethod() string { // RequestURL - returns the full url for the request. If this is a GET request // and has request parameters they are included in the URL. func (ep *EndpointConfig) RequestURL() string { - if ep.RequestMethod() == "GET" { - baseURL, _ := url.Parse(expandFakes(config.ExpandString(ep.Url))) + if ep.Method == "GET" { + baseURL, _ := url.Parse(config.ExpandString(ep.Url)) params := url.Values{} for k, v := range ep.GetRequestParams() { params.Add(k, v) @@ -138,7 +129,7 @@ func (ep *EndpointConfig) GetRequestParams() map[string]string { paramsMap := make(map[string]string) children, _ := ep.json.S("data").ChildrenMap() for key, child := range children { - paramsMap[key] = expandFakes(config.ExpandString(child.Data().(string))) + paramsMap[key] = config.ExpandString(child.Data().(string)) } return paramsMap } @@ -146,9 +137,7 @@ func (ep *EndpointConfig) GetRequestParams() map[string]string { // RequestData - returns the data attribute from the config. This contains the // payload, for a POST request, that will be sent to the server. func (ep *EndpointConfig) RequestData() []byte { - dataJSON := ep.GetJSONString("data") - dataJSON = config.ExpandString(dataJSON) - dataJSON = expandFakes(dataJSON) + dataJSON := config.ExpandString(ep.GetJSONString("data")) return []byte(dataJSON) } diff --git a/endpoint/endpoint_test.go b/endpoint/endpoint_test.go index 9e4775c..98e748a 100644 --- a/endpoint/endpoint_test.go +++ b/endpoint/endpoint_test.go @@ -41,8 +41,7 @@ func TestRequestUrl(t *testing.T) { } // POST request - json.Set("post", "method") - ep, _ = LoadBytes(json.Bytes()) + ep.Method = "POST" if ep.RequestURL() != ep.Url { t.Errorf("Request URL missmatch:\nExpecting: %s\nReceived: %s", ep.Url, ep.RequestURL()) @@ -51,11 +50,8 @@ func TestRequestUrl(t *testing.T) { // GET request with variable expansion config.LocalConfig.SetVariable("var", "value1") config.LocalConfig.SetVariable("host", "127.0.0.1") - json = buildConfig() - params, _ = json.Object("data") - params.Set("$var", "option2") - params.Set("3", "option1") json.Set("http://$host/api/endpoint", "url") + params.Set("$var", "option2") ep, _ = LoadBytes(json.Bytes()) expectedUrl = "http://127.0.0.1/api/endpoint?option1=3&option2=value1" if ep.RequestURL() != expectedUrl { @@ -76,7 +72,7 @@ func TestHeaders(t *testing.T) { t.Fatalf("Error reading config: %s", err.Error()) } - headerMap := ep.Headers + headerMap := ep.Headers() contentTypeValues := headerMap["Content-Type"] if contentTypeValues[0] != "application/json" { t.Errorf("Header not stored or retrieved correctly") diff --git a/endpoint/faker.go b/endpoint/faker.go deleted file mode 100644 index ad8cbc1..0000000 --- a/endpoint/faker.go +++ /dev/null @@ -1,52 +0,0 @@ -package endpoint - -import ( - "fmt" - "github.com/icrowley/fake" - "regexp" - "strings" -) - -// fakeFuncs - A funtion map for faker keys and generators. -var fakeFuncs = map[string]func() string{ - "city": fake.City, - "color": fake.Color, - "country": fake.Country, - "email": fake.EmailAddress, - "femaleFirstName": fake.FemaleFirstName, - "femaleFullName": fake.FemaleFullName, - "maleFirstName": fake.MaleFirstName, - "maleFullName": fake.MaleFullName, - "firstName": fake.FirstName, - "lastName": fake.LastName, - "fullName": fake.FullName, - "gender": fake.Gender, - "hexColor": fake.HexColor, - "ip": fake.IPv4, - "industry": fake.Industry, - "jobtitle": fake.JobTitle, - "month": fake.Month, - "monthNum": func() string { return string(fake.MonthNum()) }, - "monthShort": fake.MonthShort, - "phone": fake.Phone, - "product": fake.Product, - "productName": fake.ProductName, - "street": fake.Street, - "streetAddress": fake.StreetAddress, - "weekDay": fake.WeekDay, - "zip": fake.Zip, -} - -// expandFakes - Expands faker templates into values and returns the expanded -// string -func expandFakes(str string) string { - re := regexp.MustCompile(`\#\{([A-Za-z0-9]+)\}`) - matches := re.FindAllStringSubmatch(str, -1) - for _, match := range matches { - if f, ok := fakeFuncs[match[1]]; ok { - v := f() - str = strings.Replace(str, fmt.Sprintf("#{%s}", match[1]), v, 1) - } - } - return str -} diff --git a/explorer/explorer.go b/explorer/explorer.go index 0995ddf..e6605d8 100644 --- a/explorer/explorer.go +++ b/explorer/explorer.go @@ -18,7 +18,7 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re shell.Prompt = prompt shell.AddCommand(&gshell.Command{ - Name: "response.body", + Name: "body", Description: "Displays the content received from the server.", Call: func(sh *gshell.Shell, args []string) { if res.IsResponseJSON() { @@ -51,7 +51,7 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re }) shell.AddCommand(&gshell.Command{ - Name: "request.body", + Name: "payload", Description: "Displays the post data sent in the request", Call: func(sh *gshell.Shell, args []string) { switch res.Request.Header.Get("Content-Type") { @@ -86,8 +86,7 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re }) shell.AddCommand(&gshell.Command{ - Name: "refresh", - Description: "Makes the request again and reloads all data.", + Name: "refresh", Call: func(sh *gshell.Shell, args []string) { newRes, err := request.Do(config) if err != nil { @@ -99,8 +98,7 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re }) shell.AddCommand(&gshell.Command{ - Name: "response.headers", - Description: "Displays the headers recieved from the server", + Name: "headers", Call: func(sh *gshell.Shell, args []string) { for key, value := range res.Response.Header { output.PrintProperty(key, value[0]) @@ -114,8 +112,7 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re }) shell.AddCommand(&gshell.Command{ - Name: "request.headers", - Description: "Displays the headers sent to the server", + Name: "headers:sent", Call: func(sh *gshell.Shell, args []string) { for key, value := range res.Request.Header { if len(value) == 0 { @@ -133,9 +130,5 @@ func Start(endpointPath string, config *endpoint.EndpointConfig, res *request.Re }, }) - fmt.Println("") - shell.ProcessLine("response") - fmt.Println("") - shell.ProcessLine("response.body") shell.Start() } diff --git a/main.go b/main.go index 6349855..790d783 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,17 @@ +// Copyright © 2017 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import "github.com/5sigma/spyder/cmd" diff --git a/output/output.go b/output/output.go index fb8e2cb..b11941d 100644 --- a/output/output.go +++ b/output/output.go @@ -57,9 +57,8 @@ func PrintJson(contentBytes []byte) { re := regexp.MustCompile(`([\[\]\{\}]{1})`) content = re.ReplaceAllString(content, fmt.Sprintf("%s$1%s", chalk.Green, chalk.Reset)) - // String values - re = regexp.MustCompile(`(\s*?\")([^:]*?)(\"\s*?,?\n)`) - content = re.ReplaceAllString(content, fmt.Sprintf("$1%s$2%s$3", chalk.Blue, chalk.Reset)) + re = regexp.MustCompile(`:\s*\"(.*)\"`) + content = re.ReplaceAllString(content, fmt.Sprintf(": \"%s$1%s\"", chalk.Blue, chalk.Reset)) re = regexp.MustCompile(`(\:\s*[true|false]+\s*[,\n])`) content = re.ReplaceAllString(content, fmt.Sprintf("%s$1%s", chalk.Magenta, chalk.Reset)) diff --git a/output/progress.go b/output/progress.go deleted file mode 100644 index 59bfc82..0000000 --- a/output/progress.go +++ /dev/null @@ -1,53 +0,0 @@ -package output - -import ( - "fmt" - "github.com/gosuri/uilive" - "strings" - "time" -) - -// ProgressBar - A structure that controls displaying a progress bar in the -// console. -type ProgressBar struct { - writer *uilive.Writer - Max int - Current int - startTime time.Time -} - -// NewProgress - Creates a new progress bar with a given maximum value. -func NewProgress(max int) *ProgressBar { - writer := uilive.New() - writer.Start() - return &ProgressBar{ - writer: writer, - Max: max, - startTime: time.Now(), - } -} - -// Inc - Increments the current value by one. -func (bar *ProgressBar) Inc() { - bar.Current++ - bar.write() - if bar.Current == bar.Max { - bar.Stop() - } -} - -// Stop - Stops the progress bar rendering. This is automatically called if the -// current value reaches the maximum. -func (bar *ProgressBar) Stop() { - bar.writer.Stop() -} - -// write - Writes the progress bar line to the console. -func (bar *ProgressBar) write() { - elapsed := time.Since(bar.startTime) - perc := (float64(bar.Current) / float64(bar.Max)) * float64(10) - barStr := strings.Repeat("⚪", 10) - barStr = strings.Replace(barStr, "⚪", "⚫", int(perc)) - line := fmt.Sprintf("[%d/%d] %s %s", bar.Current, bar.Max, barStr, elapsed) - fmt.Fprintln(bar.writer, line) -} diff --git a/request/complete_test.go b/request/complete_test.go deleted file mode 100644 index c536506..0000000 --- a/request/complete_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package request - -import ( - "github.com/5sigma/spyder/config" - "testing" -) - -func TestOnComplete(t *testing.T) { - ts := testServer(` - { - "inner": { - "value": "1234567890" - } - } - `) - defer ts.Close() - epConfig := endpointConfig(` - { - "url": "%s", - "method": "post", - "onComplete": ["variableSave"] - } - `, ts.URL) - var variableSave = ` - $variables.set("var", JSON.parse($response.body).inner.value) - ` - createFile("testdata/scripts/variableSave.js", variableSave) - _, err := Do(epConfig) - if err != nil { - t.Fatalf("Error making request: %s", err) - } - val := config.GetVariable("var") - if val != "1234567890" { - t.Errorf("Config variable did not get set correctly in onComplete:\n%s", val) - } -} diff --git a/request/engine.go b/request/engine.go index 36093c0..89a6ef3 100644 --- a/request/engine.go +++ b/request/engine.go @@ -39,18 +39,17 @@ func NewScriptEngine(endpointConfig *endpoint.EndpointConfig) *ScriptEngine { varObj.Set("set", eng.setLocalVar) varObj.Set("get", eng.getVar) + payloadObj, _ := vm.Object("$payload = {}") + payloadObj.Set("get", eng.getPayload) + payloadObj.Set("set", eng.setPayload) + + headerObj, _ := vm.Object("$headers = {}") + headerObj.Set("get", eng.getHeader) + headerObj.Set("set", eng.setHeader) + vm.Set("$debug", eng.setDebug) vm.Set("$hmac", eng.hmac) - reqObj, _ := eng.VM.Object("$request = {}") - requestBytes := endpointConfig.RequestData() - reqObj.Set("body", string(requestBytes)) - reqObj.Set("contentLength", len(requestBytes)) - headersObj, _ := eng.VM.Object(`$request.headers = {}`) - headersObj.Set("get", eng.getReqHeader) - headersObj.Set("set", eng.setReqHeader) - reqObj.Set("setBody", eng.setPayload) - return eng } @@ -78,30 +77,10 @@ func (eng *ScriptEngine) SetResponse(res *Response) { responseObj, _ := eng.VM.Object(`$response = {}`) responseObj.Set("contentLength", res.Response.ContentLength) responseObj.Set("body", string(res.Content)) - eng.VM.Object(`$response.headers = {}`) - responseObj.Set("get", eng.getResHeader) -} - -// SetRequest - Sets the request on the engine. This also builds the functions -// to expose the request to scripts. -func (engine *ScriptEngine) SetRequest(request *http.Request) { - eng.Request = request - reqVal, _ := eng.VM.Get("$request") - reqObj := reqVal.Object() - reqObj.Set("contentLength", request.ContentLength) -} - -// SetPayload - Sets the request payload on the engine. Also exposes it to -// scripts within the request object. -func (engine *ScriptEngine) SetPayload(payload []byte) { - engine.Payload = payload - reqVal, _ := engine.VM.Get("$request") - reqObj := reqVal.Object() - reqObj.Set("body", string(payload)) } //Execute - Executes a Javascript. -func (engine *ScriptEngine) Execute(script string) error { +func (eng *ScriptEngine) Execute(script string) error { _, err := eng.VM.Run(script) if err != nil { println(err.Error()) @@ -109,6 +88,14 @@ func (engine *ScriptEngine) Execute(script string) error { return err } +// ExecuteTransform - Executes the script and with a payload value set and +// returns the newly set payload. Used for performing request transformations. +func (eng *ScriptEngine) ExecuteTransform(script string, payload []byte) []byte { + eng.Payload = payload + eng.ExecuteFile(script) + return eng.Payload +} + // Validate - Validates that the Javascript is valid. func (eng *ScriptEngine) Validate(script string) error { _, err := parser.ParseFile(nil, "", script, 0) @@ -144,7 +131,6 @@ func (engine *ScriptEngine) getVar(call otto.FunctionCall) otto.Value { return otto.Value{} } -// getPayload - Returns the request payload. func (engine *ScriptEngine) getPayload(call otto.FunctionCall) otto.Value { ov, _ := otto.ToValue(string(engine.Payload)) return ov @@ -169,34 +155,22 @@ func (engine *ScriptEngine) hmac(call otto.FunctionCall) otto.Value { return v } -// getReqHeader - Returns a header from the request. -func (engine *ScriptEngine) getReqHeader(call otto.FunctionCall) otto.Value { +// getHeader - Returns a header from the request. +func (engine *ScriptEngine) getHeader(call otto.FunctionCall) otto.Value { headerName, _ := call.Argument(0).ToString() val := engine.Request.Header.Get(headerName) v, _ := otto.ToValue(val) return v } -// setReqHeader - sets a header on the request. -func (engine *ScriptEngine) setReqHeader(call otto.FunctionCall) otto.Value { +// setHeader - sets a header on the request. +func (engine *ScriptEngine) setHeader(call otto.FunctionCall) otto.Value { headerName, _ := call.Argument(0).ToString() headerValue, _ := call.Argument(1).ToString() - if engine.Request != nil { - engine.Request.Header.Set(headerName, headerValue) - } else { - engine.EndpointConfig.Headers[headerName] = []string{headerValue} - } + engine.Request.Header.Set(headerName, headerValue) return otto.Value{} } -// getResHeader - Returns a header from the response. -func (engine *ScriptEngine) getResHeader(call otto.FunctionCall) otto.Value { - headerName, _ := call.Argument(0).ToString() - val := engine.Response.Response.Header.Get(headerName) - v, _ := otto.ToValue(val) - return v -} - // setDebug - Sets the debug value on the engine. Used for testing. func (engine *ScriptEngine) setDebug(call otto.FunctionCall) otto.Value { val, _ := call.Argument(0).ToString() diff --git a/request/engine_test.go b/request/engine_test.go index bafbd2e..b6b584d 100644 --- a/request/engine_test.go +++ b/request/engine_test.go @@ -4,12 +4,19 @@ import ( "github.com/5sigma/spyder/config" "github.com/5sigma/spyder/endpoint" "net/http" + "os" "testing" ) +func TestMain(m *testing.M) { + config.InMemory = true + retCode := m.Run() + os.Exit(retCode) +} + func TestSetVariable(t *testing.T) { script := ` $variables.set('key', 'value'); ` - engine := NewScriptEngine(endpoint.New()) + engine := NewScriptEngine(&endpoint.EndpointConfig{}) engine.Execute(script) if config.LocalConfig.GetVariable("key") != "value" { t.Errorf("Config value not set: %s", config.LocalConfig.GetVariable("key")) @@ -18,7 +25,7 @@ func TestSetVariable(t *testing.T) { func TestDebug(t *testing.T) { script := `$debug('debug'); ` - engine := NewScriptEngine(endpoint.New()) + engine := NewScriptEngine(&endpoint.EndpointConfig{}) engine.Execute(script) if engine.Debug != "debug" { t.Errorf("Debug not set: %s", engine.Debug) @@ -28,7 +35,7 @@ func TestDebug(t *testing.T) { func TestGetVariable(t *testing.T) { config.LocalConfig.SetVariable("key", "test1") script := `$debug($variables.get('key')); ` - engine := NewScriptEngine(endpoint.New()) + engine := NewScriptEngine(&endpoint.EndpointConfig{}) engine.Execute(script) if engine.Debug != "test1" { t.Errorf("Config value not set: %s", engine.Debug) @@ -36,11 +43,9 @@ func TestGetVariable(t *testing.T) { } func TestPaylaod(t *testing.T) { - script := ` - $request.setBody($request.body + ' world'); - ` - engine := NewScriptEngine(endpoint.New()) - engine.SetPayload([]byte(`hello`)) + script := `$payload.set($payload.get() + ' world')` + engine := NewScriptEngine(&endpoint.EndpointConfig{}) + engine.Payload = []byte(`hello`) engine.Execute(script) if string(engine.Payload) != "hello world" { t.Errorf("Payload not set: %s", engine.Payload) @@ -49,7 +54,7 @@ func TestPaylaod(t *testing.T) { func TestHMAC(t *testing.T) { script := `$debug($hmac('secret', 'hello'))` - engine := NewScriptEngine(endpoint.New()) + engine := NewScriptEngine(&endpoint.EndpointConfig{}) engine.Execute(script) expected := "88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b" if engine.Debug != expected { @@ -60,19 +65,15 @@ func TestHMAC(t *testing.T) { func TestHeaders(t *testing.T) { script := ` - header1 = $request.headers.get('test-header1'); - $request.headers.set('test-header2', header1); + header1 = $headers.get('test-header1'); + $headers.set('test-header2', header1); ` - engine := NewScriptEngine(endpoint.New()) + engine := NewScriptEngine(&endpoint.EndpointConfig{}) req, _ := http.NewRequest("GET", "http://localhost", nil) req.Header.Set("test-header1", "myval") engine.Request = req engine.Execute(script) if req.Header.Get("test-header2") != "myval" { - headerArr := req.Header.Get("test-header2") - if len(headerArr) == 0 { - t.Fatal("Header not present") - } t.Errorf("Header not set: %s", req.Header.Get("test-header2")[0]) } } diff --git a/request/request.go b/request/request.go index 475da26..7dfa279 100644 --- a/request/request.go +++ b/request/request.go @@ -2,54 +2,57 @@ package request import ( "bytes" - "fmt" - "github.com/5sigma/spyder/config" "github.com/5sigma/spyder/endpoint" "io/ioutil" "net/http" "path" + "strings" "time" ) -// Do - Performs the request for a given endpoint configuration. This will -// expand all variables, fake data, and execute any transforms and onComplete -// scripts specified. -func Do(epConfig *endpoint.EndpointConfig) (*Response, error) { +type ( + Response struct { + Response *http.Response + RequestTime time.Duration + Content []byte + Request *http.Request + Payload []byte + } +) + +func Do(config *endpoint.EndpointConfig) (*Response, error) { var ( client = &http.Client{} err error req *http.Request requestData = []byte{} - scriptEngine = NewScriptEngine(epConfig) + scriptEngine = NewScriptEngine(config) ) - if epConfig.RequestMethod() == "POST" || epConfig.RequestMethod() == "HEAD" { - requestData = epConfig.RequestData() + if config.Method == "POST" || config.Method == "HEAD" { + requestData = config.RequestData() if err != nil { return nil, err } } - scriptEngine.SetPayload(requestData) + req, err = http.NewRequest(config.Method, + config.RequestURL(), bytes.NewReader(requestData)) + + req.Header = config.Headers() + + scriptEngine.Request = req // Perform transformation // executes scripts with the payload set so that they are capable of modifying // it before the request is made. - if len(epConfig.Transform) > 0 { - for _, transform := range epConfig.Transform { - scriptPath := path.Join(config.ProjectPath, "scripts", transform) + ".js" - scriptEngine.ExecuteFile(scriptPath) - requestData = scriptEngine.Payload + if len(config.Transform) > 0 { + for _, transform := range config.Transform { + scriptPath := path.Join("scripts", transform) + ".js" + requestData = scriptEngine.ExecuteTransform(scriptPath, requestData) } } - req, err = http.NewRequest(epConfig.RequestMethod(), - epConfig.RequestURL(), bytes.NewReader(requestData)) - - req.Header = epConfig.Headers - - scriptEngine.Request = req - // Make the request and calculate its flight time. start := time.Now() rawResponse, err := client.Do(req) @@ -74,17 +77,21 @@ func Do(epConfig *endpoint.EndpointConfig) (*Response, error) { } // Execute any post request scripts that are listed in the config - if len(epConfig.OnComplete) > 0 { + if len(config.OnComplete) > 0 { scriptEngine.SetResponse(res) - for _, onComplete := range epConfig.OnComplete { - scriptPath := path.Join(config.ProjectPath, "scripts", onComplete) + ".js" + for _, onComplete := range config.OnComplete { + scriptPath := path.Join("scripts", onComplete) + ".js" err := scriptEngine.ExecuteFile(scriptPath) if err != nil { - return res, fmt.Errorf("Error parsing script (%s): %s", - scriptPath, err.Error()) + return res, err } } } return res, nil } + +func (res *Response) IsResponseJSON() bool { + contentType := strings.ToLower(res.Response.Header.Get("Content-Type")) + return strings.Contains(contentType, "json") +} diff --git a/request/request_test.go b/request/request_test.go deleted file mode 100644 index 5f3d67e..0000000 --- a/request/request_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package request - -import ( - "fmt" - "github.com/5sigma/spyder/config" - "github.com/5sigma/spyder/endpoint" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path" - "testing" -) - -var transform = ` - var body = JSON.parse($payload.get()); - body.inner.secondVar = 'two'; - $payload.set(JSON.stringify(body)): -` - -func endpointConfig(str, url string) *endpoint.EndpointConfig { - createFile("testdata/endpoints/request.json", - fmt.Sprintf(str, url)) - epConfig, _ := endpoint.Load(path.Join(config.ProjectPath, - "endpoints", "request.json")) - return epConfig -} - -func TestMain(m *testing.M) { - config.InMemory = true - createFile("testdata/config.json", "") - createFile("testdata/config.local.json", "") - createFile("testdata/scripts/tansform.js", transform) - config.ProjectPath = "testdata" - - retCode := m.Run() - os.RemoveAll("testdata") - os.Exit(retCode) -} - -func folder(projectPath string, folder string) error { - return os.MkdirAll(path.Join(projectPath, folder), os.ModePerm) -} - -func createFile(fpath string, content string) error { - os.MkdirAll(path.Dir(fpath), os.ModePerm) - return ioutil.WriteFile(fpath, []byte(content), 0644) -} - -func testServer(response string) *httptest.Server { - return httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, ` - { - "inner": { - "value": "1234567890" - } - } - `) - })) -} diff --git a/request/response.go b/request/response.go deleted file mode 100644 index b8141cb..0000000 --- a/request/response.go +++ /dev/null @@ -1,26 +0,0 @@ -package request - -import ( - "net/http" - "strings" - "time" -) - -// Response - Helper object returned from making a request. It holds all the -// relevant request/response data. -type ( - Response struct { - Response *http.Response - RequestTime time.Duration - Content []byte - Request *http.Request - Payload []byte - } -) - -// IsResponseJSON - Returns true if the response is JSON. This is done by -// testing the Content-Type header. -func (res *Response) IsResponseJSON() bool { - contentType := strings.ToLower(res.Response.Header.Get("Content-Type")) - return strings.Contains(contentType, "json") -} diff --git a/request/transform_test.go b/request/transform_test.go deleted file mode 100644 index 5516b55..0000000 --- a/request/transform_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package request - -import ( - "github.com/Jeffail/gabs" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" -) - -func TestTransform(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - bytes, _ := ioutil.ReadAll(r.Body) - json, _ := gabs.ParseJSON(bytes) - val, _ := json.Path("var").Data().(string) - if val != "hello" { - t.Errorf("Request was not transformed: %s", val) - } - })) - defer ts.Close() - epConfig := endpointConfig(` - { - "url": "%s", - "method": "post", - "transform": ["transform"], - "data": { - "var": "123" - } - } - `, ts.URL) - transform := ` - var payload = JSON.parse($request.body); - payload.var = "hello"; - $request.setBody(JSON.stringify(payload)); - ` - createFile("testdata/scripts/transform.js", transform) - _, err := Do(epConfig) - if err != nil { - t.Errorf("Request error: %s", err.Error()) - } -}