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  }