package goreq import ( "compress/gzip" "compress/zlib" "encoding/base64" "fmt" "io" "io/ioutil" "math" "net/http" "net/http/cookiejar" "net/http/httptest" "net/url" "strings" "testing" "time" . "github.com/franela/goblin" . "github.com/onsi/gomega" ) type Query struct { Limit int Skip int } func TestRequest(t *testing.T) { query := Query{ Limit: 3, Skip: 5, } valuesQuery := url.Values{} valuesQuery.Set("name", "marcos") valuesQuery.Add("friend", "jonas") valuesQuery.Add("friend", "peter") g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("Request", func() { g.Describe("General request methods", func() { var ts *httptest.Server var requestHeaders http.Header g.Before(func() { ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestHeaders = r.Header if (r.Method == "GET" || r.Method == "OPTIONS" || r.Method == "TRACE" || r.Method == "PATCH" || r.Method == "FOOBAR") && r.URL.Path == "/foo" { w.WriteHeader(200) fmt.Fprint(w, "bar") } if r.Method == "GET" && r.URL.Path == "/getquery" { w.WriteHeader(200) fmt.Fprint(w, fmt.Sprintf("%v", r.URL)) } if r.Method == "GET" && r.URL.Path == "/getbody" { w.WriteHeader(200) io.Copy(w, r.Body) } if r.Method == "POST" && r.URL.Path == "/" { w.Header().Add("Location", ts.URL+"/123") w.WriteHeader(201) io.Copy(w, r.Body) } if r.Method == "POST" && r.URL.Path == "/getquery" { w.WriteHeader(200) fmt.Fprint(w, fmt.Sprintf("%v", r.URL)) } if r.Method == "PUT" && r.URL.Path == "/foo/123" { w.WriteHeader(200) io.Copy(w, r.Body) } if r.Method == "DELETE" && r.URL.Path == "/foo/123" { w.WriteHeader(204) } if r.Method == "GET" && r.URL.Path == "/redirect_test/301" { http.Redirect(w, r, "/redirect_test/302", 301) } if r.Method == "GET" && r.URL.Path == "/redirect_test/302" { http.Redirect(w, r, "/redirect_test/303", 302) } if r.Method == "GET" && r.URL.Path == "/redirect_test/303" { http.Redirect(w, r, "/redirect_test/307", 303) } if r.Method == "GET" && r.URL.Path == "/redirect_test/307" { http.Redirect(w, r, "/getquery", 307) } if r.Method == "GET" && r.URL.Path == "/redirect_test/destination" { http.Redirect(w, r, ts.URL+"/destination", 301) } if r.Method == "GET" && r.URL.Path == "/getcookies" { defer r.Body.Close() w.WriteHeader(200) fmt.Fprint(w, requestHeaders.Get("Cookie")) } if r.Method == "GET" && r.URL.Path == "/setcookies" { defer r.Body.Close() w.Header().Add("Set-Cookie", "foobar=42 ; Path=/") w.WriteHeader(200) } if r.Method == "GET" && r.URL.Path == "/compressed" { defer r.Body.Close() b := "{\"foo\":\"bar\",\"fuu\":\"baz\"}" gw := gzip.NewWriter(w) defer gw.Close() if strings.Contains(r.Header.Get("Content-Encoding"), "gzip") { w.Header().Add("Content-Encoding", "gzip") } w.WriteHeader(200) gw.Write([]byte(b)) } if r.Method == "GET" && r.URL.Path == "/compressed_deflate" { defer r.Body.Close() b := "{\"foo\":\"bar\",\"fuu\":\"baz\"}" gw := zlib.NewWriter(w) defer gw.Close() if strings.Contains(r.Header.Get("Content-Encoding"), "deflate") { w.Header().Add("Content-Encoding", "deflate") } w.WriteHeader(200) gw.Write([]byte(b)) } if r.Method == "GET" && r.URL.Path == "/compressed_and_return_compressed_without_header" { defer r.Body.Close() b := "{\"foo\":\"bar\",\"fuu\":\"baz\"}" gw := gzip.NewWriter(w) defer gw.Close() w.WriteHeader(200) gw.Write([]byte(b)) } if r.Method == "GET" && r.URL.Path == "/compressed_deflate_and_return_compressed_without_header" { defer r.Body.Close() b := "{\"foo\":\"bar\",\"fuu\":\"baz\"}" gw := zlib.NewWriter(w) defer gw.Close() w.WriteHeader(200) gw.Write([]byte(b)) } if r.Method == "POST" && r.URL.Path == "/compressed" && r.Header.Get("Content-Encoding") == "gzip" { defer r.Body.Close() gr, _ := gzip.NewReader(r.Body) defer gr.Close() b, _ := ioutil.ReadAll(gr) w.WriteHeader(201) w.Write(b) } if r.Method == "POST" && r.URL.Path == "/compressed_deflate" && r.Header.Get("Content-Encoding") == "deflate" { defer r.Body.Close() gr, _ := zlib.NewReader(r.Body) defer gr.Close() b, _ := ioutil.ReadAll(gr) w.WriteHeader(201) w.Write(b) } if r.Method == "POST" && r.URL.Path == "/compressed_and_return_compressed" { defer r.Body.Close() w.Header().Add("Content-Encoding", "gzip") w.WriteHeader(201) io.Copy(w, r.Body) } if r.Method == "POST" && r.URL.Path == "/compressed_deflate_and_return_compressed" { defer r.Body.Close() w.Header().Add("Content-Encoding", "deflate") w.WriteHeader(201) io.Copy(w, r.Body) } if r.Method == "POST" && r.URL.Path == "/compressed_deflate_and_return_compressed_without_header" { defer r.Body.Close() w.WriteHeader(201) io.Copy(w, r.Body) } if r.Method == "POST" && r.URL.Path == "/compressed_and_return_compressed_without_header" { defer r.Body.Close() w.WriteHeader(201) io.Copy(w, r.Body) } })) }) g.After(func() { ts.Close() }) g.Describe("GET", func() { g.It("Should do a GET", func() { res, err := Request{Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should return ContentLength", func() { res, err := Request{Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) Expect(res.ContentLength).Should(Equal(int64(3))) }) g.It("Should do a GET with querystring", func() { res, err := Request{ Uri: ts.URL + "/getquery", QueryString: query, }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("/getquery?limit=3&skip=5")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should support url.Values in querystring", func() { res, err := Request{ Uri: ts.URL + "/getquery", QueryString: valuesQuery, }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("/getquery?friend=jonas&friend=peter&name=marcos")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should support sending string body", func() { res, err := Request{Uri: ts.URL + "/getbody", Body: "foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Shoulds support sending a Reader body", func() { res, err := Request{Uri: ts.URL + "/getbody", Body: strings.NewReader("foo")}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Support sending any object that is json encodable", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Uri: ts.URL + "/getbody", Body: obj}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Support sending an array of bytes body", func() { bdy := []byte{'f', 'o', 'o'} res, err := Request{Uri: ts.URL + "/getbody", Body: bdy}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should return an error when body is not JSON encodable", func() { res, err := Request{Uri: ts.URL + "/getbody", Body: math.NaN()}.Do() Expect(res).Should(BeNil()) Expect(err).ShouldNot(BeNil()) }) g.It("Should return a gzip reader if Content-Encoding is 'gzip'", func() { res, err := Request{Uri: ts.URL + "/compressed", Compression: Gzip()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(res.Body.compressedReader).ShouldNot(BeNil()) Expect(res.Body.reader).ShouldNot(BeNil()) Expect(string(b)).Should(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) Expect(res.Body.compressedReader).ShouldNot(BeNil()) Expect(res.Body.reader).ShouldNot(BeNil()) }) g.It("Should close reader and compresserReader on Body close", func() { res, err := Request{Uri: ts.URL + "/compressed", Compression: Gzip()}.Do() Expect(err).Should(BeNil()) _, e := ioutil.ReadAll(res.Body.reader) Expect(e).Should(BeNil()) _, e = ioutil.ReadAll(res.Body.compressedReader) Expect(e).Should(BeNil()) _, e = ioutil.ReadAll(res.Body.reader) //when reading body again it doesnt error Expect(e).Should(BeNil()) res.Body.Close() _, e = ioutil.ReadAll(res.Body.reader) //error because body is already closed Expect(e).ShouldNot(BeNil()) _, e = ioutil.ReadAll(res.Body.compressedReader) //compressedReaders dont error on reading when closed Expect(e).Should(BeNil()) }) g.It("Should not return a gzip reader if Content-Encoding is not 'gzip'", func() { res, err := Request{Uri: ts.URL + "/compressed_and_return_compressed_without_header", Compression: Gzip()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(string(b)).ShouldNot(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) }) g.It("Should return a deflate reader if Content-Encoding is 'deflate'", func() { res, err := Request{Uri: ts.URL + "/compressed_deflate", Compression: Deflate()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(string(b)).Should(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) }) g.It("Should not return a delfate reader if Content-Encoding is not 'deflate'", func() { res, err := Request{Uri: ts.URL + "/compressed_deflate_and_return_compressed_without_header", Compression: Deflate()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(string(b)).ShouldNot(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) }) g.It("Should return a deflate reader when using zlib if Content-Encoding is 'deflate'", func() { res, err := Request{Uri: ts.URL + "/compressed_deflate", Compression: Zlib()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(string(b)).Should(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) }) g.It("Should not return a delfate reader when using zlib if Content-Encoding is not 'deflate'", func() { res, err := Request{Uri: ts.URL + "/compressed_deflate_and_return_compressed_without_header", Compression: Zlib()}.Do() b, _ := ioutil.ReadAll(res.Body) Expect(err).Should(BeNil()) Expect(string(b)).ShouldNot(Equal("{\"foo\":\"bar\",\"fuu\":\"baz\"}")) }) g.It("Should send cookies from the cookiejar", func() { uri, err := url.Parse(ts.URL + "/getcookies") Expect(err).Should(BeNil()) jar, err := cookiejar.New(nil) Expect(err).Should(BeNil()) jar.SetCookies(uri, []*http.Cookie{ { Name: "bar", Value: "foo", Path: "/", }, }) res, err := Request{ Uri: ts.URL + "/getcookies", CookieJar: jar, }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar=foo")) Expect(res.StatusCode).Should(Equal(200)) Expect(res.ContentLength).Should(Equal(int64(7))) }) g.It("Should send cookies added with .AddCookie", func() { c1 := &http.Cookie{Name: "c1", Value: "v1"} c2 := &http.Cookie{Name: "c2", Value: "v2"} req := Request{Uri: ts.URL + "/getcookies"} req.AddCookie(c1) req.AddCookie(c2) res, err := req.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("c1=v1; c2=v2")) Expect(res.StatusCode).Should(Equal(200)) Expect(res.ContentLength).Should(Equal(int64(12))) }) g.It("Should send cookies added with .WithCookie", func() { c1 := &http.Cookie{Name: "c1", Value: "v2"} c2 := &http.Cookie{Name: "c2", Value: "v3"} res, err := Request{Uri: ts.URL + "/getcookies"}. WithCookie(c1). WithCookie(c2). Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("c1=v2; c2=v3")) Expect(res.StatusCode).Should(Equal(200)) Expect(res.ContentLength).Should(Equal(int64(12))) }) g.It("Should populate the cookiejar", func() { uri, err := url.Parse(ts.URL + "/setcookies") Expect(err).Should(BeNil()) jar, _ := cookiejar.New(nil) Expect(err).Should(BeNil()) res, err := Request{ Uri: ts.URL + "/setcookies", CookieJar: jar, }.Do() Expect(err).Should(BeNil()) Expect(res.Header.Get("Set-Cookie")).Should(Equal("foobar=42 ; Path=/")) cookies := jar.Cookies(uri) Expect(len(cookies)).Should(Equal(1)) cookie := cookies[0] Expect(*cookie).Should(Equal(http.Cookie{ Name: "foobar", Value: "42", })) }) }) g.Describe("POST", func() { g.It("Should send a string", func() { res, err := Request{Method: "POST", Uri: ts.URL, Body: "foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(201)) Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123")) }) g.It("Should send a Reader", func() { res, err := Request{Method: "POST", Uri: ts.URL, Body: strings.NewReader("foo")}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(201)) Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123")) }) g.It("Send any object that is json encodable", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL, Body: obj}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123")) }) g.It("Send an array of bytes", func() { bdy := []byte{'f', 'o', 'o'} res, err := Request{Method: "POST", Uri: ts.URL, Body: bdy}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(201)) Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123")) }) g.It("Should return an error when body is not JSON encodable", func() { res, err := Request{Method: "POST", Uri: ts.URL, Body: math.NaN()}.Do() Expect(res).Should(BeNil()) Expect(err).ShouldNot(BeNil()) }) g.It("Should do a POST with querystring", func() { bdy := []byte{'f', 'o', 'o'} res, err := Request{ Method: "POST", Uri: ts.URL + "/getquery", Body: bdy, QueryString: query, }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("/getquery?limit=3&skip=5")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should send body as gzip if compressed", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed", Body: obj, Compression: Gzip()}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate if compressed", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate", Body: obj, Compression: Deflate()}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate using zlib if compressed", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate", Body: obj, Compression: Zlib()}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as gzip if compressed and parse return body", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_and_return_compressed", Body: obj, Compression: Gzip()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate if compressed and parse return body", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate_and_return_compressed", Body: obj, Compression: Deflate()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate using zlib if compressed and parse return body", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate_and_return_compressed", Body: obj, Compression: Zlib()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).Should(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as gzip if compressed and not parse return body if header not set ", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_and_return_compressed_without_header", Body: obj, Compression: Gzip()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).ShouldNot(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate if compressed and not parse return body if header not set ", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate_and_return_compressed_without_header", Body: obj, Compression: Deflate()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).ShouldNot(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) g.It("Should send body as deflate using zlib if compressed and not parse return body if header not set ", func() { obj := map[string]string{"foo": "bar"} res, err := Request{Method: "POST", Uri: ts.URL + "/compressed_deflate_and_return_compressed_without_header", Body: obj, Compression: Zlib()}.Do() Expect(err).Should(BeNil()) b, _ := ioutil.ReadAll(res.Body) Expect(string(b)).ShouldNot(Equal(`{"foo":"bar"}`)) Expect(res.StatusCode).Should(Equal(201)) }) }) g.It("Should do a PUT", func() { res, err := Request{Method: "PUT", Uri: ts.URL + "/foo/123", Body: "foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("foo")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should do a DELETE", func() { res, err := Request{Method: "DELETE", Uri: ts.URL + "/foo/123"}.Do() Expect(err).Should(BeNil()) Expect(res.StatusCode).Should(Equal(204)) }) g.It("Should do a OPTIONS", func() { res, err := Request{Method: "OPTIONS", Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should do a PATCH", func() { res, err := Request{Method: "PATCH", Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should do a TRACE", func() { res, err := Request{Method: "TRACE", Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should do a custom method", func() { res, err := Request{Method: "FOOBAR", Uri: ts.URL + "/foo"}.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(str).Should(Equal("bar")) Expect(res.StatusCode).Should(Equal(200)) }) g.Describe("Responses", func() { g.It("Should handle strings", func() { res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do() str, _ := res.Body.ToString() Expect(str).Should(Equal("foo bar")) }) g.It("Should handle io.ReaderCloser", func() { res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do() body, _ := ioutil.ReadAll(res.Body) Expect(string(body)).Should(Equal("foo bar")) }) g.It("Should handle parsing JSON", func() { res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"}`}.Do() var foobar map[string]string res.Body.FromJsonTo(&foobar) Expect(foobar).Should(Equal(map[string]string{"foo": "bar"})) }) g.It("Should return the original request response", func() { res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"}`}.Do() Expect(res.Response).ShouldNot(BeNil()) }) }) g.Describe("Redirects", func() { g.It("Should not follow by default", func() { res, _ := Request{ Uri: ts.URL + "/redirect_test/301", }.Do() Expect(res.StatusCode).Should(Equal(301)) }) g.It("Should not follow if method is explicitly specified", func() { res, err := Request{ Method: "GET", Uri: ts.URL + "/redirect_test/301", }.Do() Expect(res.StatusCode).Should(Equal(301)) Expect(err).ShouldNot(HaveOccurred()) }) g.It("Should throw an error if MaxRedirect limit is exceeded", func() { res, err := Request{ Method: "GET", MaxRedirects: 1, Uri: ts.URL + "/redirect_test/301", }.Do() Expect(res.StatusCode).Should(Equal(302)) Expect(err).Should(HaveOccurred()) }) g.It("Should copy request headers headers when redirecting if specified", func() { req := Request{ Method: "GET", Uri: ts.URL + "/redirect_test/301", MaxRedirects: 4, RedirectHeaders: true, } req.AddHeader("Testheader", "TestValue") res, _ := req.Do() Expect(res.StatusCode).Should(Equal(200)) Expect(requestHeaders.Get("Testheader")).Should(Equal("TestValue")) }) g.It("Should follow only specified number of MaxRedirects", func() { res, _ := Request{ Uri: ts.URL + "/redirect_test/301", MaxRedirects: 1, }.Do() Expect(res.StatusCode).Should(Equal(302)) res, _ = Request{ Uri: ts.URL + "/redirect_test/301", MaxRedirects: 2, }.Do() Expect(res.StatusCode).Should(Equal(303)) res, _ = Request{ Uri: ts.URL + "/redirect_test/301", MaxRedirects: 3, }.Do() Expect(res.StatusCode).Should(Equal(307)) res, _ = Request{ Uri: ts.URL + "/redirect_test/301", MaxRedirects: 4, }.Do() Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should return final URL of the response when redirecting", func() { res, _ := Request{ Uri: ts.URL + "/redirect_test/destination", MaxRedirects: 2, }.Do() Expect(res.Uri).Should(Equal(ts.URL + "/destination")) }) }) }) g.Describe("Timeouts", func() { g.Describe("Connection timeouts", func() { g.It("Should connect timeout after a default of 1000 ms", func() { start := time.Now() res, err := Request{Uri: "http://10.255.255.1"}.Do() elapsed := time.Since(start) Expect(elapsed).Should(BeNumerically("<", 1100*time.Millisecond)) Expect(elapsed).Should(BeNumerically(">=", 1000*time.Millisecond)) Expect(res).Should(BeNil()) Expect(err.(*Error).Timeout()).Should(BeTrue()) }) g.It("Should connect timeout after a custom amount of time", func() { SetConnectTimeout(100 * time.Millisecond) start := time.Now() res, err := Request{Uri: "http://10.255.255.1"}.Do() elapsed := time.Since(start) Expect(elapsed).Should(BeNumerically("<", 150*time.Millisecond)) Expect(elapsed).Should(BeNumerically(">=", 100*time.Millisecond)) Expect(res).Should(BeNil()) Expect(err.(*Error).Timeout()).Should(BeTrue()) }) g.It("Should connect timeout after a custom amount of time even with method set", func() { SetConnectTimeout(100 * time.Millisecond) start := time.Now() request := Request{ Uri: "http://10.255.255.1", Method: "GET", } res, err := request.Do() elapsed := time.Since(start) Expect(elapsed).Should(BeNumerically("<", 150*time.Millisecond)) Expect(elapsed).Should(BeNumerically(">=", 100*time.Millisecond)) Expect(res).Should(BeNil()) Expect(err.(*Error).Timeout()).Should(BeTrue()) }) }) g.Describe("Request timeout", func() { var ts *httptest.Server stop := make(chan bool) g.Before(func() { ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-stop // just wait for someone to tell you when to end the request. this is used to simulate a slow server })) }) g.After(func() { stop <- true ts.Close() }) g.It("Should request timeout after a custom amount of time", func() { SetConnectTimeout(1000 * time.Millisecond) start := time.Now() res, err := Request{Uri: ts.URL, Timeout: 500 * time.Millisecond}.Do() elapsed := time.Since(start) Expect(elapsed).Should(BeNumerically("<", 550*time.Millisecond)) Expect(elapsed).Should(BeNumerically(">=", 500*time.Millisecond)) Expect(res).Should(BeNil()) Expect(err.(*Error).Timeout()).Should(BeTrue()) }) g.It("Should request timeout after a custom amount of time even with proxy", func() { proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(2000 * time.Millisecond) w.WriteHeader(200) })) SetConnectTimeout(1000 * time.Millisecond) start := time.Now() request := Request{ Uri: ts.URL, Proxy: proxy.URL, Timeout: 500 * time.Millisecond, } res, err := request.Do() elapsed := time.Since(start) Expect(elapsed).Should(BeNumerically("<", 550*time.Millisecond)) Expect(elapsed).Should(BeNumerically(">=", 500*time.Millisecond)) Expect(res).Should(BeNil()) Expect(err.(*Error).Timeout()).Should(BeTrue()) }) }) }) g.Describe("Misc", func() { g.It("Should set default golang user agent when not explicitly passed", func() { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Expect(r.Header.Get("User-Agent")).ShouldNot(BeZero()) Expect(r.Host).Should(Equal("foobar.com")) w.WriteHeader(200) })) defer ts.Close() req := Request{Uri: ts.URL, Host: "foobar.com"} res, err := req.Do() Expect(err).ShouldNot(HaveOccurred()) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should offer to set request headers", func() { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Expect(r.Header.Get("User-Agent")).Should(Equal("foobaragent")) Expect(r.Host).Should(Equal("foobar.com")) Expect(r.Header.Get("Accept")).Should(Equal("application/json")) Expect(r.Header.Get("Content-Type")).Should(Equal("application/json")) Expect(r.Header.Get("X-Custom")).Should(Equal("foobar")) Expect(r.Header.Get("X-Custom2")).Should(Equal("barfoo")) w.WriteHeader(200) })) defer ts.Close() req := Request{Uri: ts.URL, Accept: "application/json", ContentType: "application/json", UserAgent: "foobaragent", Host: "foobar.com"} req.AddHeader("X-Custom", "foobar") res, _ := req.WithHeader("X-Custom2", "barfoo").Do() Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should call hook before request", func() { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Expect(r.Header.Get("X-Custom")).Should(Equal("foobar")) w.WriteHeader(200) })) defer ts.Close() hook := func(goreq *Request, httpreq *http.Request) { httpreq.Header.Add("X-Custom", "foobar") } req := Request{Uri: ts.URL, OnBeforeRequest: hook} res, _ := req.Do() Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should not create a body by defualt", func() { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, _ := ioutil.ReadAll(r.Body) Expect(b).Should(HaveLen(0)) w.WriteHeader(200) })) defer ts.Close() req := Request{Uri: ts.URL, Host: "foobar.com"} req.Do() }) g.It("Should change transport TLS config if Request.Insecure is set", func() { ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })) defer ts.Close() req := Request{ Insecure: true, Uri: ts.URL, Host: "foobar.com", } res, _ := req.Do() Expect(DefaultClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify).Should(Equal(true)) Expect(res.StatusCode).Should(Equal(200)) }) g.It("Should work if a different transport is specified", func() { ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })) defer ts.Close() var currentTransport = DefaultTransport DefaultTransport = &http.Transport{Dial: DefaultDialer.Dial} req := Request{ Insecure: true, Uri: ts.URL, Host: "foobar.com", } res, _ := req.Do() Expect(DefaultClient.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify).Should(Equal(true)) Expect(res.StatusCode).Should(Equal(200)) DefaultTransport = currentTransport }) g.It("GetRequest should return the underlying httpRequest ", func() { req := Request{ Host: "foobar.com", } request, _ := req.NewRequest() Expect(request).ShouldNot(BeNil()) Expect(request.Host).Should(Equal(req.Host)) }) g.It("Response should allow to cancel in-flight request", func() { unblockc := make(chan bool) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello") w.(http.Flusher).Flush() <-unblockc })) defer ts.Close() defer close(unblockc) req := Request{ Insecure: true, Uri: ts.URL, Host: "foobar.com", } res, _ := req.Do() res.CancelRequest() _, err := ioutil.ReadAll(res.Body) g.Assert(err != nil).IsTrue() }) }) g.Describe("Errors", func() { var ts *httptest.Server g.Before(func() { ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" && r.URL.Path == "/" { w.Header().Add("Location", ts.URL+"/123") w.WriteHeader(201) io.Copy(w, r.Body) } })) }) g.After(func() { ts.Close() }) g.It("Should throw an error when FromJsonTo fails", func() { res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo" "bar"}`}.Do() var foobar map[string]string err := res.Body.FromJsonTo(&foobar) Expect(err).Should(HaveOccurred()) }) g.It("Should handle Url parsing errors", func() { _, err := Request{Uri: ":"}.Do() Expect(err).ShouldNot(BeNil()) }) g.It("Should handle DNS errors", func() { _, err := Request{Uri: "http://.localhost"}.Do() Expect(err).ShouldNot(BeNil()) }) }) g.Describe("Proxy", func() { var ts *httptest.Server var lastReq *http.Request g.Before(func() { ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" && r.URL.Path == "/" { lastReq = r w.Header().Add("x-forwarded-for", "test") w.Header().Add("Set-Cookie", "foo=bar") w.WriteHeader(200) w.Write([]byte("")) } else if r.Method == "GET" && r.URL.Path == "/redirect_test/301" { http.Redirect(w, r, "/", 301) } })) }) g.BeforeEach(func() { lastReq = nil }) g.After(func() { ts.Close() }) g.It("Should use Proxy", func() { proxiedHost := "www.google.com" res, err := Request{Uri: "http://" + proxiedHost, Proxy: ts.URL}.Do() Expect(err).Should(BeNil()) Expect(res.Header.Get("x-forwarded-for")).Should(Equal("test")) Expect(lastReq).ShouldNot(BeNil()) Expect(lastReq.Host).Should(Equal(proxiedHost)) }) g.It("Should not redirect if MaxRedirects is not set", func() { res, err := Request{Uri: ts.URL + "/redirect_test/301", Proxy: ts.URL}.Do() Expect(err).ShouldNot(HaveOccurred()) Expect(res.StatusCode).Should(Equal(301)) }) g.It("Should use Proxy authentication", func() { proxiedHost := "www.google.com" uri := strings.Replace(ts.URL, "http://", "http://user:pass@", -1) res, err := Request{Uri: "http://" + proxiedHost, Proxy: uri}.Do() Expect(err).Should(BeNil()) Expect(res.Header.Get("x-forwarded-for")).Should(Equal("test")) Expect(lastReq).ShouldNot(BeNil()) Expect(lastReq.Header.Get("Proxy-Authorization")).Should(Equal("Basic dXNlcjpwYXNz")) }) g.It("Should propagate cookies", func() { proxiedHost, _ := url.Parse("http://www.google.com") jar, _ := cookiejar.New(nil) res, err := Request{Uri: proxiedHost.String(), Proxy: ts.URL, CookieJar: jar}.Do() Expect(err).Should(BeNil()) Expect(res.Header.Get("x-forwarded-for")).Should(Equal("test")) Expect(jar.Cookies(proxiedHost)).Should(HaveLen(1)) Expect(jar.Cookies(proxiedHost)[0].Name).Should(Equal("foo")) Expect(jar.Cookies(proxiedHost)[0].Value).Should(Equal("bar")) }) }) g.Describe("BasicAuth", func() { var ts *httptest.Server g.Before(func() { ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/basic_auth" { auth_array := r.Header["Authorization"] if len(auth_array) > 0 { auth := strings.TrimSpace(auth_array[0]) w.WriteHeader(200) fmt.Fprint(w, auth) } else { w.WriteHeader(401) fmt.Fprint(w, "private") } } })) }) g.After(func() { ts.Close() }) g.It("Should support basic http authorization", func() { res, err := Request{ Uri: ts.URL + "/basic_auth", BasicAuthUsername: "username", BasicAuthPassword: "password", }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(res.StatusCode).Should(Equal(200)) expectedStr := "Basic " + base64.StdEncoding.EncodeToString([]byte("username:password")) Expect(str).Should(Equal(expectedStr)) }) g.It("Should fail when basic http authorization is required and not provided", func() { res, err := Request{ Uri: ts.URL + "/basic_auth", }.Do() Expect(err).Should(BeNil()) str, _ := res.Body.ToString() Expect(res.StatusCode).Should(Equal(401)) Expect(str).Should(Equal("private")) }) }) }) } func Test_paramParse(t *testing.T) { type Form struct { A string B string c string } type AnnotedForm struct { Foo string `url:"foo_bar"` Baz string `url:"bad,omitempty"` Norf string `url:"norf,omitempty"` Qux string `url:"-"` } type EmbedForm struct { AnnotedForm `url:",squash"` Form `url:",squash"` Corge string `url:"corge"` } g := Goblin(t) RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) var form = Form{} var aform = AnnotedForm{} var eform = EmbedForm{} var values = url.Values{} const result = "a=1&b=2" g.Describe("QueryString ParamParse", func() { g.Before(func() { form.A = "1" form.B = "2" form.c = "3" aform.Foo = "xyz" aform.Norf = "abc" aform.Qux = "def" eform.Form = form eform.AnnotedForm = aform eform.Corge = "xxx" values.Add("a", "1") values.Add("b", "2") }) g.It("Should accept struct and ignores unexported field", func() { str, err := paramParse(form) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) g.It("Should accept struct and use the field annotations", func() { str, err := paramParse(aform) Expect(err).Should(BeNil()) Expect(str).Should(Equal("foo_bar=xyz&norf=abc")) }) g.It("Should accept pointer of struct", func() { str, err := paramParse(&form) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) g.It("Should accept recursive pointer of struct", func() { f := &form ff := &f str, err := paramParse(ff) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) g.It("Should accept embedded struct", func() { str, err := paramParse(eform) Expect(err).Should(BeNil()) Expect(str).Should(Equal("a=1&b=2&corge=xxx&foo_bar=xyz&norf=abc")) }) g.It("Should accept interface{} which forcely converted by struct", func() { str, err := paramParse(interface{}(&form)) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) g.It("Should accept url.Values", func() { str, err := paramParse(values) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) g.It("Should accept &url.Values", func() { str, err := paramParse(&values) Expect(err).Should(BeNil()) Expect(str).Should(Equal(result)) }) }) }