github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/testhelpers/net/server.go (about)

     1  package net
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"github.com/onsi/ginkgo"
    11  )
    12  
    13  type TestRequest struct {
    14  	Method   string
    15  	Path     string
    16  	Header   http.Header
    17  	Matcher  RequestMatcher
    18  	Response TestResponse
    19  }
    20  
    21  type RequestMatcher func(*http.Request)
    22  
    23  type TestResponse struct {
    24  	Body   string
    25  	Status int
    26  	Header http.Header
    27  }
    28  
    29  type TestHandler struct {
    30  	Requests  []TestRequest
    31  	CallCount int
    32  }
    33  
    34  func (h *TestHandler) AllRequestsCalled() bool {
    35  	return h.CallCount == len(h.Requests)
    36  }
    37  
    38  func urlQueryContains(container, containee url.Values) bool {
    39  	//Cloud Controller often uses "q" as a container for search queries, which may be semantically
    40  	//equivalent to CC but be actually different strings.
    41  
    42  	//Example: "foo:bar;baz:qux" is semantically the same as "baz:qux;foo:bar". CC doesn't care about order.
    43  
    44  	//Therefore, we crack apart "q" params on their seperator (a colon) and compare the resulting
    45  	//substrings.  No other params seem to use semicolon separators AND are order-dependent, so we just
    46  	//run all params through the same process.
    47  	for key := range containee {
    48  
    49  		containerValues := strings.Split(container.Get(key), ";")
    50  		containeeValues := strings.Split(containee.Get(key), ";")
    51  
    52  		allValuesFound := make([]bool, len(containeeValues))
    53  
    54  		for index, expected := range containeeValues {
    55  			for _, actual := range containerValues {
    56  				if expected == actual {
    57  					allValuesFound[index] = true
    58  					break
    59  				}
    60  			}
    61  		}
    62  		for _, ok := range allValuesFound {
    63  			if !ok {
    64  				return false
    65  			}
    66  		}
    67  	}
    68  
    69  	return true
    70  }
    71  
    72  func (h *TestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    73  	defer ginkgo.GinkgoRecover()
    74  
    75  	if len(h.Requests) <= h.CallCount {
    76  		h.logError("Index out of range! Test server called too many times. Final Request:", r.Method, r.RequestURI)
    77  		return
    78  	}
    79  
    80  	tester := h.Requests[h.CallCount]
    81  	h.CallCount++
    82  
    83  	// match method
    84  	if tester.Method != r.Method {
    85  		h.logError("Method does not match.\nExpected: %s\nActual:   %s", tester.Method, r.Method)
    86  	}
    87  
    88  	// match path
    89  	paths := strings.Split(tester.Path, "?")
    90  	if paths[0] != r.URL.Path {
    91  		h.logError("Path does not match.\nExpected: %s\nActual:   %s", paths[0], r.URL.Path)
    92  	}
    93  	// match query string
    94  	if len(paths) > 1 {
    95  		actualValues, _ := url.ParseQuery(r.URL.RawQuery)
    96  		expectedValues, _ := url.ParseQuery(paths[1])
    97  
    98  		if !urlQueryContains(actualValues, expectedValues) {
    99  			h.logError("Query string does not match.\nExpected: %s\nActual:   %s", paths[1], r.URL.RawQuery)
   100  		}
   101  	}
   102  
   103  	for key, values := range tester.Header {
   104  		key = http.CanonicalHeaderKey(key)
   105  		actualValues := strings.Join(r.Header[key], ";")
   106  		expectedValues := strings.Join(values, ";")
   107  
   108  		if key == "Authorization" && !strings.Contains(actualValues, expectedValues) {
   109  			h.logError("%s header is not contained in actual value.\nExpected: %s\nActual:   %s", key, expectedValues, actualValues)
   110  		}
   111  		if key != "Authorization" && actualValues != expectedValues {
   112  			h.logError("%s header did not match.\nExpected: %s\nActual:   %s", key, expectedValues, actualValues)
   113  		}
   114  	}
   115  
   116  	// match custom request matcher
   117  	if tester.Matcher != nil {
   118  		tester.Matcher(r)
   119  	}
   120  
   121  	// set response headers
   122  	header := w.Header()
   123  	for name, values := range tester.Response.Header {
   124  		if len(values) < 1 {
   125  			continue
   126  		}
   127  		header.Set(name, values[0])
   128  	}
   129  
   130  	// write response
   131  	w.WriteHeader(tester.Response.Status)
   132  	fmt.Fprintln(w, tester.Response.Body)
   133  }
   134  
   135  func NewTLSServer(requests []TestRequest) (*httptest.Server, *TestHandler) {
   136  	handler := &TestHandler{Requests: requests}
   137  	return httptest.NewTLSServer(handler), handler
   138  }
   139  
   140  func NewServer(requests []TestRequest) (*httptest.Server, *TestHandler) {
   141  	handler := &TestHandler{Requests: requests}
   142  	return httptest.NewServer(handler), handler
   143  }
   144  
   145  func (h *TestHandler) logError(msg string, args ...interface{}) {
   146  	println(fmt.Sprintf(msg, args...))
   147  	ginkgo.Fail("failed")
   148  }