//go:build unittest package server import ( "encoding/json" "io/ioutil" "net/http" "net/http/httptest" "net/url" "os" "strconv" "strings" "testing" "time" "github.com/golang/glog" "github.com/gorilla/websocket" "github.com/linxGnu/grocksdb" "github.com/martinboehm/btcutil/chaincfg" gosocketio "github.com/martinboehm/golang-socketio" "github.com/martinboehm/golang-socketio/transport" "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/bchain/coins/btc" "github.com/trezor/blockbook/common" "github.com/trezor/blockbook/db" "github.com/trezor/blockbook/tests/dbtestdata" ) func TestMain(m *testing.M) { // set the current directory to blockbook root so that ./static/ works if err := os.Chdir(".."); err != nil { glog.Fatal("Chdir error:", err) } c := m.Run() chaincfg.ResetParams() os.Exit(c) } func setupRocksDB(parser bchain.BlockChainParser, chain bchain.BlockChain, t *testing.T) (*db.RocksDB, *common.InternalState, string) { tmp, err := ioutil.TempDir("", "testdb") if err != nil { t.Fatal(err) } d, err := db.NewRocksDB(tmp, 100000, -1, parser, nil) if err != nil { t.Fatal(err) } is, err := d.LoadInternalState("fakecoin") if err != nil { t.Fatal(err) } d.SetInternalState(is) // there are 2 simulated block, of height bestBlockHeight-1 and bestBlockHeight bestHeight, err := chain.GetBestBlockHeight() if err != nil { t.Fatal(err) } block1, err := chain.GetBlock("", bestHeight-1) if err != nil { t.Fatal(err) } // setup internal state BlockTimes for i := uint32(0); i < block1.Height; i++ { is.BlockTimes = append(is.BlockTimes, 0) } // import data if err := d.ConnectBlock(block1); err != nil { t.Fatal(err) } block2, err := chain.GetBlock("", bestHeight) if err != nil { t.Fatal(err) } if err := d.ConnectBlock(block2); err != nil { t.Fatal(err) } is.FinishedSync(block2.Height) if parser.GetChainType() == bchain.ChainEthereumType { if err := initTestFiatRatesEthereumType(d); err != nil { t.Fatal(err) } if err := initEthereumTypeDB(d); err != nil { t.Fatal(err) } } else { if err := initTestFiatRates(d); err != nil { t.Fatal(err) } } return d, is, tmp } var metrics *common.Metrics func setupPublicHTTPServer(parser bchain.BlockChainParser, chain bchain.BlockChain, t *testing.T) (*PublicServer, string) { d, is, path := setupRocksDB(parser, chain, t) // setup internal state and match BestHeight to test data is.Coin = "Fakecoin" is.CoinLabel = "Fake Coin" is.CoinShortcut = "FAKE" var err error // metrics can be setup only once if metrics == nil { metrics, err = common.GetMetrics("Fakecoin") if err != nil { glog.Fatal("metrics: ", err) } } mempool, err := chain.CreateMempool(chain) if err != nil { glog.Fatal("mempool: ", err) } // caching is switched off because test transactions do not have hex data txCache, err := db.NewTxCache(d, chain, metrics, is, false) if err != nil { glog.Fatal("txCache: ", err) } // s.Run is never called, binding can be to any port s, err := NewPublicServer("localhost:12345", "", d, chain, mempool, txCache, "", metrics, is, false, false) if err != nil { t.Fatal(err) } return s, path } func closeAndDestroyPublicServer(t *testing.T, s *PublicServer, dbpath string) { // destroy db if err := s.db.Close(); err != nil { t.Fatal(err) } os.RemoveAll(dbpath) } func newGetRequest(u string) *http.Request { r, err := http.NewRequest("GET", u, nil) if err != nil { glog.Fatal(err) } return r } func newPostFormRequest(u string, formdata ...string) *http.Request { form := url.Values{} for i := 0; i < len(formdata)-1; i += 2 { form.Add(formdata[i], formdata[i+1]) } r, err := http.NewRequest("POST", u, strings.NewReader(form.Encode())) if err != nil { glog.Fatal(err) } r.Header.Add("Content-Type", "application/x-www-form-urlencoded") return r } func newPostRequest(u string, body string) *http.Request { r, err := http.NewRequest("POST", u, strings.NewReader(body)) if err != nil { glog.Fatal(err) } r.Header.Add("Content-Type", "application/octet-stream") return r } func insertFiatRate(date string, rates map[string]float32, tokenRates map[string]float32, d *db.RocksDB) error { convertedDate, err := db.FiatRatesConvertDate(date) if err != nil { return err } ticker := &db.CurrencyRatesTicker{ Timestamp: *convertedDate, Rates: rates, TokenRates: tokenRates, } wb := grocksdb.NewWriteBatch() defer wb.Destroy() if err := d.FiatRatesStoreTicker(wb, ticker); err != nil { return err } return d.WriteBatch(wb) } // initTestFiatRates initializes test data for /api/v2/tickers endpoint func initTestFiatRates(d *db.RocksDB) error { if err := insertFiatRate("20180320020000", map[string]float32{ "usd": 2000.0, "eur": 1300.0, }, nil, d); err != nil { return err } if err := insertFiatRate("20180320030000", map[string]float32{ "usd": 2001.0, "eur": 1301.0, }, nil, d); err != nil { return err } if err := insertFiatRate("20180320040000", map[string]float32{ "usd": 2002.0, "eur": 1302.0, }, nil, d); err != nil { return err } if err := insertFiatRate("20180321055521", map[string]float32{ "usd": 2003.0, "eur": 1303.0, }, nil, d); err != nil { return err } if err := insertFiatRate("20191121140000", map[string]float32{ "usd": 7814.5, "eur": 7100.0, }, nil, d); err != nil { return err } return insertFiatRate("20191121143015", map[string]float32{ "usd": 7914.5, "eur": 7134.1, }, nil, d) } type httpTests struct { name string r *http.Request status int contentType string body []string } func performHttpTests(tests []httpTests, t *testing.T, ts *httptest.Server) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, err := http.DefaultClient.Do(tt.r) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != tt.status { t.Errorf("StatusCode = %v, want %v", resp.StatusCode, tt.status) } if resp.Header["Content-Type"][0] != tt.contentType { t.Errorf("Content-Type = %v, want %v", resp.Header["Content-Type"][0], tt.contentType) } bb, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } b := string(bb) for _, c := range tt.body { if !strings.Contains(b, c) { t.Errorf("got\n%v\nwant to contain %v", b, c) break } } }) } } func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { tests := []httpTests{ { name: "explorerTx", r: newGetRequest(ts.URL + "/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ `Fake Coin Explorer`, `