// Copyright (c) 2015-2021 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 <= 4 {
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-retry-error-recover":
w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
if atomic.LoadInt32(&attempt) == 0 {
w.WriteHeader(http.StatusTooManyRequests)
_, _ = w.Write([]byte(`{ "message": "too many" }`))
} else {
_, _ = w.Write([]byte(`{ "message": "hello" }`))
}
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, "application/json")
}
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(``))
_, _ = w.Write([]byte(`bad_requestUnable to read user info`))
return
}
if user.Username == "testuser" && user.Password == "testpass" {
_, _ = w.Write([]byte(``))
_, _ = w.Write([]byte(`successlogin successful`))
} else if user.Username == "testuser" && user.Password == "invalidxml" {
_, _ = w.Write([]byte(``))
_, _ = w.Write([]byte(`successlogin successful`))
} else {
w.Header().Set("Www-Authenticate", "Protected Realm")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(``))
_, _ = w.Write([]byte(`unauthorizedInvalid credentials`))
}
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, "application/json")
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 == "/login-json-html" {
w.Header().Set(hdrContentTypeKey, "text/html")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`Test JSON request with HTML response