github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/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/golang/glog"
    16  	"github.com/martinboehm/btcutil/chaincfg"
    17  	"github.com/trezor/blockbook/bchain"
    18  	"github.com/trezor/blockbook/bchain/coins/btc"
    19  	"github.com/trezor/blockbook/common"
    20  	"github.com/trezor/blockbook/db"
    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, config *common.Config) (*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(config)
    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  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    87  		var err error
    88  		var mockData string
    89  
    90  		if r.URL.Path == "/ping" {
    91  			w.WriteHeader(200)
    92  		} else if r.URL.Path == "/coins/list" {
    93  			mockData, err = getFiatRatesMockData("coinlist")
    94  		} else if r.URL.Path == "/simple/supported_vs_currencies" {
    95  			mockData, err = getFiatRatesMockData("vs_currencies")
    96  		} else if r.URL.Path == "/simple/price" {
    97  			if r.URL.Query().Get("ids") == "ethereum" {
    98  				mockData, err = getFiatRatesMockData("simpleprice_base")
    99  			} else {
   100  				mockData, err = getFiatRatesMockData("simpleprice_tokens")
   101  			}
   102  		} else if r.URL.Path == "/coins/ethereum/market_chart" {
   103  			vsCurrency := r.URL.Query().Get("vs_currency")
   104  			if vsCurrency == "usd" {
   105  				days := r.URL.Query().Get("days")
   106  				if days == "max" {
   107  					mockData, err = getFiatRatesMockData("market_chart_eth_usd_max")
   108  				} else {
   109  					mockData, err = getFiatRatesMockData("market_chart_eth_usd_1")
   110  				}
   111  			} else {
   112  				mockData, err = getFiatRatesMockData("market_chart_eth_other")
   113  			}
   114  		} else if r.URL.Path == "/coins/vendit/market_chart" || r.URL.Path == "/coins/ethereum-cash-token/market_chart" {
   115  			mockData, err = getFiatRatesMockData("market_chart_token_other")
   116  		} else {
   117  			t.Fatalf("Unknown URL path: %v", r.URL.Path)
   118  		}
   119  
   120  		if err != nil {
   121  			t.Fatalf("Error loading stub data: %v", err)
   122  		}
   123  		fmt.Fprintln(w, mockData)
   124  	}))
   125  	defer mockServer.Close()
   126  
   127  	// config with mocked CoinGecko API
   128  	config := common.Config{
   129  		CoinName:        "fakecoin",
   130  		FiatRates:       "coingecko",
   131  		FiatRatesParams: `{"url": "` + mockServer.URL + `", "coin": "ethereum","platformIdentifier": "ethereum","platformVsCurrency": "eth","periodSeconds": 60}`,
   132  	}
   133  
   134  	d, _, tmp := setupRocksDB(t, &testBitcoinParser{
   135  		BitcoinParser: bitcoinTestnetParser(),
   136  	}, &config)
   137  	defer closeAndDestroyRocksDB(t, d, tmp)
   138  
   139  	fiatRates, err := NewFiatRates(d, &config, nil, nil)
   140  	if err != nil {
   141  		t.Fatalf("FiatRates init error: %v", err)
   142  	}
   143  
   144  	// get current tickers
   145  	currentTickers, err := fiatRates.downloader.CurrentTickers()
   146  	if err != nil {
   147  		t.Fatalf("Error in CurrentTickers: %v", err)
   148  		return
   149  	}
   150  	if currentTickers == nil {
   151  		t.Fatalf("CurrentTickers returned nil value")
   152  		return
   153  	}
   154  
   155  	wantCurrentTickers := common.CurrencyRatesTicker{
   156  		Rates: map[string]float32{
   157  			"aed": 8447.1,
   158  			"ars": 268901,
   159  			"aud": 3314.36,
   160  			"btc": 0.07531005,
   161  			"eth": 1,
   162  			"eur": 2182.99,
   163  			"ltc": 29.097696,
   164  			"usd": 2299.72,
   165  		},
   166  		TokenRates: map[string]float32{
   167  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 5.58195e-07,
   168  			"0x906710835d1ae85275eb770f06873340ca54274b": 1.39852e-10,
   169  		},
   170  		Timestamp: currentTickers.Timestamp,
   171  	}
   172  	if !reflect.DeepEqual(currentTickers, &wantCurrentTickers) {
   173  		t.Fatalf("CurrentTickers() = %v, want %v", *currentTickers, wantCurrentTickers)
   174  	}
   175  
   176  	ticker, err := fiatRates.db.FiatRatesFindLastTicker("usd", "")
   177  	if err != nil {
   178  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   179  	}
   180  	if ticker != nil {
   181  		t.Fatalf("FiatRatesFindLastTicker found unexpected data")
   182  	}
   183  
   184  	// update historical tickers for the first time
   185  	err = fiatRates.downloader.UpdateHistoricalTickers()
   186  	if err != nil {
   187  		t.Fatalf("UpdateHistoricalTickers 1st pass failed with error: %v", err)
   188  	}
   189  	err = fiatRates.downloader.UpdateHistoricalTokenTickers()
   190  	if err != nil {
   191  		t.Fatalf("UpdateHistoricalTokenTickers 1st pass failed with error: %v", err)
   192  	}
   193  
   194  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "")
   195  	if err != nil || ticker == nil {
   196  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   197  	}
   198  	wantTicker := common.CurrencyRatesTicker{
   199  		Rates: map[string]float32{
   200  			"aed": 241272.48,
   201  			"ars": 241272.48,
   202  			"aud": 241272.48,
   203  			"btc": 241272.48,
   204  			"eth": 241272.48,
   205  			"eur": 241272.48,
   206  			"ltc": 241272.48,
   207  			"usd": 1794.5397,
   208  		},
   209  		TokenRates: map[string]float32{
   210  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.161734e+07,
   211  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.161734e+07,
   212  		},
   213  		Timestamp: time.Unix(1654732800, 0).UTC(),
   214  	}
   215  	if !reflect.DeepEqual(ticker, &wantTicker) {
   216  		t.Fatalf("UpdateHistoricalTickers(usd) 1st pass = %v, want %v", *ticker, wantTicker)
   217  	}
   218  
   219  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "")
   220  	if err != nil || ticker == nil {
   221  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   222  	}
   223  	wantTicker = common.CurrencyRatesTicker{
   224  		Rates: map[string]float32{
   225  			"aed": 240402.97,
   226  			"ars": 240402.97,
   227  			"aud": 240402.97,
   228  			"btc": 240402.97,
   229  			"eth": 240402.97,
   230  			"eur": 240402.97,
   231  			"ltc": 240402.97,
   232  		},
   233  		TokenRates: map[string]float32{
   234  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07,
   235  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07,
   236  		},
   237  		Timestamp: time.Unix(1654819200, 0).UTC(),
   238  	}
   239  	if !reflect.DeepEqual(ticker, &wantTicker) {
   240  		t.Fatalf("UpdateHistoricalTickers(eur) 1st pass = %v, want %v", *ticker, wantTicker)
   241  	}
   242  
   243  	// update historical tickers for the second time
   244  	err = fiatRates.downloader.UpdateHistoricalTickers()
   245  	if err != nil {
   246  		t.Fatalf("UpdateHistoricalTickers 2nd pass failed with error: %v", err)
   247  	}
   248  	err = fiatRates.downloader.UpdateHistoricalTokenTickers()
   249  	if err != nil {
   250  		t.Fatalf("UpdateHistoricalTokenTickers 2nd pass failed with error: %v", err)
   251  	}
   252  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("usd", "")
   253  	if err != nil || ticker == nil {
   254  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   255  	}
   256  	wantTicker = common.CurrencyRatesTicker{
   257  		Rates: map[string]float32{
   258  			"aed": 240402.97,
   259  			"ars": 240402.97,
   260  			"aud": 240402.97,
   261  			"btc": 240402.97,
   262  			"eth": 240402.97,
   263  			"eur": 240402.97,
   264  			"ltc": 240402.97,
   265  			"usd": 1788.4183,
   266  		},
   267  		TokenRates: map[string]float32{
   268  			"0x5e9997684d061269564f94e5d11ba6ce6fa9528c": 4.1464476e+07,
   269  			"0x906710835d1ae85275eb770f06873340ca54274b": 4.1464476e+07,
   270  		},
   271  		Timestamp: time.Unix(1654819200, 0).UTC(),
   272  	}
   273  	if !reflect.DeepEqual(ticker, &wantTicker) {
   274  		t.Fatalf("UpdateHistoricalTickers(usd) 2nd pass = %v, want %v", *ticker, wantTicker)
   275  	}
   276  	ticker, err = fiatRates.db.FiatRatesFindLastTicker("eur", "")
   277  	if err != nil || ticker == nil {
   278  		t.Fatalf("FiatRatesFindLastTicker failed with error: %v", err)
   279  	}
   280  	if !reflect.DeepEqual(ticker, &wantTicker) {
   281  		t.Fatalf("UpdateHistoricalTickers(eur) 2nd pass = %v, want %v", *ticker, wantTicker)
   282  	}
   283  }