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 }