github.com/0chain/gosdk@v1.17.11/core/tokenrate/tokenrate_test.go (about)

     1  package tokenrate
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"net/http"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/mock"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/0chain/gosdk/core/resty"
    19  	"github.com/0chain/gosdk/zboxcore/mocks"
    20  )
    21  
    22  func TestGetUSD(t *testing.T) {
    23  	mockProviderUrl := "coinProvider"
    24  	var mockClient = mocks.HttpClient{}
    25  	resty.CreateClient = func(t *http.Transport, timeout time.Duration) resty.Client {
    26  		return &mockClient
    27  	}
    28  
    29  	for _, tc := range []struct {
    30  		name          string
    31  		expectedErr   error
    32  		expectedValue float64
    33  		timeout       time.Duration
    34  		symbol        string
    35  		provider      string
    36  		setup         func(testcaseName, symbol, provider string)
    37  		response      func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error)
    38  	}{
    39  		{
    40  			name:        "ContextDeadlineExceeded",
    41  			expectedErr: context.DeadlineExceeded,
    42  			timeout:     1 * time.Microsecond,
    43  			symbol:      "ZCN",
    44  			provider:    "bancor",
    45  			setup: func(testCaseName, symbol, provider string) {
    46  				setupMockHttpResponse(&mockClient, provider, mockProviderUrl, "TestGetUSD", testCaseName, "GET", symbol, http.StatusOK, getProviderJsonResponse(t, provider))
    47  			},
    48  			response: getBancorResponse(),
    49  		},
    50  		{
    51  			name:          "TestBancorCorrectSymbol",
    52  			expectedErr:   nil,
    53  			expectedValue: 0.118837,
    54  			timeout:       10 * time.Second,
    55  			symbol:        "ZCN",
    56  			provider:      "bancor",
    57  			setup: func(testCaseName, symbol, provider string) {
    58  				setupMockHttpResponse(&mockClient, provider, mockProviderUrl, "TestGetUSD", testCaseName, "GET", symbol, http.StatusOK, getProviderJsonResponse(t, provider))
    59  			},
    60  			response: getBancorResponse(),
    61  		},
    62  		{
    63  			name:          "TestCoinmarketcapCorrectSymbol",
    64  			expectedErr:   nil,
    65  			expectedValue: 0.2209498683307504,
    66  			timeout:       10 * time.Second,
    67  			symbol:        "ZCN",
    68  			provider:      "coinmarketcap",
    69  			setup: func(testCaseName, symbol, provider string) {
    70  				setupMockHttpResponse(&mockClient, provider, mockProviderUrl, "TestGetUSD", testCaseName, "GET", symbol, http.StatusOK, getProviderJsonResponse(t, provider))
    71  			},
    72  			response: getCoinmarketcapResponse(),
    73  		},
    74  		{
    75  			name:          "TestCoingeckoCorrectSymbol",
    76  			expectedErr:   nil,
    77  			expectedValue: 0.218343,
    78  			timeout:       10 * time.Second,
    79  			symbol:        "0chain",
    80  			provider:      "coingecko",
    81  			setup: func(testCaseName, symbol, provider string) {
    82  				setupMockHttpResponse(&mockClient, provider, mockProviderUrl, "TestGetUSD", testCaseName, "GET", symbol, http.StatusOK, getProviderJsonResponse(t, provider))
    83  			},
    84  			response: getCoinGeckoResponse(),
    85  		},
    86  		{
    87  			name:        "TestCoinmarketcapWrongSymbol",
    88  			expectedErr: fmt.Errorf("429, failed to get coin data from provider coinmarketcap for symbol \"wrong\""),
    89  			timeout:     10 * time.Second,
    90  			symbol:      "wrong",
    91  			provider:    "coinmarketcap",
    92  			setup: func(testCaseName, symbol, provider string) {
    93  				setupMockHttpResponse(&mockClient, provider, mockProviderUrl, "TestGetUSD", testCaseName, "GET", symbol, http.StatusTooManyRequests, getProviderJsonResponse(t, provider))
    94  			},
    95  			response: getBancorResponse(),
    96  		},
    97  	} {
    98  		t.Run(tc.name, func(t *testing.T) {
    99  			if tc.setup != nil {
   100  				tc.setup(tc.name, tc.symbol, tc.provider)
   101  			}
   102  			var value float64
   103  			var err error
   104  			if tc.response != nil {
   105  				value, err = tc.response(tc.name, mockProviderUrl, tc.provider, tc.symbol, tc.timeout)
   106  			}
   107  
   108  			if tc.expectedErr != nil {
   109  				require.EqualError(t, err, tc.expectedErr.Error())
   110  			} else {
   111  				require.NoError(t, err)
   112  				require.Equal(t, value, tc.expectedValue)
   113  			}
   114  		})
   115  	}
   116  }
   117  
   118  func setupMockHttpResponse(
   119  	mockClient *mocks.HttpClient, provider, mockProviderUrl, funcName, testCaseName, httpMethod, symbol string,
   120  	statusCode int, body []byte) {
   121  	url := funcName + testCaseName + mockProviderUrl + provider
   122  	mockClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
   123  		return req.Method == httpMethod &&
   124  			strings.Contains(req.URL.String(), url) && (req.URL.Query().Get("symbol") == symbol)
   125  	})).Return(
   126  		&http.Response{
   127  			StatusCode: statusCode,
   128  			Body:       ioutil.NopCloser(bytes.NewReader(body)),
   129  		}, nil).Once()
   130  }
   131  
   132  func getBancorResponse() func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   133  	return func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   134  		var br bancorResponse
   135  		ctx, cancel := context.WithTimeout(context.TODO(), timeout)
   136  		go func() {
   137  			<-ctx.Done()
   138  			cancel()
   139  		}()
   140  
   141  		reqUrl := "TestGetUSD" + testCaseName + mockProviderURL + providerName + "?symbol=" + symbol
   142  		r := resty.New(resty.WithRetry(1))
   143  		r.DoGet(ctx, reqUrl).Then(func(req *http.Request, resp *http.Response, respBody []byte, cf context.CancelFunc, err error) error {
   144  			if err != nil {
   145  				return err
   146  			}
   147  			if resp.StatusCode != http.StatusOK {
   148  				return fmt.Errorf("%v, failed to get coin data from provider %v for symbol \"%v\"", resp.StatusCode, providerName, symbol)
   149  			}
   150  			err = json.Unmarshal(respBody, &br)
   151  			log.Printf("==Response: %v", br)
   152  			if err != nil {
   153  				return err
   154  			}
   155  			br.Raw = string(respBody)
   156  			return nil
   157  		})
   158  		errs := r.Wait()
   159  		if len(errs) != 0 {
   160  			return 0.0, errs[0]
   161  		}
   162  		return br.Data.Rate["usd"].Value, nil
   163  	}
   164  }
   165  
   166  func getCoinmarketcapResponse() func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   167  	return func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   168  		var cr coinmarketcapResponse
   169  		ctx, cancel := context.WithTimeout(context.TODO(), timeout)
   170  		go func() {
   171  			<-ctx.Done()
   172  			cancel()
   173  		}()
   174  
   175  		reqUrl := "TestGetUSD" + testCaseName + mockProviderURL + providerName + "?symbol=" + symbol
   176  		r := resty.New(resty.WithRetry(1))
   177  		r.DoGet(ctx, reqUrl).Then(func(req *http.Request, resp *http.Response, respBody []byte, cf context.CancelFunc, err error) error {
   178  			if err != nil {
   179  				return err
   180  			}
   181  			if resp.StatusCode != http.StatusOK {
   182  				return fmt.Errorf("failed to get coin data from provider %v for symbol \"%v\"", providerName, symbol)
   183  			}
   184  			err = json.Unmarshal(respBody, &cr)
   185  			if err != nil {
   186  				return err
   187  			}
   188  			cr.Raw = string(respBody)
   189  			return nil
   190  		})
   191  		errs := r.Wait()
   192  		if len(errs) != 0 {
   193  			return 0.0, errs[0]
   194  		}
   195  		if len(cr.Data[strings.ToUpper(symbol)]) == 0 {
   196  			return 0.0, fmt.Errorf("coinmarketcap: symbol \"%v\" is not provided on coinmarketcap apis", symbol)
   197  		}
   198  		val := cr.Data[strings.ToUpper(symbol)][0].Quote["USD"].Price
   199  		return val, nil
   200  	}
   201  }
   202  
   203  func getCoinGeckoResponse() func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   204  	return func(testCaseName, mockProviderURL, providerName, symbol string, timeout time.Duration) (float64, error) {
   205  		var cg coingeckoResponse
   206  		ctx, cancel := context.WithTimeout(context.TODO(), timeout)
   207  		go func() {
   208  			<-ctx.Done()
   209  			cancel()
   210  		}()
   211  
   212  		reqUrl := "TestGetUSD" + testCaseName + mockProviderURL + providerName + "?symbol=" + symbol
   213  		r := resty.New(resty.WithRetry(1))
   214  		r.DoGet(ctx, reqUrl).Then(func(req *http.Request, resp *http.Response, respBody []byte, cf context.CancelFunc, err error) error {
   215  			if err != nil {
   216  				return err
   217  			}
   218  			if resp.StatusCode != http.StatusOK {
   219  				return fmt.Errorf("failed to get coin data from provider %v for symbol \"%v\"", providerName, symbol)
   220  			}
   221  			err = json.Unmarshal(respBody, &cg)
   222  			if err != nil {
   223  				return err
   224  			}
   225  			cg.Raw = string(respBody)
   226  			return nil
   227  		})
   228  		errs := r.Wait()
   229  		if len(errs) != 0 {
   230  			return 0.0, errs[0]
   231  		}
   232  		return cg.MarketData.CurrentPrice["usd"], nil
   233  	}
   234  }
   235  
   236  func getProviderJsonResponse(t *testing.T, provider string) []byte {
   237  	data, err := ioutil.ReadFile("mockresponses/" + provider + ".json")
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	return data
   242  }