github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/test/http.go (about) 1 package test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "strings" 13 "testing" 14 "time" 15 ) 16 17 type TestCase struct { 18 Method string `json:",omitempty"` 19 Path string `json:",omitempty"` 20 BaseURL string `json:",omitempty"` 21 Domain string `json:",omitempty"` 22 Proto string `json:",omitempty"` 23 Code int `json:",omitempty"` 24 Data interface{} `json:",omitempty"` 25 Headers map[string]string `json:",omitempty"` 26 PathParams map[string]string `json:",omitempty"` 27 FormParams map[string]string `json:",omitempty"` 28 Cookies []*http.Cookie `json:",omitempty"` 29 Delay time.Duration `json:",omitempty"` 30 BodyMatch string `json:",omitempty"` 31 BodyMatchFunc func([]byte) bool `json:",omitempty"` 32 BodyNotMatch string `json:",omitempty"` 33 HeadersMatch map[string]string `json:",omitempty"` 34 HeadersNotMatch map[string]string `json:",omitempty"` 35 JSONMatch map[string]string `json:",omitempty"` 36 ErrorMatch string `json:",omitempty"` 37 BeforeFn func() `json:"-"` 38 Client *http.Client `json:"-"` 39 40 AdminAuth bool `json:",omitempty"` 41 ControlRequest bool `json:",omitempty"` 42 } 43 44 func AssertResponse(resp *http.Response, tc *TestCase) error { 45 body, _ := ioutil.ReadAll(resp.Body) 46 resp.Body = ioutil.NopCloser(bytes.NewBuffer(body)) 47 defer resp.Body.Close() 48 49 if tc.Code != 0 && resp.StatusCode != tc.Code { 50 return fmt.Errorf("Expected status code `%d` got `%d. %s`", tc.Code, resp.StatusCode, string(body)) 51 } 52 53 if tc.BodyMatch != "" && !bytes.Contains(body, []byte(tc.BodyMatch)) { 54 return fmt.Errorf("Response body does not contain `%s`. %s", tc.BodyMatch, string(body)) 55 } 56 57 if tc.BodyNotMatch != "" && bytes.Contains(body, []byte(tc.BodyNotMatch)) { 58 return fmt.Errorf("Response body should not contain `%s`. %s", tc.BodyNotMatch, string(body)) 59 } 60 61 if tc.BodyMatchFunc != nil && !tc.BodyMatchFunc(body) { 62 return fmt.Errorf("Response body did not pass BodyMatchFunc: %s", string(body)) 63 } 64 65 if tc.Proto != "" && tc.Proto != resp.Proto { 66 return fmt.Errorf("Expected protocol `%s` got `%s`.", tc.Proto, resp.Proto) 67 } 68 69 for k, v := range tc.HeadersMatch { 70 if resp.Header.Get(k) != v { 71 return fmt.Errorf("Response header `%s` expected `%s` instead `%s`. %v", k, v, resp.Header.Get(k), resp.Header) 72 } 73 } 74 75 for k, v := range tc.HeadersNotMatch { 76 if resp.Header.Get(k) == v { 77 return fmt.Errorf("Response header `%s` should not be %s`", k, v) 78 } 79 } 80 81 if len(tc.JSONMatch) == 0 { 82 return nil 83 } 84 85 var jsonBody map[string]json.RawMessage 86 if err := json.Unmarshal(body, &jsonBody); err != nil { 87 return fmt.Errorf("Should return JSON body: %s. %d", string(body), resp.StatusCode) 88 } 89 90 for k, expect := range tc.JSONMatch { 91 if got, ok := jsonBody[k]; !ok { 92 return fmt.Errorf("`%s` JSON field not found: %s", k, string(body)) 93 } else if string(got) != expect { 94 return fmt.Errorf("`%s` not match: `%s` != `%s`", k, got, expect) 95 } 96 } 97 98 return nil 99 } 100 101 func ReqBodyReader(body interface{}) io.Reader { 102 switch x := body.(type) { 103 case []byte: 104 return bytes.NewReader(x) 105 case string: 106 return strings.NewReader(x) 107 case io.Reader: 108 return x 109 case nil: 110 return nil 111 default: // JSON objects (structs) 112 bs, err := json.Marshal(x) 113 if err != nil { 114 panic(err) 115 } 116 return bytes.NewReader(bs) 117 } 118 } 119 120 func NewRequest(tc *TestCase) (req *http.Request, err error) { 121 if tc.Method == "" { 122 tc.Method = "GET" 123 } 124 125 if tc.Path == "" { 126 tc.Path = "/" 127 } 128 129 if tc.Domain == "" { 130 tc.Domain = "127.0.0.1" 131 } 132 133 if tc.Client == nil { 134 tc.Client = &http.Client{} 135 } 136 137 uri := tc.Path 138 if tc.BaseURL != "" { 139 uri = tc.BaseURL + tc.Path 140 } 141 142 if strings.HasPrefix(uri, "http") { 143 uri = strings.Replace(uri, "[::]", tc.Domain, 1) 144 uri = strings.Replace(uri, "127.0.0.1", tc.Domain, 1) 145 146 req, err = http.NewRequest(tc.Method, uri, ReqBodyReader(tc.Data)) 147 if err != nil { 148 return 149 } 150 } else { 151 req = httptest.NewRequest(tc.Method, uri, ReqBodyReader(tc.Data)) 152 } 153 154 for k, v := range tc.Headers { 155 req.Header.Add(k, v) 156 } 157 158 for _, c := range tc.Cookies { 159 req.AddCookie(c) 160 } 161 162 formParams := url.Values{} 163 for k, v := range tc.FormParams { 164 formParams.Add(k, v) 165 } 166 req.PostForm = formParams 167 req.Form = formParams 168 169 return req, nil 170 } 171 172 // nopCloser is just like ioutil's, but here to let us re-read the same 173 // buffer inside by moving position to the start every time we done with reading 174 type nopCloser struct { 175 io.ReadSeeker 176 } 177 178 // Read just a wrapper around real Read which also moves position to the start if we get EOF 179 // to have it ready for next read-cycle 180 func (n nopCloser) Read(p []byte) (int, error) { 181 num, err := n.ReadSeeker.Read(p) 182 if err == io.EOF { // move to start to have it ready for next read cycle 183 n.Seek(0, io.SeekStart) 184 } 185 return num, err 186 } 187 188 // Close is a no-op Close 189 func (n nopCloser) Close() error { 190 return nil 191 } 192 193 func copyBody(body io.ReadCloser) io.ReadCloser { 194 // check if body was already read and converted into our nopCloser 195 if nc, ok := body.(nopCloser); ok { 196 // seek to the beginning to have it ready for next read 197 nc.Seek(0, io.SeekStart) 198 return body 199 } 200 201 // body is http's io.ReadCloser - let's close it after we read data 202 defer body.Close() 203 204 // body is http's io.ReadCloser - read it up until EOF 205 var bodyRead bytes.Buffer 206 io.Copy(&bodyRead, body) 207 208 // use seek-able reader for further body usage 209 reusableBody := bytes.NewReader(bodyRead.Bytes()) 210 211 return nopCloser{reusableBody} 212 } 213 214 func copyResponse(r *http.Response) *http.Response { 215 if r.Body != nil { 216 r.Body = copyBody(r.Body) 217 } 218 return r 219 } 220 221 type HTTPTestRunner struct { 222 Do func(*http.Request, *TestCase) (*http.Response, error) 223 Assert func(*http.Response, *TestCase) error 224 RequestBuilder func(*TestCase) (*http.Request, error) 225 } 226 227 func (r HTTPTestRunner) Run(t testing.TB, testCases ...TestCase) (*http.Response, error) { 228 var lastResponse *http.Response 229 var lastError error 230 231 if r.Do == nil { 232 panic("Request runner not implemented") 233 } 234 235 if r.Assert == nil { 236 r.Assert = AssertResponse 237 } 238 239 if r.RequestBuilder == nil { 240 r.RequestBuilder = NewRequest 241 } 242 243 for ti, tc := range testCases { 244 req, err := r.RequestBuilder(&tc) 245 if err != nil { 246 t.Errorf("[%d] Request build error: %s", ti, err.Error()) 247 continue 248 } 249 lastResponse, lastError = r.Do(req, &tc) 250 tcJSON, _ := json.Marshal(tc) 251 252 if lastError != nil { 253 if tc.ErrorMatch != "" { 254 if !strings.Contains(lastError.Error(), tc.ErrorMatch) { 255 t.Errorf("[%d] Expect error `%s` to contain `%s`. %s", ti, lastError.Error(), tc.ErrorMatch, string(tcJSON)) 256 } 257 } else { 258 t.Errorf("[%d] Connection error: %s. %s", ti, lastError.Error(), string(tcJSON)) 259 } 260 continue 261 } else if tc.ErrorMatch != "" { 262 t.Error("Expect error.", string(tcJSON)) 263 continue 264 } 265 266 respCopy := copyResponse(lastResponse) 267 if lastError = r.Assert(respCopy, &tc); lastError != nil { 268 t.Errorf("[%d] %s. %s\n", ti, lastError.Error(), string(tcJSON)) 269 } 270 271 delay := tc.Delay 272 273 if delay > 0 { 274 time.Sleep(delay) 275 } 276 } 277 278 return lastResponse, lastError 279 } 280 281 func HttpHandlerRunner(handler http.HandlerFunc) func(*http.Request, *TestCase) (*http.Response, error) { 282 return func(r *http.Request, _ *TestCase) (*http.Response, error) { 283 rec := httptest.NewRecorder() 284 handler(rec, r) 285 return rec.Result(), nil 286 } 287 } 288 289 func TestHttpHandler(t testing.TB, handle http.HandlerFunc, testCases ...TestCase) { 290 runner := HTTPTestRunner{ 291 Do: HttpHandlerRunner(handle), 292 } 293 runner.Run(t, testCases...) 294 } 295 296 func HttpServerRequestBuilder(baseURL string) func(tc *TestCase) (*http.Request, error) { 297 return func(tc *TestCase) (*http.Request, error) { 298 tc.BaseURL = baseURL 299 return NewRequest(tc) 300 } 301 } 302 303 func HttpServerRunner() func(*http.Request, *TestCase) (*http.Response, error) { 304 return func(r *http.Request, tc *TestCase) (*http.Response, error) { 305 return tc.Client.Do(r) 306 } 307 } 308 309 func TestHttpServer(t testing.TB, baseURL string, testCases ...TestCase) { 310 runner := HTTPTestRunner{ 311 Do: HttpServerRunner(), 312 RequestBuilder: HttpServerRequestBuilder(baseURL), 313 } 314 runner.Run(t, testCases...) 315 }