github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/fiat/fiat_rates_test.go (about)

     1  //go:build unittest
     2  
     3  package fiat
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"reflect"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/cryptohub-digital/blockbook/bchain"
    16  	"github.com/cryptohub-digital/blockbook/bchain/coins/btc"
    17  	"github.com/cryptohub-digital/blockbook/common"
    18  	"github.com/cryptohub-digital/blockbook/db"
    19  	"github.com/golang/glog"
    20  	"github.com/martinboehm/btcutil/chaincfg"
    21  )
    22  
    23  func TestMain(m *testing.M) {
    24  	// set the current directory to blockbook root so that ./static/ works
    25  	if err := os.Chdir(".."); err != nil {
    26  		glog.Fatal("Chdir error:", err)
    27  	}
    28  	c := m.Run()
    29  	chaincfg.ResetParams()
    30  	os.Exit(c)
    31  }
    32  
    33  func setupRocksDB(t *testing.T, parser bchain.BlockChainParser) (*db.RocksDB, *common.InternalState, string) {
    34  	tmp, err := os.MkdirTemp("", "testdb")
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	d, err := db.NewRocksDB(tmp, 100000, -1, parser, nil, false)
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	is, err := d.LoadInternalState("fakecoin")
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	d.SetInternalState(is)
    47  	return d, is, tmp
    48  }
    49  
    50  func closeAndDestroyRocksDB(t *testing.T, db *db.RocksDB, dbpath string) {
    51  	// destroy db
    52  	if err := db.Close(); err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	os.RemoveAll(dbpath)
    56  }
    57  
    58  type testBitcoinParser struct {
    59  	*btc.BitcoinParser
    60  }
    61  
    62  func bitcoinTestnetParser() *btc.BitcoinParser {
    63  	return btc.NewBitcoinParser(
    64  		btc.GetChainParams("test"),
    65  		&btc.Configuration{BlockAddressesToKeep: 1})
    66  }
    67  
    68  // getFiatRatesMockData reads a stub JSON response from a file and returns its content as string
    69  func getFiatRatesMockData(name string) (string, error) {
    70  	var filename string
    71  	filename = "fiat/mock_data/" + name + ".json"
    72  	mockFile, err := os.Open(filename)
    73  	if err != nil {
    74  		glog.Errorf("Cannot open file %v", filename)
    75  		return "", err
    76  	}
    77  	b, err := io.ReadAll(mockFile)
    78  	if err != nil {
    79  		glog.Errorf("Cannot read file %v", filename)
    80  		return "", err
    81  	}
    82  	return string(b), nil
    83  }
    84  
    85  func TestFiatRates(t *testing.T) {
    86  	d, _, tmp := setupRocksDB(t, &testBitcoinParser{
    87  		BitcoinParser: bitcoinTestnetParser(),
    88  	})
    89  	defer closeAndDestroyRocksDB(t, d, tmp)
    90  
    91  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    92  		var err error
    93  		var mockData string
    94  
    95  		if r.URL.Path == "/ping" {
    96  			w.WriteHeader(200)
    97  		} else if r.URL.Path == "/coins/list" {
    98  			mockData, err = getFiatRatesMockData("coinlist")
    99  		} else if r.URL.Path == "/simple/supported_vs_currencies" {
   100  			mockData, err = getFiatRatesMockData("vs_currencies")
   101  		} else if r.URL.Path == "/simple/price" {
   102  			if r.URL.Query().Get("ids") == "ethereum" {
   103  				mockData, err = getFiatRatesMockData("simpleprice_base")
   104  			} else {
   105  				mockData, err = getFiatRatesMockData("simpleprice_tokens")
   106  			}
   107  		} else if r.URL.Path == "/coins/ethereum/market_chart" {
   108  			vsCurrency := r.URL.Query().Get("vs_currency")
   109  			if vsCurrency == "usd" {
   110  				days := r.URL.Query().Get("days")
   111  				if days == "max" {
   112  					mockData, err = getFiatRatesMockData("market_chart_eth_usd_max")
   113  				} else {
   114  					mockData, err = getFiatRatesMockData("market_chart_eth_usd_1")
   115  				}
   116  			} else {
   117  				mockData, err = getFiatRatesMockData("market_chart_eth_other")
   118  			}
   119  		} else if r.URL.Path == "/coins/vendit/market_chart" || r.URL.Path == "/coins/ethereum-cash-token/market_chart" {
   120  			mockData, err = getFiatRatesMockData("market_chart_token_other")
   121  		} else {
   122  			t.Fatalf("Unknown URL path: %v", r.URL.Path)
   123  		}
   124  
   125  		if err != nil {
   126  			t.Fatalf("Error loading stub data: %v", err)
   127  		}
   128  		fmt.Fprintln(w, mockData)
   129  	}))
   130  	defer mockServer.Close()
   131  
   132  	// mocked CoinGecko API
   133  	configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"` + mockServer.URL + `\", \"coin\": \"ethereum\",\"platformIdentifier\":\"ethereum\",\"platformVsCurrency\": \"eth\",\"periodSeconds\": 60}"}`
   134  
   135  	fiatRates, err := NewFiatRates(d, []byte(configJSON), nil, nil)
   136  	if err != nil {
   137  		t.Fatalf("FiatRates init error: %v", err)
   138  	}
   139  
   140  	// get current tickers
   141  	currentTickers, err := fiatRates.downloader.CurrentTickers()
   142  	if err != nil {
   143  		t.Fatalf("Error in CurrentTickers: %v", err)
   144  		return
   145  	}
   146  	if currentTickers == nil {
   147  		t.Fatalf("CurrentTickers returned nil value")
   148  		return
   149  	}
   150  
   151  	wantCurrentTickers := common.CurrencyRatesTicker{
   152  		Rates: map[string]float32{
   153  			"aed": 8447.1,
   154  			"ars": 268901,
   155  			"aud": 3314.36,
   156  			"btc": 0.07531005,
   157  			"eth": 1,
   158  			"eur": 2182.99,
   159  			"ltc": 29.097696,
   160  			"usd": 2299.72,
   161  		},
   162  		TokenRates: map[string]float32{
   163  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 5.58195e-07,
   164  			"0x906710835d1ae85275eb770f06873340ca54274b": 1.39852e-10,
   165  		},
   166  		Timestamp: currentTickers.Timestamp,
   167  	}
   168  	if !reflect.DeepEqual(currentTickers, &wantCurrentTickers) {
   169  		t.Fatalf("CurrentTickers() = %v, want %v", *currentTickers, wantCurrentTickers)
   170  	}
   171  
   172  	ticker, err := fiatRates.db.FiatRatesFindLastTicker("usd", "")
   173  	if err != nil {
   174  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   175  	}
   176  	if ticker != nil {
   177  		t.Fatalf("FiatRatesFindLastTicker found unexpected data")
   178  	}
   179  
   180  	// update historical tickers for the first time
   181  	err = fiatRates.downloader.UpdateHistoricalTickers()
   182  	if err != nil {
   183  		t.Fatalf("UpdateHistoricalTickers 1st pass failed with error: %v", err)
   184  	}
   185  	err = fiatRates.downloader.UpdateHistoricalTokenTickers()
   186  	if err != nil {
   187  		t.Fatalf("UpdateHistoricalTokenTickers 1st pass failed with error: %v", err)
   188  	}
   189  
   190  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "")
   191  	if err != nil || ticker == nil {
   192  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   193  	}
   194  	wantTicker := common.CurrencyRatesTicker{
   195  		Rates: map[string]float32{
   196  			"aed": 241272.48,
   197  			"ars": 241272.48,
   198  			"aud": 241272.48,
   199  			"btc": 241272.48,
   200  			"eth": 241272.48,
   201  			"eur": 241272.48,
   202  			"ltc": 241272.48,
   203  			"usd": 1794.5397,
   204  		},
   205  		TokenRates: map[string]float32{
   206  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.161734e+07,
   207  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.161734e+07,
   208  		},
   209  		Timestamp: time.Unix(1654732800, 0).UTC(),
   210  	}
   211  	if !reflect.DeepEqual(ticker, &wantTicker) {
   212  		t.Fatalf("UpdateHistoricalTickers(usd) 1st pass = %v, want %v", *ticker, wantTicker)
   213  	}
   214  
   215  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "")
   216  	if err != nil || ticker == nil {
   217  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   218  	}
   219  	wantTicker = common.CurrencyRatesTicker{
   220  		Rates: map[string]float32{
   221  			"aed": 240402.97,
   222  			"ars": 240402.97,
   223  			"aud": 240402.97,
   224  			"btc": 240402.97,
   225  			"eth": 240402.97,
   226  			"eur": 240402.97,
   227  			"ltc": 240402.97,
   228  		},
   229  		TokenRates: map[string]float32{
   230  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07,
   231  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07,
   232  		},
   233  		Timestamp: time.Unix(1654819200, 0).UTC(),
   234  	}
   235  	if !reflect.DeepEqual(ticker, &wantTicker) {
   236  		t.Fatalf("UpdateHistoricalTickers(eur) 1st pass = %v, want %v", *ticker, wantTicker)
   237  	}
   238  
   239  	// update historical tickers for the second time
   240  	err = fiatRates.downloader.UpdateHistoricalTickers()
   241  	if err != nil {
   242  		t.Fatalf("UpdateHistoricalTickers 2nd pass failed with error: %v", err)
   243  	}
   244  	err = fiatRates.downloader.UpdateHistoricalTokenTickers()
   245  	if err != nil {
   246  		t.Fatalf("UpdateHistoricalTokenTickers 2nd pass failed with error: %v", err)
   247  	}
   248  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "")
   249  	if err != nil || ticker == nil {
   250  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   251  	}
   252  	wantTicker = common.CurrencyRatesTicker{
   253  		Rates: map[string]float32{
   254  			"aed": 240402.97,
   255  			"ars": 240402.97,
   256  			"aud": 240402.97,
   257  			"btc": 240402.97,
   258  			"eth": 240402.97,
   259  			"eur": 240402.97,
   260  			"ltc": 240402.97,
   261  			"usd": 1788.4183,
   262  		},
   263  		TokenRates: map[string]float32{
   264  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07,
   265  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07,
   266  		},
   267  		Timestamp: time.Unix(1654819200, 0).UTC(),
   268  	}
   269  	if !reflect.DeepEqual(ticker, &wantTicker) {
   270  		t.Fatalf("UpdateHistoricalTickers(usd) 2nd pass = %v, want %v", *ticker, wantTicker)
   271  	}
   272  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "")
   273  	if err != nil || ticker == nil {
   274  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   275  	}
   276  	if !reflect.DeepEqual(ticker, &wantTicker) {
   277  		t.Fatalf("UpdateHistoricalTickers(eur) 2nd pass = %v, want %v", *ticker, wantTicker)
   278  	}
   279  }