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 }