// New makes a new Runner with the given testing T target and the // root URL. func New(t T, URL string) *Runner { r := &Runner{ t: t, rootURL: URL, vars: make(map[string]*parse.Value), DoRequest: http.DefaultTransport.RoundTrip, Log: func(s string) { fmt.Println(s) }, Verbose: func(args ...interface{}) { if !testing.Verbose() { return } fmt.Println(args...) }, ParseBody: ParseJSONBody, NewRequest: http.NewRequest, } // capture environment variables by default for _, e := range os.Environ() { pair := strings.Split(e, "=") r.vars[pair[0]] = parse.ParseValue([]byte(pair[1])) } return r }
func (r *Runner) assertDetail(key string, actual interface{}, expected *parse.Value) bool { if actual != expected.Data { actualVal := parse.ParseValue([]byte(fmt.Sprintf("%v", actual))) r.log(key, fmt.Sprintf("expected %s: %s actual %T: %s", expected.Type(), expected, actual, actualVal)) return false } return true }
func (r *Runner) assertDetail(line *parse.Line, key string, actual interface{}, expected *parse.Value) bool { if !expected.Equal(actual) { actualVal := parse.ParseValue([]byte(fmt.Sprintf("%v", actual))) actualString := actualVal.String() if v, ok := actual.(string); ok { actualString = fmt.Sprintf(`"%s"`, v) } if expected.Type() == actualVal.Type() { r.log(key, fmt.Sprintf("expected: %s actual: %s", expected, actualString)) } else { r.log(key, fmt.Sprintf("expected %s: %s actual %T: %s", expected.Type(), expected, actual, actualString)) } return false } // capture any vars (// e.g. {placeholder}) if capture := line.Capture(); len(capture) > 0 { r.capture(capture, actual) } return true }
func (r *Runner) assertData(line *parse.Line, data interface{}, errData error, key string, expected *parse.Value) bool { if errData != nil { r.log(key, fmt.Sprintf("expected %s: %s actual: failed to parse body: %s", expected.Type(), expected, errData)) return false } if data == nil { r.log(key, fmt.Sprintf("expected %s: %s actual: no data", expected.Type(), expected)) return false } actual, ok := m.GetOK(map[string]interface{}{"Data": data}, key) if !ok && expected.Data != nil { r.log(key, fmt.Sprintf("expected %s: %s actual: (missing)", expected.Type(), expected)) return false } // capture any vars (// e.g. {placeholder}) if capture := line.Capture(); len(capture) > 0 { r.capture(capture, actual) } if !ok && expected.Data == nil { return true } if !expected.Equal(actual) { actualVal := parse.ParseValue([]byte(fmt.Sprintf("%v", actual))) actualString := actualVal.String() if v, ok := actual.(string); ok { actualString = fmt.Sprintf(`"%s"`, v) } if expected.Type() == actualVal.Type() { r.log(key, fmt.Sprintf("expected: %s actual: %s", expected, actualString)) } else { r.log(key, fmt.Sprintf("expected %s: %s actual %T: %s", expected.Type(), expected, actual, actualString)) } return false } return true }
func (r *Runner) assertData(data interface{}, errData error, key string, expected *parse.Value) bool { if errData != nil { r.log(key, fmt.Sprintf("expected %s: %s actual: failed to parse body: %s", expected.Type(), expected, errData)) return false } if data == nil { r.log(key, fmt.Sprintf("expected %s: %s actual: no data", expected.Type(), expected)) return false } actual, ok := m.GetOK(map[string]interface{}{"Data": data}, key) if !ok && expected.Data != nil { r.log(key, fmt.Sprintf("expected %s: %s actual: (missing)", expected.Type(), expected)) return false } if !ok && expected.Data == nil { return true } if !expected.Equal(actual) { actualVal := parse.ParseValue([]byte(fmt.Sprintf("%v", actual))) r.log(key, fmt.Sprintf("expected %s: %s actual %T: %s", expected.Type(), expected, actual, actualVal)) return false } return true }
func (r *Runner) runRequest(group *parse.Group, req *parse.Request) { m := string(req.Method) p := string(req.Path) absPath := r.resolveVars(r.rootURL + p) m = r.resolveVars(m) r.Verbose(string(req.Method), absPath) var body io.Reader var bodyStr string if len(req.Body) > 0 { bodyStr = r.resolveVars(req.Body.String()) body = strings.NewReader(bodyStr) } // make request httpReq, err := r.NewRequest(m, absPath, body) if err != nil { r.log("invalid request: ", err) r.t.FailNow() return } // set body bodyLen := len(bodyStr) if bodyLen > 0 { httpReq.ContentLength = int64(bodyLen) } // set request headers for _, line := range req.Details { detail := line.Detail() val := fmt.Sprintf("%v", detail.Value.Data) val = r.resolveVars(val) detail.Value = parse.ParseValue([]byte(val)) r.Verbose(indent, detail.String()) httpReq.Header.Add(detail.Key, val) } // set parameters q := httpReq.URL.Query() for _, line := range req.Params { detail := line.Detail() val := fmt.Sprintf("%v", detail.Value.Data) val = r.resolveVars(val) detail.Value = parse.ParseValue([]byte(val)) r.Verbose(indent, detail.String()) q.Add(detail.Key, val) } httpReq.URL.RawQuery = q.Encode() // print request body if bodyLen > 0 { r.Verbose("```") r.Verbose(bodyStr) r.Verbose("```") } // perform request httpRes, err := r.DoRequest(httpReq) if err != nil { r.log(err) r.t.FailNow() return } // collect response details responseDetails := make(map[string]interface{}) for k, vs := range httpRes.Header { for _, v := range vs { responseDetails[k] = v } } // add cookies to repsonse details var cookieStrs []string for _, cookie := range httpRes.Cookies() { cookieStrs = append(cookieStrs, cookie.String()) } responseDetails["Set-Cookie"] = strings.Join(cookieStrs, "|") // set other details responseDetails["Status"] = float64(httpRes.StatusCode) actualBody, err := ioutil.ReadAll(httpRes.Body) if err != nil { r.log("failed to read body: ", err) r.t.FailNow() return } if len(actualBody) > 0 { r.Verbose("```") r.Verbose(string(actualBody)) r.Verbose("```") } // set the body as a field (see issue #15) responseDetails["Body"] = string(actualBody) /* Assertions --------------------------------------------------------- */ // assert the body if len(req.ExpectedBody) > 0 { // check body against expected body exp := r.resolveVars(req.ExpectedBody.String()) // depending on the expectedBodyType: // json*: check if expectedBody as JSON is a subset of the actualBody as json // json(exact): check JSON for deep equality (avoids checking diffs in white space and order) // *: check string for verbatim equality expectedTypeIsJSON := strings.HasPrefix(req.ExpectedBodyType, "json") if expectedTypeIsJSON { // decode json from string var expectedJSON interface{} var actualJSON interface{} json.Unmarshal([]byte(exp), &expectedJSON) json.Unmarshal(actualBody, &actualJSON) if !strings.Contains(req.ExpectedBodyType, "exact") { eq, err := r.assertJSONIsEqualOrSubset(expectedJSON, actualJSON) if !eq { r.fail(group, req, req.ExpectedBody.Number(), "- body doesn't match", err) return } } else if !reflect.DeepEqual(actualJSON, expectedJSON) { r.fail(group, req, req.ExpectedBody.Number(), "- body doesn't match") return } } else if !r.assertBody(actualBody, []byte(exp)) { r.fail(group, req, req.ExpectedBody.Number(), "- body doesn't match") return } } // assert the details var parseDataOnce sync.Once var data interface{} var errData error if len(req.ExpectedDetails) > 0 { for _, line := range req.ExpectedDetails { detail := line.Detail() // resolve any variables mentioned in this detail value if detail.Value.Type() == "string" { detail.Value.Data = r.resolveVars(detail.Value.Data.(string)) } if strings.HasPrefix(detail.Key, "Data") { parseDataOnce.Do(func() { data, errData = r.ParseBody(bytes.NewReader(actualBody)) }) if !r.assertData(line, data, errData, detail.Key, detail.Value) { r.fail(group, req, line.Number, "- "+detail.Key+" doesn't match") return } continue } var actual interface{} var present bool if actual, present = responseDetails[detail.Key]; !present { r.log(detail.Key, fmt.Sprintf("expected %s: %s actual %T: %s", detail.Value.Type(), detail.Value, actual, "(missing)")) r.fail(group, req, line.Number, "- "+detail.Key+" doesn't match") return } if !r.assertDetail(line, detail.Key, actual, detail.Value) { r.fail(group, req, line.Number, "- "+detail.Key+" doesn't match") return } } } }