123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638 |
- // Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
- // resty source code and usage is governed by a MIT style
- // license that can be found in the LICENSE file.
- package resty
- import (
- "compress/gzip"
- "encoding/base64"
- "encoding/json"
- "encoding/xml"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "reflect"
- "strconv"
- "strings"
- "sync/atomic"
- "testing"
- "time"
- )
- //‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
- // Testing Unexported methods
- //___________________________________
- func getTestDataPath() string {
- pwd, _ := os.Getwd()
- return filepath.Join(pwd, ".testdata")
- }
- func createGetServer(t *testing.T) *httptest.Server {
- var attempt int32
- var sequence int32
- var lastRequest time.Time
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- if r.Method == MethodGet {
- switch r.URL.Path {
- case "/":
- _, _ = w.Write([]byte("TestGet: text response"))
- case "/no-content":
- _, _ = w.Write([]byte(""))
- case "/json":
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{"TestGet": "JSON response"}`))
- case "/json-invalid":
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte("TestGet: Invalid JSON"))
- case "/long-text":
- _, _ = w.Write([]byte("TestGet: text response with size > 30"))
- case "/long-json":
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte(`{"TestGet": "JSON response with size > 30"}`))
- case "/mypage":
- w.WriteHeader(http.StatusBadRequest)
- case "/mypage2":
- _, _ = w.Write([]byte("TestGet: text response from mypage2"))
- case "/set-retrycount-test":
- attp := atomic.AddInt32(&attempt, 1)
- if attp <= 3 {
- time.Sleep(time.Second * 6)
- }
- _, _ = w.Write([]byte("TestClientRetry page"))
- case "/set-retrywaittime-test":
- // Returns time.Duration since last request here
- // or 0 for the very first request
- if atomic.LoadInt32(&attempt) == 0 {
- lastRequest = time.Now()
- _, _ = fmt.Fprint(w, "0")
- } else {
- now := time.Now()
- sinceLastRequest := now.Sub(lastRequest)
- lastRequest = now
- _, _ = fmt.Fprintf(w, "%d", uint64(sinceLastRequest))
- }
- atomic.AddInt32(&attempt, 1)
- case "/set-timeout-test-with-sequence":
- seq := atomic.AddInt32(&sequence, 1)
- time.Sleep(time.Second * 2)
- _, _ = fmt.Fprintf(w, "%d", seq)
- case "/set-timeout-test":
- time.Sleep(time.Second * 6)
- _, _ = w.Write([]byte("TestClientTimeout page"))
- case "/my-image.png":
- fileBytes, _ := ioutil.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
- w.Header().Set("Content-Type", "image/png")
- w.Header().Set("Content-Length", strconv.Itoa(len(fileBytes)))
- _, _ = w.Write(fileBytes)
- case "/get-method-payload-test":
- body, err := ioutil.ReadAll(r.Body)
- if err != nil {
- t.Errorf("Error: could not read get body: %s", err.Error())
- }
- _, _ = w.Write(body)
- case "/host-header":
- _, _ = w.Write([]byte(r.Host))
- }
- switch {
- case strings.HasPrefix(r.URL.Path, "/v1/users/sample@sample.com/100002"):
- if strings.HasSuffix(r.URL.Path, "details") {
- _, _ = w.Write([]byte("TestGetPathParams: text response: " + r.URL.String()))
- } else {
- _, _ = w.Write([]byte("TestPathParamURLInput: text response: " + r.URL.String()))
- }
- }
- }
- })
- return ts
- }
- func handleLoginEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/login" {
- user := &User{}
- // JSON
- if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
- jd := json.NewDecoder(r.Body)
- err := jd.Decode(user)
- if r.URL.Query().Get("ct") == "problem" {
- w.Header().Set(hdrContentTypeKey, "application/problem+json; charset=utf-8")
- } else if r.URL.Query().Get("ct") == "rpc" {
- w.Header().Set(hdrContentTypeKey, "application/json-rpc")
- } else {
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- }
- if err != nil {
- t.Logf("Error: %#v", err)
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
- return
- }
- if user.Username == "testuser" && user.Password == "testpass" {
- _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
- } else if user.Username == "testuser" && user.Password == "invalidjson" {
- _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful", }`))
- } else {
- w.WriteHeader(http.StatusUnauthorized)
- _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
- }
- return
- }
- // XML
- if IsXMLType(r.Header.Get(hdrContentTypeKey)) {
- xd := xml.NewDecoder(r.Body)
- err := xd.Decode(user)
- w.Header().Set(hdrContentTypeKey, "application/xml")
- if err != nil {
- t.Logf("Error: %v", err)
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
- _, _ = w.Write([]byte(`<AuthError><Id>bad_request</Id><Message>Unable to read user info</Message></AuthError>`))
- return
- }
- if user.Username == "testuser" && user.Password == "testpass" {
- _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
- _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</Message></AuthSuccess>`))
- } else if user.Username == "testuser" && user.Password == "invalidxml" {
- _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
- _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</AuthSuccess>`))
- } else {
- w.Header().Set("Www-Authenticate", "Protected Realm")
- w.WriteHeader(http.StatusUnauthorized)
- _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
- _, _ = w.Write([]byte(`<AuthError><Id>unauthorized</Id><Message>Invalid credentials</Message></AuthError>`))
- }
- return
- }
- }
- }
- func handleUsersEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/users" {
- // JSON
- if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
- var users []ExampleUser
- jd := json.NewDecoder(r.Body)
- err := jd.Decode(&users)
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- if err != nil {
- t.Logf("Error: %v", err)
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
- return
- }
- // logic check, since we are excepting to reach 3 records
- if len(users) != 3 {
- t.Log("Error: Excepted count of 3 records")
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`))
- return
- }
- eu := users[2]
- if eu.FirstName == "firstname3" && eu.ZipCode == "10003" {
- w.WriteHeader(http.StatusAccepted)
- _, _ = w.Write([]byte(`{ "message": "Accepted" }`))
- }
- return
- }
- }
- }
- func createPostServer(t *testing.T) *httptest.Server {
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- t.Logf("RawQuery: %v", r.URL.RawQuery)
- t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
- if r.Method == MethodPost {
- handleLoginEndpoint(t, w, r)
- handleUsersEndpoint(t, w, r)
- if r.URL.Path == "/usersmap" {
- // JSON
- if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
- if r.URL.Query().Get("status") == "500" {
- body, err := ioutil.ReadAll(r.Body)
- if err != nil {
- t.Errorf("Error: could not read post body: %s", err.Error())
- }
- t.Logf("Got query param: status=500 so we're returning the post body as response and a 500 status code. body: %s", string(body))
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- w.WriteHeader(http.StatusInternalServerError)
- _, _ = w.Write(body)
- return
- }
- var users []map[string]interface{}
- jd := json.NewDecoder(r.Body)
- err := jd.Decode(&users)
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- if err != nil {
- t.Logf("Error: %v", err)
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
- return
- }
- // logic check, since we are excepting to reach 1 map records
- if len(users) != 1 {
- t.Log("Error: Excepted count of 1 map records")
- w.WriteHeader(http.StatusBadRequest)
- _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`))
- return
- }
- w.WriteHeader(http.StatusAccepted)
- _, _ = w.Write([]byte(`{ "message": "Accepted" }`))
- return
- }
- }
- }
- })
- return ts
- }
- func createFormPostServer(t *testing.T) *httptest.Server {
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
- if r.Method == MethodPost {
- _ = r.ParseMultipartForm(10e6)
- if r.URL.Path == "/profile" {
- t.Logf("FirstName: %v", r.FormValue("first_name"))
- t.Logf("LastName: %v", r.FormValue("last_name"))
- t.Logf("City: %v", r.FormValue("city"))
- t.Logf("Zip Code: %v", r.FormValue("zip_code"))
- _, _ = w.Write([]byte("Success"))
- return
- } else if r.URL.Path == "/search" {
- formEncodedData := r.Form.Encode()
- t.Logf("Received Form Encoded values: %v", formEncodedData)
- assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=pencil"))
- assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=glass"))
- _, _ = w.Write([]byte("Success"))
- return
- } else if r.URL.Path == "/upload" {
- t.Logf("FirstName: %v", r.FormValue("first_name"))
- t.Logf("LastName: %v", r.FormValue("last_name"))
- targetPath := filepath.Join(getTestDataPath(), "upload")
- _ = os.MkdirAll(targetPath, 0700)
- for _, fhdrs := range r.MultipartForm.File {
- for _, hdr := range fhdrs {
- t.Logf("Name: %v", hdr.Filename)
- t.Logf("Header: %v", hdr.Header)
- dotPos := strings.LastIndex(hdr.Filename, ".")
- fname := fmt.Sprintf("%s-%v%s", hdr.Filename[:dotPos], time.Now().Unix(), hdr.Filename[dotPos:])
- t.Logf("Write name: %v", fname)
- infile, _ := hdr.Open()
- f, err := os.OpenFile(filepath.Join(targetPath, fname), os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- t.Logf("Error: %v", err)
- return
- }
- defer func() {
- _ = f.Close()
- }()
- _, _ = io.Copy(f, infile)
- _, _ = w.Write([]byte(fmt.Sprintf("File: %v, uploaded as: %v\n", hdr.Filename, fname)))
- }
- }
- return
- }
- }
- })
- return ts
- }
- func createFilePostServer(t *testing.T) *httptest.Server {
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
- if r.Method != MethodPost {
- t.Log("createPostServer:: Not a Post request")
- w.WriteHeader(http.StatusBadRequest)
- fmt.Fprint(w, http.StatusText(http.StatusBadRequest))
- return
- }
- targetPath := filepath.Join(getTestDataPath(), "upload-large")
- _ = os.MkdirAll(targetPath, 0700)
- defer cleanupFiles(targetPath)
- switch r.URL.Path {
- case "/upload":
- f, err := os.OpenFile(filepath.Join(targetPath, "large-file.png"),
- os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- t.Logf("Error: %v", err)
- return
- }
- defer func() {
- _ = f.Close()
- }()
- size, _ := io.Copy(f, r.Body)
- fmt.Fprintf(w, "File Uploaded successfully, file size: %v", size)
- }
- })
- return ts
- }
- func createAuthServer(t *testing.T) *httptest.Server {
- ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
- if r.Method == MethodGet {
- if r.URL.Path == "/profile" {
- // 004DDB79-6801-4587-B976-F093E6AC44FF
- auth := r.Header.Get("Authorization")
- t.Logf("Bearer Auth: %v", auth)
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- if !strings.HasPrefix(auth, "Bearer ") {
- w.Header().Set("Www-Authenticate", "Protected Realm")
- w.WriteHeader(http.StatusUnauthorized)
- _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
- return
- }
- if auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF" || auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF-Request" {
- _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
- }
- }
- return
- }
- if r.Method == MethodPost {
- if r.URL.Path == "/login" {
- auth := r.Header.Get("Authorization")
- t.Logf("Basic Auth: %v", auth)
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- password, err := base64.StdEncoding.DecodeString(auth[6:])
- if err != nil || string(password) != "myuser:basicauth" {
- w.Header().Set("Www-Authenticate", "Protected Realm")
- w.WriteHeader(http.StatusUnauthorized)
- _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
- return
- }
- _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
- }
- return
- }
- }))
- return ts
- }
- func createGenServer(t *testing.T) *httptest.Server {
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- if r.Method == MethodGet {
- if r.URL.Path == "/json-no-set" {
- // Set empty header value for testing, since Go server sets to
- // text/plain; charset=utf-8
- w.Header().Set(hdrContentTypeKey, "")
- _, _ = w.Write([]byte(`{"response":"json response no content type set"}`))
- } else if r.URL.Path == "/gzip-test" {
- w.Header().Set(hdrContentTypeKey, plainTextType)
- w.Header().Set(hdrContentEncodingKey, "gzip")
- zw := gzip.NewWriter(w)
- zw.Write([]byte("This is Gzip response testing"))
- zw.Close()
- } else if r.URL.Path == "/gzip-test-gziped-empty-body" {
- w.Header().Set(hdrContentTypeKey, plainTextType)
- w.Header().Set(hdrContentEncodingKey, "gzip")
- zw := gzip.NewWriter(w)
- // write gziped empty body
- zw.Write([]byte(""))
- zw.Close()
- } else if r.URL.Path == "/gzip-test-no-gziped-body" {
- w.Header().Set(hdrContentTypeKey, plainTextType)
- w.Header().Set(hdrContentEncodingKey, "gzip")
- // don't write body
- }
- return
- }
- if r.Method == MethodPut {
- if r.URL.Path == "/plaintext" {
- _, _ = w.Write([]byte("TestPut: plain text response"))
- } else if r.URL.Path == "/json" {
- w.Header().Set(hdrContentTypeKey, jsonContentType)
- _, _ = w.Write([]byte(`{"response":"json response"}`))
- } else if r.URL.Path == "/xml" {
- w.Header().Set(hdrContentTypeKey, "application/xml")
- _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><Response>XML response</Response>`))
- }
- return
- }
- if r.Method == MethodOptions && r.URL.Path == "/options" {
- w.Header().Set("Access-Control-Allow-Origin", "localhost")
- w.Header().Set("Access-Control-Allow-Methods", "PUT, PATCH")
- w.Header().Set("Access-Control-Expose-Headers", "x-go-resty-id")
- w.WriteHeader(http.StatusOK)
- return
- }
- if r.Method == MethodPatch && r.URL.Path == "/patch" {
- w.WriteHeader(http.StatusOK)
- return
- }
- if r.Method == "REPORT" && r.URL.Path == "/report" {
- body, _ := ioutil.ReadAll(r.Body)
- if len(body) == 0 {
- w.WriteHeader(http.StatusOK)
- }
- return
- }
- })
- return ts
- }
- func createRedirectServer(t *testing.T) *httptest.Server {
- ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
- t.Logf("Method: %v", r.Method)
- t.Logf("Path: %v", r.URL.Path)
- if r.Method == MethodGet {
- if strings.HasPrefix(r.URL.Path, "/redirect-host-check-") {
- cntStr := strings.SplitAfter(r.URL.Path, "-")[3]
- cnt, _ := strconv.Atoi(cntStr)
- if cnt != 7 { // Testing hard stop via logical
- if cnt >= 5 {
- http.Redirect(w, r, "http://httpbin.org/get", http.StatusTemporaryRedirect)
- } else {
- http.Redirect(w, r, fmt.Sprintf("/redirect-host-check-%d", cnt+1), http.StatusTemporaryRedirect)
- }
- }
- } else if strings.HasPrefix(r.URL.Path, "/redirect-") {
- cntStr := strings.SplitAfter(r.URL.Path, "-")[1]
- cnt, _ := strconv.Atoi(cntStr)
- http.Redirect(w, r, fmt.Sprintf("/redirect-%d", cnt+1), http.StatusTemporaryRedirect)
- }
- }
- })
- return ts
- }
- func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
- return httptest.NewServer(http.HandlerFunc(fn))
- }
- func dc() *Client {
- DefaultClient = New()
- DefaultClient.SetLogger(ioutil.Discard)
- return DefaultClient
- }
- func dcr() *Request {
- return dc().R()
- }
- func dclr() *Request {
- c := dc()
- c.SetDebug(true)
- c.SetLogger(ioutil.Discard)
- return c.R()
- }
- func assertNil(t *testing.T, v interface{}) {
- if !isNil(v) {
- t.Errorf("[%v] was expected to be nil", v)
- }
- }
- func assertNotNil(t *testing.T, v interface{}) {
- if isNil(v) {
- t.Errorf("[%v] was expected to be non-nil", v)
- }
- }
- func assertType(t *testing.T, typ, v interface{}) {
- if reflect.DeepEqual(reflect.TypeOf(typ), reflect.TypeOf(v)) {
- t.Errorf("Expected type %t, got %t", typ, v)
- }
- }
- func assertError(t *testing.T, err error) {
- if err != nil {
- t.Errorf("Error occurred [%v]", err)
- }
- }
- func assertEqual(t *testing.T, e, g interface{}) (r bool) {
- if !equal(e, g) {
- t.Errorf("Expected [%v], got [%v]", e, g)
- }
- return
- }
- func assertNotEqual(t *testing.T, e, g interface{}) (r bool) {
- if equal(e, g) {
- t.Errorf("Expected [%v], got [%v]", e, g)
- } else {
- r = true
- }
- return
- }
- func equal(expected, got interface{}) bool {
- return reflect.DeepEqual(expected, got)
- }
- func isNil(v interface{}) bool {
- if v == nil {
- return true
- }
- rv := reflect.ValueOf(v)
- kind := rv.Kind()
- if kind >= reflect.Chan && kind <= reflect.Slice && rv.IsNil() {
- return true
- }
- return false
- }
- func logResponse(t *testing.T, resp *Response) {
- t.Logf("Response Status: %v", resp.Status())
- t.Logf("Response Time: %v", resp.Time())
- t.Logf("Response Headers: %v", resp.Header())
- t.Logf("Response Cookies: %v", resp.Cookies())
- t.Logf("Response Body: %v", resp)
- }
- func cleanupFiles(files ...string) {
- pwd, _ := os.Getwd()
- for _, f := range files {
- if filepath.IsAbs(f) {
- _ = os.RemoveAll(f)
- } else {
- _ = os.RemoveAll(filepath.Join(pwd, f))
- }
- }
- }
|