arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/api_test.go
api/api_test.goRaw
   1package api
   2
   3import (
   4	"context"
   5	"encoding/json"
   6	"errors"
   7	"fmt"
   8	"io"
   9	"io/ioutil"
  10	"math"
  11	"math/big"
  12	"net/http"
  13	"os"
  14	"sort"
  15	"strconv"
  16	"testing"
  17	"time"
  18
  19	ethCommon "github.com/ethereum/go-ethereum/common"
  20	swagger "github.com/getkin/kin-openapi/openapi3filter"
  21	"github.com/gin-gonic/gin"
  22	"github.com/hermeznetwork/hermez-node/common"
  23	"github.com/hermeznetwork/hermez-node/db"
  24	"github.com/hermeznetwork/hermez-node/db/historydb"
  25	"github.com/hermeznetwork/hermez-node/db/l2db"
  26	"github.com/hermeznetwork/hermez-node/db/statedb"
  27	"github.com/hermeznetwork/hermez-node/log"
  28	"github.com/hermeznetwork/hermez-node/test"
  29	"github.com/iden3/go-iden3-crypto/babyjub"
  30	"github.com/mitchellh/copystructure"
  31	"github.com/stretchr/testify/assert"
  32	"github.com/stretchr/testify/require"
  33)
  34
  35const apiPort = ":4010"
  36const apiURL = "http://localhost" + apiPort + "/"
  37
  38type testCommon struct {
  39	blocks   []common.Block
  40	tokens   []historydb.TokenRead
  41	batches  []common.Batch
  42	usrAddr  string
  43	usrBjj   string
  44	accs     []common.Account
  45	usrTxs   []historyTxAPI
  46	allTxs   []historyTxAPI
  47	exits    []exitAPI
  48	usrExits []exitAPI
  49	router   *swagger.Router
  50}
  51
  52// TxSortFields represents the fields needed to sort L1 and L2 transactions
  53type txSortFields struct {
  54	BatchNum *common.BatchNum
  55	Position int
  56}
  57
  58// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
  59type txSortFielder interface {
  60	SortFields() txSortFields
  61	L1() *common.L1Tx
  62	L2() *common.L2Tx
  63}
  64
  65// TxsSort array of TxSortFielder
  66type txsSort []txSortFielder
  67
  68func (t txsSort) Len() int      { return len(t) }
  69func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
  70func (t txsSort) Less(i, j int) bool {
  71	// i not forged yet
  72	isf := t[i].SortFields()
  73	jsf := t[j].SortFields()
  74	if isf.BatchNum == nil {
  75		if jsf.BatchNum != nil { // j is already forged
  76			return false
  77		}
  78		// Both aren't forged, is i in a smaller position?
  79		return isf.Position < jsf.Position
  80	}
  81	// i is forged
  82	if jsf.BatchNum == nil {
  83		return false // j is not forged
  84	}
  85	// Both are forged
  86	if *isf.BatchNum == *jsf.BatchNum {
  87		// At the same batch,  is i in a smaller position?
  88		return isf.Position < jsf.Position
  89	}
  90	// At different batches, is i in a smaller batch?
  91	return *isf.BatchNum < *jsf.BatchNum
  92}
  93
  94type wrappedL1 common.L1Tx
  95
  96// SortFields implements TxSortFielder
  97func (tx *wrappedL1) SortFields() txSortFields {
  98	return txSortFields{
  99		BatchNum: tx.BatchNum,
 100		Position: tx.Position,
 101	}
 102}
 103
 104// L1 implements TxSortFielder
 105func (tx *wrappedL1) L1() *common.L1Tx {
 106	l1tx := common.L1Tx(*tx)
 107	return &l1tx
 108}
 109
 110// L2 implements TxSortFielder
 111func (tx *wrappedL1) L2() *common.L2Tx { return nil }
 112
 113type wrappedL2 common.L2Tx
 114
 115// SortFields implements TxSortFielder
 116func (tx *wrappedL2) SortFields() txSortFields {
 117	return txSortFields{
 118		BatchNum: &tx.BatchNum,
 119		Position: tx.Position,
 120	}
 121}
 122
 123// L1 implements TxSortFielder
 124func (tx *wrappedL2) L1() *common.L1Tx { return nil }
 125
 126// L2 implements TxSortFielder
 127func (tx *wrappedL2) L2() *common.L2Tx {
 128	l2tx := common.L2Tx(*tx)
 129	return &l2tx
 130}
 131
 132var tc testCommon
 133
 134func TestMain(m *testing.M) {
 135	// Init swagger
 136	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
 137	// Init DBs
 138	// HistoryDB
 139	pass := os.Getenv("POSTGRES_PASS")
 140	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
 141	if err != nil {
 142		panic(err)
 143	}
 144	hdb := historydb.NewHistoryDB(database)
 145	err = hdb.Reorg(-1)
 146	if err != nil {
 147		panic(err)
 148	}
 149	// StateDB
 150	dir, err := ioutil.TempDir("", "tmpdb")
 151	if err != nil {
 152		panic(err)
 153	}
 154	sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
 155	if err != nil {
 156		panic(err)
 157	}
 158	// L2DB
 159	l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
 160	test.CleanL2DB(l2DB.DB())
 161
 162	// Init API
 163	api := gin.Default()
 164	if err := SetAPIEndpoints(
 165		true,
 166		true,
 167		api,
 168		hdb,
 169		sdb,
 170		l2DB,
 171	); err != nil {
 172		panic(err)
 173	}
 174	// Start server
 175	server := &http.Server{Addr: apiPort, Handler: api}
 176	go func() {
 177		if err := server.ListenAndServe(); err != nil &&
 178			err != http.ErrServerClosed {
 179			panic(err)
 180		}
 181	}()
 182
 183	// Populate DBs
 184	// Clean DB
 185	err = h.Reorg(0)
 186	if err != nil {
 187		panic(err)
 188	}
 189	// Gen blocks and add them to DB
 190	const nBlocks = 5
 191	blocks := test.GenBlocks(1, nBlocks+1)
 192	err = h.AddBlocks(blocks)
 193	if err != nil {
 194		panic(err)
 195	}
 196	// Gen tokens and add them to DB
 197	const nTokens = 10
 198	tokens := test.GenTokens(nTokens, blocks)
 199	err = h.AddTokens(tokens)
 200	if err != nil {
 201		panic(err)
 202	}
 203	// Set token value
 204	tokensUSD := []historydb.TokenRead{}
 205	for i, tkn := range tokens {
 206		token := historydb.TokenRead{
 207			TokenID:     tkn.TokenID,
 208			EthBlockNum: tkn.EthBlockNum,
 209			EthAddr:     tkn.EthAddr,
 210			Name:        tkn.Name,
 211			Symbol:      tkn.Symbol,
 212			Decimals:    tkn.Decimals,
 213		}
 214		// Set value of 50% of the tokens
 215		if i%2 != 0 {
 216			value := float64(i) * 1.234567
 217			now := time.Now().UTC()
 218			token.USD = &value
 219			token.USDUpdate = &now
 220			err = h.UpdateTokenValue(token.Symbol, value)
 221			if err != nil {
 222				panic(err)
 223			}
 224		}
 225		tokensUSD = append(tokensUSD, token)
 226	}
 227	// Gen batches and add them to DB
 228	const nBatches = 10
 229	batches := test.GenBatches(nBatches, blocks)
 230	err = h.AddBatches(batches)
 231	if err != nil {
 232		panic(err)
 233	}
 234	// Gen accounts and add them to DB
 235	const totalAccounts = 40
 236	const userAccounts = 4
 237	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
 238	privK := babyjub.NewRandPrivKey()
 239	usrBjj := privK.Public()
 240	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
 241	err = h.AddAccounts(accs)
 242	if err != nil {
 243		panic(err)
 244	}
 245	// Gen exits and add them to DB
 246	const totalExits = 40
 247	exits := test.GenExitTree(totalExits, batches, accs)
 248	err = h.AddExitTree(exits)
 249	if err != nil {
 250		panic(err)
 251	}
 252	// Gen L1Txs and add them to DB
 253	const totalL1Txs = 40
 254	const userL1Txs = 4
 255	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
 256	// Gen L2Txs and add them to DB
 257	const totalL2Txs = 20
 258	const userL2Txs = 4
 259	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
 260	// Order txs
 261	sortedTxs := []txSortFielder{}
 262	for i := 0; i < len(usrL1Txs); i++ {
 263		wL1 := wrappedL1(usrL1Txs[i])
 264		sortedTxs = append(sortedTxs, &wL1)
 265	}
 266	for i := 0; i < len(othrL1Txs); i++ {
 267		wL1 := wrappedL1(othrL1Txs[i])
 268		sortedTxs = append(sortedTxs, &wL1)
 269	}
 270	for i := 0; i < len(usrL2Txs); i++ {
 271		wL2 := wrappedL2(usrL2Txs[i])
 272		sortedTxs = append(sortedTxs, &wL2)
 273	}
 274	for i := 0; i < len(othrL2Txs); i++ {
 275		wL2 := wrappedL2(othrL2Txs[i])
 276		sortedTxs = append(sortedTxs, &wL2)
 277	}
 278	sort.Sort(txsSort(sortedTxs))
 279	// Add txs to DB and prepare them for test commons
 280	usrTxs := []historyTxAPI{}
 281	allTxs := []historyTxAPI{}
 282	getTimestamp := func(blockNum int64) time.Time {
 283		for i := 0; i < len(blocks); i++ {
 284			if blocks[i].EthBlockNum == blockNum {
 285				return blocks[i].Timestamp
 286			}
 287		}
 288		panic("timesamp not found")
 289	}
 290	getToken := func(id common.TokenID) historydb.TokenRead {
 291		for i := 0; i < len(tokensUSD); i++ {
 292			if tokensUSD[i].TokenID == id {
 293				return tokensUSD[i]
 294			}
 295		}
 296		panic("token not found")
 297	}
 298	getTokenByIdx := func(idx common.Idx) historydb.TokenRead {
 299		for _, acc := range accs {
 300			if idx == acc.Idx {
 301				return getToken(acc.TokenID)
 302			}
 303		}
 304		panic("token not found")
 305	}
 306	usrIdxs := []string{}
 307	for _, acc := range accs {
 308		if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
 309			for _, token := range tokens {
 310				if token.TokenID == acc.TokenID {
 311					usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
 312				}
 313			}
 314		}
 315	}
 316	isUsrTx := func(tx historyTxAPI) bool {
 317		for _, idx := range usrIdxs {
 318			if tx.FromIdx != nil && *tx.FromIdx == idx {
 319				return true
 320			}
 321			if tx.ToIdx == idx {
 322				return true
 323			}
 324		}
 325		return false
 326	}
 327	for _, genericTx := range sortedTxs {
 328		l1 := genericTx.L1()
 329		l2 := genericTx.L2()
 330		if l1 != nil {
 331			// Add L1 tx to DB
 332			err = h.AddL1Txs([]common.L1Tx{*l1})
 333			if err != nil {
 334				panic(err)
 335			}
 336			// L1Tx ==> historyTxAPI
 337			token := getToken(l1.TokenID)
 338			tx := historyTxAPI{
 339				IsL1:      "L1",
 340				TxID:      l1.TxID.String(),
 341				Type:      l1.Type,
 342				Position:  l1.Position,
 343				ToIdx:     idxToHez(l1.ToIdx, token.Symbol),
 344				Amount:    l1.Amount.String(),
 345				BatchNum:  l1.BatchNum,
 346				Timestamp: getTimestamp(l1.EthBlockNum),
 347				L1Info: &l1Info{
 348					ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
 349					UserOrigin:      l1.UserOrigin,
 350					FromEthAddr:     ethAddrToHez(l1.FromEthAddr),
 351					FromBJJ:         bjjToString(l1.FromBJJ),
 352					LoadAmount:      l1.LoadAmount.String(),
 353					EthBlockNum:     l1.EthBlockNum,
 354				},
 355				Token: token,
 356			}
 357			if l1.FromIdx != 0 {
 358				idxStr := idxToHez(l1.FromIdx, token.Symbol)
 359				tx.FromIdx = &idxStr
 360			}
 361			if token.USD != nil {
 362				af := new(big.Float).SetInt(l1.Amount)
 363				amountFloat, _ := af.Float64()
 364				usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
 365				tx.HistoricUSD = &usd
 366				laf := new(big.Float).SetInt(l1.LoadAmount)
 367				loadAmountFloat, _ := laf.Float64()
 368				loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
 369				tx.L1Info.HistoricLoadAmountUSD = &loadUSD
 370			}
 371			allTxs = append(allTxs, tx)
 372			if isUsrTx(tx) {
 373				usrTxs = append(usrTxs, tx)
 374			}
 375		} else {
 376			// Add L2 tx to DB
 377			err = h.AddL2Txs([]common.L2Tx{*l2})
 378			if err != nil {
 379				panic(err)
 380			}
 381			// L2Tx ==> historyTxAPI
 382			var tokenID common.TokenID
 383			found := false
 384			for _, acc := range accs {
 385				if acc.Idx == l2.FromIdx {
 386					found = true
 387					tokenID = acc.TokenID
 388					break
 389				}
 390			}
 391			if !found {
 392				panic("tokenID not found")
 393			}
 394			token := getToken(tokenID)
 395			tx := historyTxAPI{
 396				IsL1:      "L2",
 397				TxID:      l2.TxID.String(),
 398				Type:      l2.Type,
 399				Position:  l2.Position,
 400				ToIdx:     idxToHez(l2.ToIdx, token.Symbol),
 401				Amount:    l2.Amount.String(),
 402				BatchNum:  &l2.BatchNum,
 403				Timestamp: getTimestamp(l2.EthBlockNum),
 404				L2Info: &l2Info{
 405					Nonce: l2.Nonce,
 406					Fee:   l2.Fee,
 407				},
 408				Token: token,
 409			}
 410			if l2.FromIdx != 0 {
 411				idxStr := idxToHez(l2.FromIdx, token.Symbol)
 412				tx.FromIdx = &idxStr
 413			}
 414			if token.USD != nil {
 415				af := new(big.Float).SetInt(l2.Amount)
 416				amountFloat, _ := af.Float64()
 417				usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
 418				tx.HistoricUSD = &usd
 419				feeUSD := usd * l2.Fee.Percentage()
 420				tx.HistoricUSD = &usd
 421				tx.L2Info.HistoricFeeUSD = &feeUSD
 422			}
 423			allTxs = append(allTxs, tx)
 424			if isUsrTx(tx) {
 425				usrTxs = append(usrTxs, tx)
 426			}
 427		}
 428	}
 429	// Transform exits to API
 430	exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
 431		historyExits := []historydb.HistoryExit{}
 432		for _, exit := range exits {
 433			token := getTokenByIdx(exit.AccountIdx)
 434			historyExits = append(historyExits, historydb.HistoryExit{
 435				BatchNum:               exit.BatchNum,
 436				AccountIdx:             exit.AccountIdx,
 437				MerkleProof:            exit.MerkleProof,
 438				Balance:                exit.Balance,
 439				InstantWithdrawn:       exit.InstantWithdrawn,
 440				DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
 441				DelayedWithdrawn:       exit.DelayedWithdrawn,
 442				TokenID:                token.TokenID,
 443				TokenEthBlockNum:       token.EthBlockNum,
 444				TokenEthAddr:           token.EthAddr,
 445				TokenName:              token.Name,
 446				TokenSymbol:            token.Symbol,
 447				TokenDecimals:          token.Decimals,
 448				TokenUSD:               token.USD,
 449				TokenUSDUpdate:         token.USDUpdate,
 450			})
 451		}
 452		return historyExitsToAPI(historyExits)
 453	}
 454	apiExits := exitsToAPIExits(exits, accs, tokens)
 455	// sort.Sort(apiExits)
 456	usrExits := []exitAPI{}
 457	for _, exit := range apiExits {
 458		for _, idx := range usrIdxs {
 459			if idx == exit.AccountIdx {
 460				usrExits = append(usrExits, exit)
 461			}
 462		}
 463	}
 464	tc = testCommon{
 465		blocks:   blocks,
 466		tokens:   tokensUSD,
 467		batches:  batches,
 468		usrAddr:  ethAddrToHez(usrAddr),
 469		usrBjj:   bjjToString(usrBjj),
 470		accs:     accs,
 471		usrTxs:   usrTxs,
 472		allTxs:   allTxs,
 473		exits:    apiExits,
 474		usrExits: usrExits,
 475		router:   router,
 476	}
 477	// Run tests
 478	result := m.Run()
 479	// Stop server
 480	if err := server.Shutdown(context.Background()); err != nil {
 481		panic(err)
 482	}
 483	if err := database.Close(); err != nil {
 484		panic(err)
 485	}
 486	os.Exit(result)
 487}
 488
 489func TestGetHistoryTxs(t *testing.T) {
 490	endpoint := apiURL + "transactions-history"
 491	fetchedTxs := []historyTxAPI{}
 492	appendIter := func(intr interface{}) {
 493		for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
 494			tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
 495			if err != nil {
 496				panic(err)
 497			}
 498			fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
 499		}
 500	}
 501	// Get all (no filters)
 502	limit := 8
 503	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 504	err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 505	assert.NoError(t, err)
 506	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
 507	// Uncomment once tx generation for tests is fixed
 508	// // Get by ethAddr
 509	// fetchedTxs = []historyTxAPI{}
 510	// limit = 7
 511	// path = fmt.Sprintf(
 512	// 	"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
 513	// 	endpoint, tc.usrAddr, limit,
 514	// )
 515	// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 516	// assert.NoError(t, err)
 517	// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
 518	// // Get by bjj
 519	// fetchedTxs = []historyTxAPI{}
 520	// limit = 6
 521	// path = fmt.Sprintf(
 522	// 	"%s?BJJ=%s&limit=%d&fromItem=",
 523	// 	endpoint, tc.usrBjj, limit,
 524	// )
 525	// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 526	// assert.NoError(t, err)
 527	// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
 528	// Get by tokenID
 529	fetchedTxs = []historyTxAPI{}
 530	limit = 5
 531	tokenID := tc.allTxs[0].Token.TokenID
 532	path = fmt.Sprintf(
 533		"%s?tokenId=%d&limit=%d&fromItem=",
 534		endpoint, tokenID, limit,
 535	)
 536	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 537	assert.NoError(t, err)
 538	tokenIDTxs := []historyTxAPI{}
 539	for i := 0; i < len(tc.allTxs); i++ {
 540		if tc.allTxs[i].Token.TokenID == tokenID {
 541			tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
 542		}
 543	}
 544	assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
 545	// idx
 546	fetchedTxs = []historyTxAPI{}
 547	limit = 4
 548	idx := tc.allTxs[0].ToIdx
 549	path = fmt.Sprintf(
 550		"%s?accountIndex=%s&limit=%d&fromItem=",
 551		endpoint, idx, limit,
 552	)
 553	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 554	assert.NoError(t, err)
 555	idxTxs := []historyTxAPI{}
 556	for i := 0; i < len(tc.allTxs); i++ {
 557		if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
 558			tc.allTxs[i].ToIdx[6:] == idx[6:] {
 559			idxTxs = append(idxTxs, tc.allTxs[i])
 560		}
 561	}
 562	assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
 563	// batchNum
 564	fetchedTxs = []historyTxAPI{}
 565	limit = 3
 566	batchNum := tc.allTxs[0].BatchNum
 567	path = fmt.Sprintf(
 568		"%s?batchNum=%d&limit=%d&fromItem=",
 569		endpoint, *batchNum, limit,
 570	)
 571	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 572	assert.NoError(t, err)
 573	batchNumTxs := []historyTxAPI{}
 574	for i := 0; i < len(tc.allTxs); i++ {
 575		if tc.allTxs[i].BatchNum != nil &&
 576			*tc.allTxs[i].BatchNum == *batchNum {
 577			batchNumTxs = append(batchNumTxs, tc.allTxs[i])
 578		}
 579	}
 580	assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
 581	// type
 582	txTypes := []common.TxType{
 583		// Uncomment once test gen is fixed
 584		// common.TxTypeExit,
 585		// common.TxTypeTransfer,
 586		// common.TxTypeDeposit,
 587		common.TxTypeCreateAccountDeposit,
 588		// common.TxTypeCreateAccountDepositTransfer,
 589		// common.TxTypeDepositTransfer,
 590		common.TxTypeForceTransfer,
 591		// common.TxTypeForceExit,
 592		// common.TxTypeTransferToEthAddr,
 593		// common.TxTypeTransferToBJJ,
 594	}
 595	for _, txType := range txTypes {
 596		fetchedTxs = []historyTxAPI{}
 597		limit = 2
 598		path = fmt.Sprintf(
 599			"%s?type=%s&limit=%d&fromItem=",
 600			endpoint, txType, limit,
 601		)
 602		err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 603		assert.NoError(t, err)
 604		txTypeTxs := []historyTxAPI{}
 605		for i := 0; i < len(tc.allTxs); i++ {
 606			if tc.allTxs[i].Type == txType {
 607				txTypeTxs = append(txTypeTxs, tc.allTxs[i])
 608			}
 609		}
 610		assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
 611	}
 612	// Multiple filters
 613	fetchedTxs = []historyTxAPI{}
 614	limit = 1
 615	path = fmt.Sprintf(
 616		"%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
 617		endpoint, *batchNum, tokenID, limit,
 618	)
 619	err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
 620	assert.NoError(t, err)
 621	mixedTxs := []historyTxAPI{}
 622	for i := 0; i < len(tc.allTxs); i++ {
 623		if tc.allTxs[i].BatchNum != nil {
 624			if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
 625				mixedTxs = append(mixedTxs, tc.allTxs[i])
 626			}
 627		}
 628	}
 629	assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
 630	// All, in reverse order
 631	fetchedTxs = []historyTxAPI{}
 632	limit = 5
 633	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 634	err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
 635	assert.NoError(t, err)
 636	flipedTxs := []historyTxAPI{}
 637	for i := 0; i < len(tc.allTxs); i++ {
 638		flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
 639	}
 640	assertHistoryTxAPIs(t, flipedTxs, fetchedTxs)
 641	// 400
 642	path = fmt.Sprintf(
 643		"%s?accountIndex=%s&hermezEthereumAddress=%s",
 644		endpoint, idx, tc.usrAddr,
 645	)
 646	err = doBadReq("GET", path, nil, 400)
 647	assert.NoError(t, err)
 648	path = fmt.Sprintf("%s?tokenId=X", endpoint)
 649	err = doBadReq("GET", path, nil, 400)
 650	assert.NoError(t, err)
 651	// 404
 652	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
 653	err = doBadReq("GET", path, nil, 404)
 654	assert.NoError(t, err)
 655	path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
 656	err = doBadReq("GET", path, nil, 404)
 657	assert.NoError(t, err)
 658}
 659
 660func TestGetHistoryTx(t *testing.T) {
 661	// Get all txs by their ID
 662	endpoint := apiURL + "transactions-history/"
 663	fetchedTxs := []historyTxAPI{}
 664	for _, tx := range tc.allTxs {
 665		fetchedTx := historyTxAPI{}
 666		assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID, nil, &fetchedTx))
 667		fetchedTxs = append(fetchedTxs, fetchedTx)
 668	}
 669	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
 670	// 400
 671	err := doBadReq("GET", endpoint+"0x001", nil, 400)
 672	assert.NoError(t, err)
 673	// 404
 674	err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
 675	assert.NoError(t, err)
 676}
 677
 678func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
 679	require.Equal(t, len(expected), len(actual))
 680	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
 681		actual[i].ItemID = 0
 682		assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
 683		expected[i].Timestamp = actual[i].Timestamp
 684		if expected[i].Token.USDUpdate == nil {
 685			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
 686		} else {
 687			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
 688			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
 689		}
 690		test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
 691		if expected[i].L2Info != nil {
 692			test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
 693		} else {
 694			test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
 695		}
 696		assert.Equal(t, expected[i], actual[i])
 697	}
 698}
 699
 700func TestGetExits(t *testing.T) {
 701	endpoint := apiURL + "exits"
 702	fetchedExits := []exitAPI{}
 703	appendIter := func(intr interface{}) {
 704		for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
 705			tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
 706			if err != nil {
 707				panic(err)
 708			}
 709			fetchedExits = append(fetchedExits, tmp.(exitAPI))
 710		}
 711	}
 712	// Get all (no filters)
 713	limit := 8
 714	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 715	err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 716	assert.NoError(t, err)
 717	assertExitAPIs(t, tc.exits, fetchedExits)
 718
 719	// Get by ethAddr
 720	fetchedExits = []exitAPI{}
 721	limit = 7
 722	path = fmt.Sprintf(
 723		"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
 724		endpoint, tc.usrAddr, limit,
 725	)
 726	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 727	assert.NoError(t, err)
 728	assertExitAPIs(t, tc.usrExits, fetchedExits)
 729	// Get by bjj
 730	fetchedExits = []exitAPI{}
 731	limit = 6
 732	path = fmt.Sprintf(
 733		"%s?BJJ=%s&limit=%d&fromItem=",
 734		endpoint, tc.usrBjj, limit,
 735	)
 736	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 737	assert.NoError(t, err)
 738	assertExitAPIs(t, tc.usrExits, fetchedExits)
 739	// Get by tokenID
 740	fetchedExits = []exitAPI{}
 741	limit = 5
 742	tokenID := tc.exits[0].Token.TokenID
 743	path = fmt.Sprintf(
 744		"%s?tokenId=%d&limit=%d&fromItem=",
 745		endpoint, tokenID, limit,
 746	)
 747	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 748	assert.NoError(t, err)
 749	tokenIDExits := []exitAPI{}
 750	for i := 0; i < len(tc.exits); i++ {
 751		if tc.exits[i].Token.TokenID == tokenID {
 752			tokenIDExits = append(tokenIDExits, tc.exits[i])
 753		}
 754	}
 755	assertExitAPIs(t, tokenIDExits, fetchedExits)
 756	// idx
 757	fetchedExits = []exitAPI{}
 758	limit = 4
 759	idx := tc.exits[0].AccountIdx
 760	path = fmt.Sprintf(
 761		"%s?accountIndex=%s&limit=%d&fromItem=",
 762		endpoint, idx, limit,
 763	)
 764	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 765	assert.NoError(t, err)
 766	idxExits := []exitAPI{}
 767	for i := 0; i < len(tc.exits); i++ {
 768		if tc.exits[i].AccountIdx[6:] == idx[6:] {
 769			idxExits = append(idxExits, tc.exits[i])
 770		}
 771	}
 772	assertExitAPIs(t, idxExits, fetchedExits)
 773	// batchNum
 774	fetchedExits = []exitAPI{}
 775	limit = 3
 776	batchNum := tc.exits[0].BatchNum
 777	path = fmt.Sprintf(
 778		"%s?batchNum=%d&limit=%d&fromItem=",
 779		endpoint, batchNum, limit,
 780	)
 781	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 782	assert.NoError(t, err)
 783	batchNumExits := []exitAPI{}
 784	for i := 0; i < len(tc.exits); i++ {
 785		if tc.exits[i].BatchNum == batchNum {
 786			batchNumExits = append(batchNumExits, tc.exits[i])
 787		}
 788	}
 789	assertExitAPIs(t, batchNumExits, fetchedExits)
 790	// Multiple filters
 791	fetchedExits = []exitAPI{}
 792	limit = 1
 793	path = fmt.Sprintf(
 794		"%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
 795		endpoint, batchNum, tokenID, limit,
 796	)
 797	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
 798	assert.NoError(t, err)
 799	mixedExits := []exitAPI{}
 800	flipedExits := []exitAPI{}
 801	for i := 0; i < len(tc.exits); i++ {
 802		if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
 803			mixedExits = append(mixedExits, tc.exits[i])
 804		}
 805		flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
 806	}
 807	assertExitAPIs(t, mixedExits, fetchedExits)
 808	// All, in reverse order
 809	fetchedExits = []exitAPI{}
 810	limit = 5
 811	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
 812	err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
 813	assert.NoError(t, err)
 814	assertExitAPIs(t, flipedExits, fetchedExits)
 815	// 400
 816	path = fmt.Sprintf(
 817		"%s?accountIndex=%s&hermezEthereumAddress=%s",
 818		endpoint, idx, tc.usrAddr,
 819	)
 820	err = doBadReq("GET", path, nil, 400)
 821	assert.NoError(t, err)
 822	path = fmt.Sprintf("%s?tokenId=X", endpoint)
 823	err = doBadReq("GET", path, nil, 400)
 824	assert.NoError(t, err)
 825	// 404
 826	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
 827	err = doBadReq("GET", path, nil, 404)
 828	assert.NoError(t, err)
 829	path = fmt.Sprintf("%s?limit=1000&fromItem=1000", endpoint)
 830	err = doBadReq("GET", path, nil, 404)
 831	assert.NoError(t, err)
 832}
 833
 834func TestGetExit(t *testing.T) {
 835	// Get all txs by their ID
 836	endpoint := apiURL + "exits/"
 837	fetchedExits := []exitAPI{}
 838	for _, exit := range tc.exits {
 839		fetchedExit := exitAPI{}
 840		assert.NoError(
 841			t, doGoodReq(
 842				"GET",
 843				fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
 844				nil, &fetchedExit,
 845			),
 846		)
 847		fetchedExits = append(fetchedExits, fetchedExit)
 848	}
 849	assertExitAPIs(t, tc.exits, fetchedExits)
 850	// 400
 851	err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
 852	assert.NoError(t, err)
 853	err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
 854	assert.NoError(t, err)
 855	// 404
 856	err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
 857	assert.NoError(t, err)
 858}
 859
 860func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
 861	require.Equal(t, len(expected), len(actual))
 862	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
 863		actual[i].ItemID = 0
 864		if expected[i].Token.USDUpdate == nil {
 865			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
 866		} else {
 867			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
 868			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
 869		}
 870		assert.Equal(t, expected[i], actual[i])
 871	}
 872}
 873
 874func TestGetToken(t *testing.T) {
 875	// Get all txs by their ID
 876	endpoint := apiURL + "tokens/"
 877	fetchedTokens := []historydb.TokenRead{}
 878	for _, token := range tc.tokens {
 879		fetchedToken := historydb.TokenRead{}
 880		assert.NoError(t, doGoodReq("GET", endpoint+strconv.Itoa(int(token.TokenID)), nil, &fetchedToken))
 881		fetchedTokens = append(fetchedTokens, fetchedToken)
 882	}
 883	assertTokensAPIs(t, tc.tokens, fetchedTokens)
 884}
 885
 886func assertTokensAPIs(t *testing.T, expected, actual []historydb.TokenRead) {
 887	require.Equal(t, len(expected), len(actual))
 888	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
 889		if expected[i].USDUpdate == nil {
 890			assert.Equal(t, expected[i].USDUpdate, actual[i].USDUpdate)
 891		} else {
 892			assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
 893			expected[i].USDUpdate = actual[i].USDUpdate
 894		}
 895		assert.Equal(t, expected[i], actual[i])
 896	}
 897}
 898
 899func doGoodReqPaginated(
 900	path, order string,
 901	iterStruct db.Paginationer,
 902	appendIter func(res interface{}),
 903) error {
 904	next := 0
 905	for {
 906		// Call API to get this iteration items
 907		iterPath := path
 908		if next == 0 && order == historydb.OrderDesc {
 909			// Fetch first item in reverse order
 910			iterPath += "99999"
 911		} else {
 912			// Fetch from next item or 0 if it's ascending order
 913			iterPath += strconv.Itoa(next)
 914		}
 915		if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
 916			return err
 917		}
 918		appendIter(iterStruct)
 919		// Keep iterating?
 920		pag := iterStruct.GetPagination()
 921		if order == historydb.OrderAsc {
 922			if pag.LastReturnedItem == pag.LastItem { // No
 923				break
 924			} else { // Yes
 925				next = pag.LastReturnedItem + 1
 926			}
 927		} else {
 928			if pag.FirstReturnedItem == pag.FirstItem { // No
 929				break
 930			} else { // Yes
 931				next = pag.FirstReturnedItem - 1
 932			}
 933		}
 934	}
 935	return nil
 936}
 937
 938func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
 939	ctx := context.Background()
 940	client := &http.Client{}
 941	httpReq, _ := http.NewRequest(method, path, reqBody)
 942	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
 943	if err != nil {
 944		return err
 945	}
 946	// Validate request against swagger spec
 947	requestValidationInput := &swagger.RequestValidationInput{
 948		Request:    httpReq,
 949		PathParams: pathParams,
 950		Route:      route,
 951	}
 952	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
 953		return err
 954	}
 955	// Do API call
 956	resp, err := client.Do(httpReq)
 957	if err != nil {
 958		return err
 959	}
 960	if resp.Body == nil {
 961		return errors.New("Nil body")
 962	}
 963	//nolint
 964	defer resp.Body.Close()
 965	body, err := ioutil.ReadAll(resp.Body)
 966	if err != nil {
 967		return err
 968	}
 969	if resp.StatusCode != 200 {
 970		return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
 971	}
 972	// Unmarshal body into return struct
 973	if err := json.Unmarshal(body, returnStruct); err != nil {
 974		return err
 975	}
 976	// Validate response against swagger spec
 977	responseValidationInput := &swagger.ResponseValidationInput{
 978		RequestValidationInput: requestValidationInput,
 979		Status:                 resp.StatusCode,
 980		Header:                 resp.Header,
 981	}
 982	responseValidationInput = responseValidationInput.SetBodyBytes(body)
 983	return swagger.ValidateResponse(ctx, responseValidationInput)
 984}
 985
 986func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
 987	ctx := context.Background()
 988	client := &http.Client{}
 989	httpReq, _ := http.NewRequest(method, path, reqBody)
 990	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
 991	if err != nil {
 992		return err
 993	}
 994	// Validate request against swagger spec
 995	requestValidationInput := &swagger.RequestValidationInput{
 996		Request:    httpReq,
 997		PathParams: pathParams,
 998		Route:      route,
 999	}
1000	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1001		if expectedResponseCode != 400 {
1002			return err
1003		}
1004		log.Warn("The request does not match the API spec")
1005	}
1006	// Do API call
1007	resp, err := client.Do(httpReq)
1008	if err != nil {
1009		return err
1010	}
1011	if resp.Body == nil {
1012		return errors.New("Nil body")
1013	}
1014	//nolint
1015	defer resp.Body.Close()
1016	body, err := ioutil.ReadAll(resp.Body)
1017	if err != nil {
1018		return err
1019	}
1020	if resp.StatusCode != expectedResponseCode {
1021		return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
1022	}
1023	// Validate response against swagger spec
1024	responseValidationInput := &swagger.ResponseValidationInput{
1025		RequestValidationInput: requestValidationInput,
1026		Status:                 resp.StatusCode,
1027		Header:                 resp.Header,
1028	}
1029	responseValidationInput = responseValidationInput.SetBodyBytes(body)
1030	return swagger.ValidateResponse(ctx, responseValidationInput)
1031}