github.com/rumhocker/blockbook@v0.3.2/fiat/fiat_rates_test.go (about)

     1  // +build unittest
     2  
     3  package fiat
     4  
     5  import (
     6  	"blockbook/bchain"
     7  	"blockbook/bchain/coins/btc"
     8  	"blockbook/common"
     9  	"blockbook/db"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"os"
    16  	"testing"
    17  	"time"
    18  
    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 := ioutil.TempDir("", "testdb")
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	d, err := db.NewRocksDB(tmp, 100000, -1, parser, nil)
    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(dateParam string) (string, error) {
    70  	var filename string
    71  	if dateParam == "current" {
    72  		filename = "fiat/mock_data/current.json"
    73  	} else {
    74  		filename = "fiat/mock_data/" + dateParam + ".json"
    75  	}
    76  	mockFile, err := os.Open(filename)
    77  	if err != nil {
    78  		glog.Errorf("Cannot open file %v", filename)
    79  		return "", err
    80  	}
    81  	b, err := ioutil.ReadAll(mockFile)
    82  	if err != nil {
    83  		glog.Errorf("Cannot read file %v", filename)
    84  		return "", err
    85  	}
    86  	return string(b), nil
    87  }
    88  
    89  func TestFiatRates(t *testing.T) {
    90  	d, _, tmp := setupRocksDB(t, &testBitcoinParser{
    91  		BitcoinParser: bitcoinTestnetParser(),
    92  	})
    93  	defer closeAndDestroyRocksDB(t, d, tmp)
    94  
    95  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    96  		var err error
    97  		var mockData string
    98  
    99  		if r.URL.Path == "/ping" {
   100  			w.WriteHeader(200)
   101  		} else if r.URL.Path == "/coins/bitcoin/history" {
   102  			date := r.URL.Query()["date"][0]
   103  			mockData, err = getFiatRatesMockData(date) // get stub rates by date
   104  		} else if r.URL.Path == "/coins/bitcoin" {
   105  			mockData, err = getFiatRatesMockData("current") // get "latest" stub rates
   106  		} else {
   107  			t.Errorf("Unknown URL path: %v", r.URL.Path)
   108  		}
   109  
   110  		if err != nil {
   111  			t.Errorf("Error loading stub data: %v", err)
   112  		}
   113  		fmt.Fprintln(w, mockData)
   114  	}))
   115  	defer mockServer.Close()
   116  
   117  	// real CoinGecko API
   118  	//configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"bitcoin\", \"periodSeconds\": 60}"}`
   119  
   120  	// mocked CoinGecko API
   121  	configJSON := `{"fiat_rates": "coingecko", "fiat_rates_params": "{\"url\": \"` + mockServer.URL + `\", \"coin\": \"bitcoin\", \"periodSeconds\": 60}"}`
   122  
   123  	type fiatRatesConfig struct {
   124  		FiatRates       string `json:"fiat_rates"`
   125  		FiatRatesParams string `json:"fiat_rates_params"`
   126  	}
   127  
   128  	var config fiatRatesConfig
   129  	err := json.Unmarshal([]byte(configJSON), &config)
   130  	if err != nil {
   131  		t.Errorf("Error parsing config: %v", err)
   132  	}
   133  
   134  	if config.FiatRates == "" || config.FiatRatesParams == "" {
   135  		t.Errorf("Error parsing FiatRates config - empty parameter")
   136  		return
   137  	}
   138  	testStartTime := time.Date(2019, 11, 22, 16, 0, 0, 0, time.UTC)
   139  	fiatRates, err := NewFiatRatesDownloader(d, config.FiatRates, config.FiatRatesParams, &testStartTime, nil)
   140  	if err != nil {
   141  		t.Errorf("FiatRates init error: %v\n", err)
   142  	}
   143  	if config.FiatRates == "coingecko" {
   144  		timestamp, err := fiatRates.findEarliestMarketData()
   145  		if err != nil {
   146  			t.Errorf("Error looking up earliest market data: %v", err)
   147  			return
   148  		}
   149  		earliestTimestamp, _ := time.Parse(db.FiatRatesTimeFormat, "20130429000000")
   150  		if *timestamp != earliestTimestamp {
   151  			t.Errorf("Incorrect earliest available timestamp found. Wanted: %v, got: %v", earliestTimestamp, timestamp)
   152  			return
   153  		}
   154  
   155  		// After verifying that findEarliestMarketData works correctly,
   156  		// set the earliest available timestamp to 2 days ago for easier testing
   157  		*timestamp = fiatRates.startTime.Add(time.Duration(-24*2) * time.Hour)
   158  
   159  		err = fiatRates.syncHistorical(timestamp)
   160  		if err != nil {
   161  			t.Errorf("RatesDownloader syncHistorical error: %v", err)
   162  			return
   163  		}
   164  		ticker, err := fiatRates.downloader.getTicker(fiatRates.startTime)
   165  		if err != nil {
   166  			// Do not exit on GET error, log it, wait and try again
   167  			glog.Errorf("Sync GetData error: %v", err)
   168  			return
   169  		}
   170  		err = fiatRates.db.FiatRatesStoreTicker(ticker)
   171  		if err != nil {
   172  			glog.Errorf("Sync StoreTicker error %v", err)
   173  			return
   174  		}
   175  	}
   176  }