package httpexpect import ( "bufio" "crypto/tls" "io/ioutil" "net/http" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/valyala/fasthttp" ) type mockHandler struct { t *testing.T https bool http10 bool chunked bool } func (c *mockHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { body, err := ioutil.ReadAll(req.Body) assert.True(c.t, err == nil) if c.http10 { assert.Equal(c.t, "HTTP/1.0", req.Proto) assert.Equal(c.t, 1, req.ProtoMajor) assert.Equal(c.t, 0, req.ProtoMinor) } else { assert.Equal(c.t, "HTTP/1.1", req.Proto) assert.Equal(c.t, 1, req.ProtoMajor) assert.Equal(c.t, 1, req.ProtoMinor) } if c.https { assert.Equal(c.t, "https://example.com/path", req.URL.String()) } else { assert.Equal(c.t, "http://example.com/path", req.URL.String()) } assert.Equal(c.t, "/path", req.URL.RequestURI()) assert.Equal(c.t, "/path", req.RequestURI) assert.Equal(c.t, "GET", req.Method) assert.Equal(c.t, "body", string(body)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, err = w.Write([]byte(`{"hello":"world"}`)) if c.chunked { w.(http.Flusher).Flush() } assert.True(c.t, err == nil) } func TestBinder(t *testing.T) { handler := &mockHandler{ t: t, http10: true, chunked: false, } client := &http.Client{ Transport: NewBinder(handler), } req, err := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) if err != nil { t.Fatal(err) } req.ProtoMajor = 1 req.ProtoMinor = 0 req.Proto = "" resp, err := client.Do(req) if err != nil { t.Fatal(err) } header := http.Header{ "Content-Type": {"application/json"}, } b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } assert.Equal(t, header, resp.Header) assert.Equal(t, `{"hello":"world"}`, string(b)) assert.Equal(t, []string(nil), resp.TransferEncoding) } func TestBinderTLS(t *testing.T) { handler := &mockHandler{t: t} httpClient := &http.Client{ Transport: &Binder{ Handler: handler, TLS: nil, }, } tlsState := &tls.ConnectionState{} httpsClient := &http.Client{ Transport: &Binder{ Handler: handler, TLS: tlsState, }, } handler.https = false req, _ := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) resp, err := httpClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.Nil(t, req.TLS) handler.https = true req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body")) resp, err = httpClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.Nil(t, req.TLS) handler.https = false req, _ = http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) resp, err = httpsClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.Nil(t, req.TLS) handler.https = true req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body")) resp, err = httpsClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.NotNil(t, req.TLS) assert.Equal(t, tlsState, req.TLS) } func TestBinderChunked(t *testing.T) { handler := &mockHandler{ t: t, chunked: true, } client := &http.Client{ Transport: NewBinder(handler), } req, err := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) if err != nil { t.Fatal(err) } req.ContentLength = -1 resp, err := client.Do(req) if err != nil { t.Fatal(err) } assert.Equal(t, []string{"chunked"}, req.TransferEncoding) assert.Equal(t, []string{"chunked"}, resp.TransferEncoding) } func TestFastBinder(t *testing.T) { handler := func(ctx *fasthttp.RequestCtx) { assert.Equal(t, "POST", string(ctx.Request.Header.Method())) assert.Equal(t, "http://example.com/path", string(ctx.Request.Header.RequestURI())) assert.Equal(t, "application/x-www-form-urlencoded", string(ctx.Request.Header.ContentType())) headers := map[string][]string{} ctx.Request.Header.VisitAll(func(k, v []byte) { headers[string(k)] = append(headers[string(k)], string(v)) }) expected := map[string][]string{ "Content-Type": {"application/x-www-form-urlencoded"}, "Content-Length": {"7"}, "Some-Header": {"foo", "bar"}, } assert.Equal(t, expected, headers) assert.Equal(t, "bar", string(ctx.FormValue("foo"))) assert.Equal(t, "foo=bar", string(ctx.Request.Body())) ctx.Response.Header.Set("Content-Type", "application/json") ctx.Response.SetBody([]byte(`{"hello":"world"}`)) } client := &http.Client{ Transport: NewFastBinder(handler), } req, err := http.NewRequest( "POST", "http://example.com/path", strings.NewReader("foo=bar")) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Some-Header", "foo") req.Header.Add("Some-Header", "bar") resp, err := client.Do(req) if err != nil { t.Fatal(err) } header := http.Header{ "Content-Type": {"application/json"}, } b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } assert.Equal(t, header, resp.Header) assert.Equal(t, `{"hello":"world"}`, string(b)) assert.Equal(t, []string(nil), resp.TransferEncoding) } func TestFastBinderTLS(t *testing.T) { var isHTTPS, isTLS bool tlsState := &tls.ConnectionState{} handler := func(ctx *fasthttp.RequestCtx) { isHTTPS = strings.HasPrefix(string(ctx.Request.Header.RequestURI()), "https://") isTLS = ctx.IsTLS() if isTLS { assert.Equal(t, *tlsState, *ctx.TLSConnectionState()) } } httpClient := &http.Client{ Transport: &FastBinder{ Handler: handler, TLS: nil, }, } httpsClient := &http.Client{ Transport: &FastBinder{ Handler: handler, TLS: tlsState, }, } req, _ := http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) resp, err := httpClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.False(t, isHTTPS) assert.False(t, isTLS) req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body")) resp, err = httpClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.True(t, isHTTPS) assert.False(t, isTLS) req, _ = http.NewRequest("GET", "http://example.com/path", strings.NewReader("body")) resp, err = httpsClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.False(t, isHTTPS) assert.False(t, isTLS) req, _ = http.NewRequest("GET", "https://example.com/path", strings.NewReader("body")) resp, err = httpsClient.Do(req) assert.Nil(t, err) assert.NotNil(t, resp) assert.True(t, isHTTPS) assert.True(t, isTLS) } func TestFastBinderChunked(t *testing.T) { handler := func(ctx *fasthttp.RequestCtx) { assert.Equal(t, "POST", string(ctx.Request.Header.Method())) assert.Equal(t, "http://example.com/path", string(ctx.Request.Header.RequestURI())) assert.Equal(t, "application/x-www-form-urlencoded", string(ctx.Request.Header.ContentType())) headers := map[string][]string{} ctx.Request.Header.VisitAll(func(k, v []byte) { headers[string(k)] = append(headers[string(k)], string(v)) }) expected := map[string][]string{ "Content-Type": {"application/x-www-form-urlencoded"}, "Transfer-Encoding": {"chunked"}, } assert.Equal(t, expected, headers) assert.Equal(t, "bar", string(ctx.FormValue("foo"))) assert.Equal(t, "foo=bar", string(ctx.Request.Body())) ctx.Response.Header.Set("Content-Type", "application/json") ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) { w.WriteString(`[1, `) w.Flush() w.WriteString(`2]`) }) } client := &http.Client{ Transport: NewFastBinder(handler), } req, err := http.NewRequest( "POST", "http://example.com/path", strings.NewReader("foo=bar")) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.ContentLength = -1 resp, err := client.Do(req) if err != nil { t.Fatal(err) } assert.Equal(t, []string{"chunked"}, resp.TransferEncoding) } func TestFastBinderEmptyResponse(t *testing.T) { handler := func(*fasthttp.RequestCtx) {} client := &http.Client{ Transport: NewFastBinder(handler), } req, err := http.NewRequest("POST", "http://example.com/path", nil) if err != nil { t.Fatal(err) } resp, err := client.Do(req) if err != nil { t.Fatal(err) } assert.False(t, resp.Body == nil) b, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } assert.Equal(t, "", string(b)) }