status-go/services/wallet/market/market_test.go

277 lines
7.9 KiB
Go

package market
import (
"errors"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/ethereum/go-ethereum/event"
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/services/wallet/thirdparty"
mock_thirdparty "github.com/status-im/status-go/services/wallet/thirdparty/mock"
)
type MockPriceProvider struct {
mock_thirdparty.MockMarketDataProvider
mockPrices map[string]map[string]float64
}
func NewMockPriceProvider(ctrl *gomock.Controller) *MockPriceProvider {
return &MockPriceProvider{
MockMarketDataProvider: *mock_thirdparty.NewMockMarketDataProvider(ctrl),
}
}
func (mpp *MockPriceProvider) setMockPrices(prices map[string]map[string]float64) {
mpp.mockPrices = prices
}
func (mpp *MockPriceProvider) ID() string {
return "MockPriceProvider"
}
func (mpp *MockPriceProvider) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
res := make(map[string]map[string]float64)
for _, symbol := range symbols {
res[symbol] = make(map[string]float64)
for _, currency := range currencies {
res[symbol][currency] = mpp.mockPrices[symbol][currency]
}
}
return res, nil
}
type MockPriceProviderWithError struct {
MockPriceProvider
}
func (mpp *MockPriceProviderWithError) FetchPrices(symbols []string, currencies []string) (map[string]map[string]float64, error) {
return nil, errors.New("error")
}
func setupMarketManager(t *testing.T, providers []thirdparty.MarketDataProvider) *Manager {
return NewManager(providers, &event.Feed{})
}
var mockPrices = map[string]map[string]float64{
"BTC": {
"USD": 1.23456,
"EUR": 2.34567,
"DAI": 3.45678,
"ARS": 9.87654,
},
"ETH": {
"USD": 4.56789,
"EUR": 5.67891,
"DAI": 6.78912,
"ARS": 8.76543,
},
"SNT": {
"USD": 7.654,
"EUR": 6.0,
"DAI": 1455.12,
"ARS": 0.0,
},
}
func TestPrice(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
priceProvider := NewMockPriceProvider(ctrl)
priceProvider.setMockPrices(mockPrices)
manager := setupMarketManager(t, []thirdparty.MarketDataProvider{priceProvider, priceProvider})
{
rst := manager.GetCachedPrices()
require.Empty(t, rst)
}
{
symbols := []string{"BTC", "ETH"}
currencies := []string{"USD", "EUR"}
rst, err := manager.FetchPrices(symbols, currencies)
require.NoError(t, err)
for _, symbol := range symbols {
for _, currency := range currencies {
require.Equal(t, rst[symbol][currency], mockPrices[symbol][currency])
}
}
}
{
symbols := []string{"BTC", "ETH", "SNT"}
currencies := []string{"USD", "EUR", "DAI", "ARS"}
rst, err := manager.FetchPrices(symbols, currencies)
require.NoError(t, err)
for _, symbol := range symbols {
for _, currency := range currencies {
require.Equal(t, rst[symbol][currency], mockPrices[symbol][currency])
}
}
}
cache := manager.GetCachedPrices()
for symbol, pricePerCurrency := range mockPrices {
for currency, price := range pricePerCurrency {
require.Equal(t, price, cache[symbol][currency].Price)
}
}
}
func TestFetchPriceErrorFirstProvider(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
priceProvider := NewMockPriceProvider(ctrl)
priceProvider.setMockPrices(mockPrices)
priceProviderWithError := &MockPriceProviderWithError{}
symbols := []string{"BTC", "ETH"}
currencies := []string{"USD", "EUR"}
manager := setupMarketManager(t, []thirdparty.MarketDataProvider{priceProviderWithError, priceProvider})
rst, err := manager.FetchPrices(symbols, currencies)
require.NoError(t, err)
for _, symbol := range symbols {
for _, currency := range currencies {
require.Equal(t, rst[symbol][currency], mockPrices[symbol][currency])
}
}
}
func TestFetchTokenMarketValues(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
symbols := []string{"BTC", "ETH"}
currency := "EUR"
expectedMarketValues := map[string]thirdparty.TokenMarketValues{
"BTC": {
MKTCAP: 1000000000,
HIGHDAY: 1.23456,
LOWDAY: 1.00000,
CHANGEPCTHOUR: 0.1,
CHANGEPCTDAY: 0.2,
CHANGEPCT24HOUR: 0.3,
CHANGE24HOUR: 0.4,
},
"ETH": {
MKTCAP: 2000000000,
HIGHDAY: 4.56789,
LOWDAY: 4.00000,
CHANGEPCTHOUR: 0.5,
CHANGEPCTDAY: 0.6,
CHANGEPCT24HOUR: 0.7,
CHANGE24HOUR: 0.8,
},
}
// Can't use fake provider, because the key {receiver, method} will be different, no match
provider := mock_thirdparty.NewMockMarketDataProvider(ctrl)
provider.EXPECT().ID().Return("MockPriceProvider").AnyTimes()
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(expectedMarketValues, nil)
manager := setupMarketManager(t, []thirdparty.MarketDataProvider{provider})
marketValues, err := manager.FetchTokenMarketValues(symbols, currency)
require.NoError(t, err)
require.Equal(t, expectedMarketValues, marketValues)
// Test error
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(nil, errors.New("error"))
marketValues, err = manager.FetchTokenMarketValues(symbols, currency)
require.Error(t, err)
require.Nil(t, marketValues)
}
func TestCachedFetchTokenMarketValues(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
symbols := []string{"BTC", "ETH"}
currency := "EUR"
initialTokenMarketValues := map[string]thirdparty.TokenMarketValues{
"BTC": {
MKTCAP: 1000000000,
HIGHDAY: 1.23456,
LOWDAY: 1.00000,
CHANGEPCTHOUR: 0.1,
CHANGEPCTDAY: 0.2,
CHANGEPCT24HOUR: 0.3,
CHANGE24HOUR: 0.4,
},
"ETH": {
MKTCAP: 2000000000,
HIGHDAY: 4.56789,
LOWDAY: 4.00000,
CHANGEPCTHOUR: 0.5,
CHANGEPCTDAY: 0.6,
CHANGEPCT24HOUR: 0.7,
CHANGE24HOUR: 0.8,
},
}
updatedTokenMarketValues := map[string]thirdparty.TokenMarketValues{
"BTC": {
MKTCAP: 1000000000,
HIGHDAY: 2.23456,
LOWDAY: 1.00000,
CHANGEPCTHOUR: 0.1,
CHANGEPCTDAY: 0.2,
CHANGEPCT24HOUR: 0.3,
CHANGE24HOUR: 0.4,
},
"ETH": {
MKTCAP: 2000000000,
HIGHDAY: 5.56789,
LOWDAY: 4.00000,
CHANGEPCTHOUR: 0.5,
CHANGEPCTDAY: 0.6,
CHANGEPCT24HOUR: 0.7,
CHANGE24HOUR: 0.8,
},
}
provider := mock_thirdparty.NewMockMarketDataProvider(ctrl)
provider.EXPECT().ID().Return("MockMarketProvider").AnyTimes()
manager := setupMarketManager(t, []thirdparty.MarketDataProvider{provider})
ttl := 200 * time.Millisecond
cache := manager.MakeCacheForFetchTokenMarketValues(ttl)
cacheKey := GenerateCacheKeyForFetchTokenMarketValues(currency, symbols)
fresh := false
// Test: ensure errors are propagated
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(nil, errors.New("error"))
marketValues, err := cache.Get(cacheKey, fresh)
require.Error(t, err)
require.Nil(t, marketValues)
// Test: ensure token market values are retrieved
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(initialTokenMarketValues, nil)
marketValues, err = cache.Get(cacheKey, fresh)
require.NoError(t, err)
require.Equal(t, initialTokenMarketValues, marketValues)
// Test: ensure token market values are cached
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(updatedTokenMarketValues, nil).MaxTimes(0)
marketValues, err = cache.Get(cacheKey, fresh)
require.NoError(t, err)
require.Equal(t, initialTokenMarketValues, marketValues)
// Test: ensure token market values are updated when requesting fresh data
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(updatedTokenMarketValues, nil).Times(1)
fresh = true
marketValues, err = cache.Get(cacheKey, fresh)
require.NoError(t, err)
require.Equal(t, updatedTokenMarketValues, marketValues)
// Test: stale data is ignored and the cache is refreshed
time.Sleep(ttl + time.Millisecond)
provider.EXPECT().FetchTokenMarketValues(symbols, currency).Return(initialTokenMarketValues, nil).Times(1)
fresh = false
marketValues, err = cache.Get(cacheKey, fresh)
require.NoError(t, err)
require.Equal(t, initialTokenMarketValues, marketValues)
}