Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New endoint /accounts/raw_address #154

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
396 changes: 152 additions & 244 deletions api/account/api.go

Large diffs are not rendered by default.

367 changes: 247 additions & 120 deletions api/account/api_test.go
Original file line number Diff line number Diff line change
@@ -1,151 +1,278 @@
package account
package account_test

import (
"context"
"fmt"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/emerishq/demeris-api-server/api/account"
"github.com/emerishq/demeris-api-server/lib/fflag"
"github.com/emerishq/demeris-backend-models/tracelistener"
"github.com/emerishq/emeris-utils/logging"
"github.com/gin-gonic/gin"
gomock "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_balanceRespForBalance(t *testing.T) {
tests := []struct {
name string
rawBalance tracelistener.BalanceRow
vd map[string]bool
dt denomTraceFunc
want Balance
}{
{
"verified IBC balance returns verified balance",
tracelistener.BalanceRow{
Address: "address",
Amount: "42",
Denom: "ibc/hash",
},
map[string]bool{
"uatom": true,
type mocks struct {
app *MockApp
}

func newAccountAPI(t *testing.T, setup func(mocks)) *account.AccountAPI {
ctrl := gomock.NewController(t)
m := mocks{
app: NewMockApp(ctrl),
}
if setup != nil {
setup(m)
}
return account.New(m.app)
}

func TestGetAccounts(t *testing.T) {
var (
ctx = context.Background()
resp = account.AccountsResponse{
Balances: []account.Balance{
{Address: "adr1", BaseDenom: "denom1", Amount: "42"},
{Address: "adr2", BaseDenom: "denom2", Amount: "42"},
},
func(_ context.Context, _, hash string) (tracelistener.IBCDenomTraceRow, error) {
return tracelistener.IBCDenomTraceRow{
Path: "path",
BaseDenom: "uatom",
Hash: "hash",
}, nil
StakingBalances: []account.StakingBalance{
{ValidatorAddress: "adr1", ChainName: "chain1", Amount: "42"},
{ValidatorAddress: "adr2", ChainName: "chain1", Amount: "42"},
},
Balance{
Address: "address",
BaseDenom: "uatom",
Verified: true,
Amount: "42",
OnChain: "",
Ibc: IbcInfo{
Path: "path",
Hash: "hash",
UnbondingDelegations: []account.UnbondingDelegation{
{
ChainName: "chain1",
ValidatorAddress: "vadr1",
Entries: []tracelistener.UnbondingDelegationEntry{
{
Balance: "42",
InitialBalance: "1",
CreationHeight: 1024,
CompletionTime: "time",
},
},
},
},
},
}
respJSON, _ = json.Marshal(resp)
)
tests := []struct {
name string
expectedStatusCode int
expectedBody string
expectedError string
setup func(mocks)
}{
{
"non-verified IBC balance returns non-verified balance",
tracelistener.BalanceRow{
Address: "address",
Amount: "42",
Denom: "ibc/hash",
},
map[string]bool{
"uatom": false,
},
func(_ context.Context, _, hash string) (tracelistener.IBCDenomTraceRow, error) {
return tracelistener.IBCDenomTraceRow{
Path: "path",
BaseDenom: "uatom",
Hash: "hash",
}, nil
},
Balance{
Address: "address",
BaseDenom: "uatom",
Verified: false,
Amount: "42",
OnChain: "",
Ibc: IbcInfo{
Path: "path",
Hash: "hash",
},
name: "ok",
expectedStatusCode: http.StatusOK,
expectedBody: string(respJSON),

setup: func(m mocks) {
adrs := []string{"adr1", "adr2"}
m.app.EXPECT().DeriveRawAddress(gomock.Any(), "xxx").Return(adrs, nil)
m.app.EXPECT().Balances(gomock.Any(), adrs).Return(resp.Balances, nil)
m.app.EXPECT().StakingBalances(gomock.Any(), adrs).Return(resp.StakingBalances, nil)
m.app.EXPECT().UnbondingDelegations(gomock.Any(), adrs).Return(resp.UnbondingDelegations, nil)
},
},
{
"error on denomtrace function returns unverified balance",
tracelistener.BalanceRow{
Address: "address",
Amount: "42",
Denom: "ibc/hash",
},
map[string]bool{
"uatom": true,
},
func(_ context.Context, _, hash string) (tracelistener.IBCDenomTraceRow, error) {
return tracelistener.IBCDenomTraceRow{}, fmt.Errorf("error")
},
Balance{
Address: "address",
BaseDenom: "ibc/hash",
Verified: false,
Amount: "42",
OnChain: "",
Ibc: IbcInfo{
Hash: "hash",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "rawaddress", Value: "xxx"}}
c.Request, _ = http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
// add logger or else it fails
logger := logging.New(logging.LoggingConfig{})
c.Set(logging.LoggerKey, logger)
ac := newAccountAPI(t, tt.setup)

ac.GetAccounts(c)

assert.Equal(tt.expectedStatusCode, w.Code)
if tt.expectedError != "" {
require.Len(c.Errors, 1, "expected one error but got %d", len(c.Errors))
require.EqualError(c.Errors[0], tt.expectedError)
return
}
require.Empty(c.Errors)
assert.JSONEq(tt.expectedBody, w.Body.String())
})
}
}

func TestGetBalancesPerAddress(t *testing.T) {
var (
ctx = context.Background()
resp = account.BalancesResponse{
Balances: []account.Balance{
{Address: "adr1", BaseDenom: "denom1", Amount: "42"},
{Address: "adr2", BaseDenom: "denom2", Amount: "42"},
},
},
}
respJSON, _ = json.Marshal(resp)
)
tests := []struct {
name string
expectedStatusCode int
expectedBody string
expectedError string
setup func(mocks)
}{
{
"verified non-ibc token returns verified balance",
tracelistener.BalanceRow{
Address: "address",
Amount: "42",
Denom: "denom",
},
map[string]bool{
"denom": true,
},
func(_ context.Context, _, hash string) (tracelistener.IBCDenomTraceRow, error) {
return tracelistener.IBCDenomTraceRow{}, nil
},
Balance{
Address: "address",
BaseDenom: "denom",
Verified: true,
Amount: "42",
name: "ok",
expectedStatusCode: http.StatusOK,
expectedBody: string(respJSON),

setup: func(m mocks) {
m.app.EXPECT().Balances(ctx, []string{"adr1"}).Return(resp.Balances, nil)
},
},
{
"non-verified non-ibc token returns non-verified balance",
tracelistener.BalanceRow{
Address: "address",
Amount: "42",
Denom: "denom",
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "address", Value: "adr1"}}
c.Request, _ = http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
// add logger or else it fails
logger := logging.New(logging.LoggingConfig{})
c.Set(logging.LoggerKey, logger)
ac := newAccountAPI(t, tt.setup)

ac.GetBalancesByAddress(c)

assert.Equal(tt.expectedStatusCode, w.Code)
if tt.expectedError != "" {
require.Len(c.Errors, 1, "expected one error but got %d", len(c.Errors))
require.EqualError(c.Errors[0], tt.expectedError)
return
}
require.Empty(c.Errors)
assert.JSONEq(tt.expectedBody, w.Body.String())
})
}

}

func TestGetDelegationsPerAddress(t *testing.T) {
var (
ctx = context.Background()
resp = account.StakingBalancesResponse{
StakingBalances: []account.StakingBalance{
{ValidatorAddress: "adr1", ChainName: "chain1", Amount: "42"},
{ValidatorAddress: "adr2", ChainName: "chain1", Amount: "42"},
},
map[string]bool{
"denom": false,
}
respJSON, _ = json.Marshal(resp)
)
tests := []struct {
name string
expectedStatusCode int
expectedBody string
expectedError string
setup func(mocks)
}{
{
name: "ok",
expectedStatusCode: http.StatusOK,
expectedBody: string(respJSON),

setup: func(m mocks) {
m.app.EXPECT().StakingBalances(ctx, []string{"adr1"}).Return(resp.StakingBalances, nil)
},
func(_ context.Context, _, hash string) (tracelistener.IBCDenomTraceRow, error) {
return tracelistener.IBCDenomTraceRow{}, nil
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)
assert := assert.New(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "address", Value: "adr1"}}
c.Request, _ = http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
// FIXME remove when #801 is done
fflag.EnableGlobal(account.FixSlashedDelegations)
// add logger or else it fails
logger := logging.New(logging.LoggingConfig{})
c.Set(logging.LoggerKey, logger)
ac := newAccountAPI(t, tt.setup)

ac.GetDelegationsByAddress(c)

assert.Equal(tt.expectedStatusCode, w.Code)
if tt.expectedError != "" {
require.Len(c.Errors, 1, "expected one error but got %d", len(c.Errors))
require.EqualError(c.Errors[0], tt.expectedError)
return
}
require.Empty(c.Errors)
assert.JSONEq(tt.expectedBody, w.Body.String())
})
}
}

func TestGetUnbondingDelegationsPerAddress(t *testing.T) {
var (
ctx = context.Background()
resp = account.UnbondingDelegationsResponse{
UnbondingDelegations: []account.UnbondingDelegation{
{ValidatorAddress: "adr1", ChainName: "chain1"},
{ValidatorAddress: "adr2", ChainName: "chain1"},
},
Balance{
Address: "address",
BaseDenom: "denom",
Verified: false,
Amount: "42",
}
respJSON, _ = json.Marshal(resp)
)
tests := []struct {
name string
expectedStatusCode int
expectedBody string
expectedError string
setup func(mocks)
}{
{
name: "ok",
expectedStatusCode: http.StatusOK,
expectedBody: string(respJSON),

setup: func(m mocks) {
m.app.EXPECT().UnbondingDelegations(ctx, []string{"adr1"}).Return(resp.UnbondingDelegations, nil)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t,
tt.want,
balanceRespForBalance(context.Background(), tt.rawBalance, tt.vd, tt.dt),
)
require := require.New(t)
assert := assert.New(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Params = gin.Params{{Key: "address", Value: "adr1"}}
c.Request, _ = http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
// add logger or else it fails
logger := logging.New(logging.LoggingConfig{})
c.Set(logging.LoggerKey, logger)
ac := newAccountAPI(t, tt.setup)

ac.GetUnbondingDelegationsByAddress(c)

assert.Equal(tt.expectedStatusCode, w.Code)
if tt.expectedError != "" {
require.Len(c.Errors, 1, "expected one error but got %d", len(c.Errors))
require.EqualError(c.Errors[0], tt.expectedError)
return
}
require.Empty(c.Errors)
assert.JSONEq(tt.expectedBody, w.Body.String())
})
}
}
Loading