arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/api_test.go
api/api_test.goRaw
  1package api
  2
  3import (
  4	"bytes"
  5	"context"
  6	"encoding/json"
  7	"errors"
  8	"fmt"
  9	"io"
 10	"io/ioutil"
 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/eth"
 28	"github.com/hermeznetwork/hermez-node/log"
 29	"github.com/hermeznetwork/hermez-node/test"
 30	"github.com/iden3/go-iden3-crypto/babyjub"
 31	"github.com/mitchellh/copystructure"
 32	"github.com/stretchr/testify/assert"
 33	"github.com/stretchr/testify/require"
 34)
 35
 36const apiPort = ":4010"
 37const apiURL = "http://localhost" + apiPort + "/"
 38
 39type testCommon struct {
 40	blocks           []common.Block
 41	tokens           []historydb.TokenWithUSD
 42	batches          []testBatch
 43	fullBatches      []testFullBatch
 44	coordinators     []historydb.CoordinatorAPI
 45	usrAddr          string
 46	usrBjj           string
 47	accs             []common.Account
 48	usrTxs           []testTx
 49	allTxs           []testTx
 50	exits            []exitAPI
 51	usrExits         []exitAPI
 52	poolTxsToSend    []testPoolTxSend
 53	poolTxsToReceive []testPoolTxReceive
 54	auths            []accountCreationAuthAPI
 55	router           *swagger.Router
 56	bids             []testBid
 57}
 58
 59var tc testCommon
 60var config configAPI
 61
 62// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
 63// emulating the task of the synchronizer in order to have data to be returned
 64// by the API endpoints that will be tested
 65func TestMain(m *testing.M) {
 66	// Initializations
 67	// Swagger
 68	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
 69	// HistoryDB
 70	pass := os.Getenv("POSTGRES_PASS")
 71	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
 72	if err != nil {
 73		panic(err)
 74	}
 75	hdb := historydb.NewHistoryDB(database)
 76	if err != nil {
 77		panic(err)
 78	}
 79	// StateDB
 80	dir, err := ioutil.TempDir("", "tmpdb")
 81	if err != nil {
 82		panic(err)
 83	}
 84	defer func() {
 85		if err := os.RemoveAll(dir); err != nil {
 86			panic(err)
 87		}
 88	}()
 89	sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
 90	if err != nil {
 91		panic(err)
 92	}
 93	// L2DB
 94	l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
 95	test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
 96	// Config (smart contract constants)
 97	config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
 98	config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
 99	config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
100	config.RollupConstants.LimitLoadAmount, _ = new(big.Int).SetString("340282366920938463463374607431768211456", 10)
101	config.RollupConstants.LimitL2TransferAmount, _ = new(big.Int).SetString("6277101735386680763835789423207666416102355444464034512896", 10)
102	config.RollupConstants.LimitTokens = eth.RollupConstLimitTokens
103	config.RollupConstants.L1CoordinatorTotalBytes = eth.RollupConstL1CoordinatorTotalBytes
104	config.RollupConstants.L1UserTotalBytes = eth.RollupConstL1UserTotalBytes
105	config.RollupConstants.MaxL1UserTx = eth.RollupConstMaxL1UserTx
106	config.RollupConstants.MaxL1Tx = eth.RollupConstMaxL1Tx
107	config.RollupConstants.InputSHAConstantBytes = eth.RollupConstInputSHAConstantBytes
108	config.RollupConstants.NumBuckets = eth.RollupConstNumBuckets
109	config.RollupConstants.MaxWithdrawalDelay = eth.RollupConstMaxWithdrawalDelay
110	var rollupPublicConstants eth.RollupPublicConstants
111	rollupPublicConstants.AbsoluteMaxL1L2BatchTimeout = 240
112	rollupPublicConstants.HermezAuctionContract = ethCommon.HexToAddress("0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e")
113	rollupPublicConstants.HermezGovernanceDAOAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
114	rollupPublicConstants.SafetyAddress = ethCommon.HexToAddress("0xE5904695748fe4A84b40b3fc79De2277660BD1D3")
115	rollupPublicConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
116	rollupPublicConstants.WithdrawDelayerContract = ethCommon.HexToAddress("0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe")
117	var verifier eth.RollupVerifierStruct
118	verifier.MaxTx = 512
119	verifier.NLevels = 32
120	rollupPublicConstants.Verifiers = append(rollupPublicConstants.Verifiers, verifier)
121
122	var auctionConstants eth.AuctionConstants
123	auctionConstants.BlocksPerSlot = 40
124	auctionConstants.GenesisBlockNum = 100
125	auctionConstants.GovernanceAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
126	auctionConstants.InitialMinimalBidding, _ = new(big.Int).SetString("10000000000000000000", 10)
127	auctionConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
128	auctionConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
129
130	var wdelayerConstants eth.WDelayerConstants
131	wdelayerConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
132	wdelayerConstants.MaxEmergencyModeTime = uint64(1000000)
133	wdelayerConstants.MaxWithdrawalDelay = uint64(10000000)
134
135	config.RollupConstants.PublicConstants = rollupPublicConstants
136	config.AuctionConstants = auctionConstants
137	config.WDelayerConstants = wdelayerConstants
138
139	// API
140	api := gin.Default()
141	if err := SetAPIEndpoints(
142		true,
143		true,
144		api,
145		hdb,
146		sdb,
147		l2DB,
148		&config,
149	); err != nil {
150		panic(err)
151	}
152	// Start server
153	server := &http.Server{Addr: apiPort, Handler: api}
154	go func() {
155		if err := server.ListenAndServe(); err != nil &&
156			err != http.ErrServerClosed {
157			panic(err)
158		}
159	}()
160
161	// Fill HistoryDB and StateDB with fake data
162	// Gen blocks and add them to DB
163	const nBlocks = 5
164	blocks := test.GenBlocks(1, nBlocks+1)
165	err = h.AddBlocks(blocks)
166	if err != nil {
167		panic(err)
168	}
169	// Gen tokens and add them to DB
170	const nTokens = 10
171	tokens, ethToken := test.GenTokens(nTokens, blocks)
172	err = h.AddTokens(tokens)
173	if err != nil {
174		panic(err)
175	}
176	tokens = append([]common.Token{ethToken}, tokens...)
177	// Set token value
178	tokensUSD := []historydb.TokenWithUSD{}
179	for i, tkn := range tokens {
180		token := historydb.TokenWithUSD{
181			TokenID:     tkn.TokenID,
182			EthBlockNum: tkn.EthBlockNum,
183			EthAddr:     tkn.EthAddr,
184			Name:        tkn.Name,
185			Symbol:      tkn.Symbol,
186			Decimals:    tkn.Decimals,
187		}
188		// Set value of 50% of the tokens
189		if i%2 != 0 {
190			value := float64(i) * 1.234567
191			now := time.Now().UTC()
192			token.USD = &value
193			token.USDUpdate = &now
194			err = h.UpdateTokenValue(token.Symbol, value)
195			if err != nil {
196				panic(err)
197			}
198		}
199		tokensUSD = append(tokensUSD, token)
200	}
201	// Gen batches and add them to DB
202	const nBatches = 10
203	batches := test.GenBatches(nBatches, blocks)
204	err = h.AddBatches(batches)
205	if err != nil {
206		panic(err)
207	}
208	// Gen accounts and add them to HistoryDB and StateDB
209	const totalAccounts = 40
210	const userAccounts = 4
211	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
212	privK := babyjub.NewRandPrivKey()
213	usrBjj := privK.Public()
214	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
215	err = h.AddAccounts(accs)
216	if err != nil {
217		panic(err)
218	}
219	for i := 0; i < len(accs); i++ {
220		if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
221			panic(err)
222		}
223	}
224	// helper to vinculate user related resources
225	usrIdxs := []string{}
226	for _, acc := range accs {
227		if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
228			for _, token := range tokens {
229				if token.TokenID == acc.TokenID {
230					usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
231				}
232			}
233		}
234	}
235	// Gen exits and add them to DB
236	const totalExits = 40
237	exits := test.GenExitTree(totalExits, batches, accs)
238	err = h.AddExitTree(exits)
239	if err != nil {
240		panic(err)
241	}
242
243	// L1 and L2 txs need to be sorted in a combined way
244	// Gen L1Txs
245	const totalL1Txs = 40
246	const userL1Txs = 4
247	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
248	// Gen L2Txs
249	const totalL2Txs = 20
250	const userL2Txs = 4
251	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
252	// Sort txs
253	sortedTxs := []txSortFielder{}
254	for i := 0; i < len(usrL1Txs); i++ {
255		wL1 := wrappedL1(usrL1Txs[i])
256		sortedTxs = append(sortedTxs, &wL1)
257	}
258	for i := 0; i < len(othrL1Txs); i++ {
259		wL1 := wrappedL1(othrL1Txs[i])
260		sortedTxs = append(sortedTxs, &wL1)
261	}
262	for i := 0; i < len(usrL2Txs); i++ {
263		wL2 := wrappedL2(usrL2Txs[i])
264		sortedTxs = append(sortedTxs, &wL2)
265	}
266	for i := 0; i < len(othrL2Txs); i++ {
267		wL2 := wrappedL2(othrL2Txs[i])
268		sortedTxs = append(sortedTxs, &wL2)
269	}
270	sort.Sort(txsSort(sortedTxs))
271	// Store txs to DB
272	for _, genericTx := range sortedTxs {
273		l1 := genericTx.L1()
274		l2 := genericTx.L2()
275		if l1 != nil {
276			err = h.AddL1Txs([]common.L1Tx{*l1})
277			if err != nil {
278				panic(err)
279			}
280		} else if l2 != nil {
281			err = h.AddL2Txs([]common.L2Tx{*l2})
282			if err != nil {
283				panic(err)
284			}
285		} else {
286			panic("should be l1 or l2")
287		}
288	}
289
290	// Transform exits to API
291	exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
292		historyExits := []historydb.HistoryExit{}
293		for _, exit := range exits {
294			token := getTokenByIdx(exit.AccountIdx, tokensUSD, accs)
295			historyExits = append(historyExits, historydb.HistoryExit{
296				BatchNum:               exit.BatchNum,
297				AccountIdx:             exit.AccountIdx,
298				MerkleProof:            exit.MerkleProof,
299				Balance:                exit.Balance,
300				InstantWithdrawn:       exit.InstantWithdrawn,
301				DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
302				DelayedWithdrawn:       exit.DelayedWithdrawn,
303				TokenID:                token.TokenID,
304				TokenEthBlockNum:       token.EthBlockNum,
305				TokenEthAddr:           token.EthAddr,
306				TokenName:              token.Name,
307				TokenSymbol:            token.Symbol,
308				TokenDecimals:          token.Decimals,
309				TokenUSD:               token.USD,
310				TokenUSDUpdate:         token.USDUpdate,
311			})
312		}
313		return historyExitsToAPI(historyExits)
314	}
315	apiExits := exitsToAPIExits(exits, accs, tokens)
316	// sort.Sort(apiExits)
317	usrExits := []exitAPI{}
318	for _, exit := range apiExits {
319		for _, idx := range usrIdxs {
320			if idx == exit.AccountIdx {
321				usrExits = append(usrExits, exit)
322			}
323		}
324	}
325
326	// Coordinators
327	const nCoords = 10
328	coords := test.GenCoordinators(nCoords, blocks)
329	err = hdb.AddCoordinators(coords)
330	if err != nil {
331		panic(err)
332	}
333	fromItem := uint(0)
334	limit := uint(99999)
335	coordinators, _, err := hdb.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
336	if err != nil {
337		panic(err)
338	}
339	// Account creation auth
340	const nAuths = 5
341	auths := test.GenAuths(nAuths)
342	// Transform auths to API format
343	apiAuths := []accountCreationAuthAPI{}
344	for _, auth := range auths {
345		apiAuth := accountCreationAuthToAPI(auth)
346		apiAuths = append(apiAuths, *apiAuth)
347	}
348
349	// Bids
350	const nBids = 10
351	bids := test.GenBids(nBids, blocks, coords)
352	err = hdb.AddBids(bids)
353	if err != nil {
354		panic(err)
355	}
356
357	// Set testCommon
358	usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
359	poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted.
360	testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
361	tc = testCommon{
362		blocks:           blocks,
363		tokens:           tokensUSD,
364		batches:          testBatches,
365		fullBatches:      fullBatches,
366		coordinators:     coordinators,
367		usrAddr:          ethAddrToHez(usrAddr),
368		usrBjj:           bjjToString(usrBjj),
369		accs:             accs,
370		usrTxs:           usrTxs,
371		allTxs:           allTxs,
372		exits:            apiExits,
373		usrExits:         usrExits,
374		poolTxsToSend:    poolTxsToSend,
375		poolTxsToReceive: poolTxsToReceive,
376		auths:            apiAuths,
377		router:           router,
378		bids:             genTestBids(blocks, coordinators, bids),
379	}
380
381	// Fake server
382	if os.Getenv("FAKE_SERVER") == "yes" {
383		for {
384			log.Info("Running fake server at " + apiURL + " until ^C is received")
385			time.Sleep(30 * time.Second)
386		}
387	}
388	// Run tests
389	result := m.Run()
390	// Stop server
391	if err := server.Shutdown(context.Background()); err != nil {
392		panic(err)
393	}
394	if err := database.Close(); err != nil {
395		panic(err)
396	}
397	if err := os.RemoveAll(dir); err != nil {
398		panic(err)
399	}
400	os.Exit(result)
401}
402
403func TestGetExits(t *testing.T) {
404	endpoint := apiURL + "exits"
405	fetchedExits := []exitAPI{}
406	appendIter := func(intr interface{}) {
407		for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
408			tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
409			if err != nil {
410				panic(err)
411			}
412			fetchedExits = append(fetchedExits, tmp.(exitAPI))
413		}
414	}
415	// Get all (no filters)
416	limit := 8
417	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
418	err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
419	assert.NoError(t, err)
420	assertExitAPIs(t, tc.exits, fetchedExits)
421
422	// Get by ethAddr
423	fetchedExits = []exitAPI{}
424	limit = 7
425	path = fmt.Sprintf(
426		"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
427		endpoint, tc.usrAddr, limit,
428	)
429	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
430	assert.NoError(t, err)
431	assertExitAPIs(t, tc.usrExits, fetchedExits)
432	// Get by bjj
433	fetchedExits = []exitAPI{}
434	limit = 6
435	path = fmt.Sprintf(
436		"%s?BJJ=%s&limit=%d&fromItem=",
437		endpoint, tc.usrBjj, limit,
438	)
439	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
440	assert.NoError(t, err)
441	assertExitAPIs(t, tc.usrExits, fetchedExits)
442	// Get by tokenID
443	fetchedExits = []exitAPI{}
444	limit = 5
445	tokenID := tc.exits[0].Token.TokenID
446	path = fmt.Sprintf(
447		"%s?tokenId=%d&limit=%d&fromItem=",
448		endpoint, tokenID, limit,
449	)
450	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
451	assert.NoError(t, err)
452	tokenIDExits := []exitAPI{}
453	for i := 0; i < len(tc.exits); i++ {
454		if tc.exits[i].Token.TokenID == tokenID {
455			tokenIDExits = append(tokenIDExits, tc.exits[i])
456		}
457	}
458	assertExitAPIs(t, tokenIDExits, fetchedExits)
459	// idx
460	fetchedExits = []exitAPI{}
461	limit = 4
462	idx := tc.exits[0].AccountIdx
463	path = fmt.Sprintf(
464		"%s?accountIndex=%s&limit=%d&fromItem=",
465		endpoint, idx, limit,
466	)
467	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
468	assert.NoError(t, err)
469	idxExits := []exitAPI{}
470	for i := 0; i < len(tc.exits); i++ {
471		if tc.exits[i].AccountIdx[6:] == idx[6:] {
472			idxExits = append(idxExits, tc.exits[i])
473		}
474	}
475	assertExitAPIs(t, idxExits, fetchedExits)
476	// batchNum
477	fetchedExits = []exitAPI{}
478	limit = 3
479	batchNum := tc.exits[0].BatchNum
480	path = fmt.Sprintf(
481		"%s?batchNum=%d&limit=%d&fromItem=",
482		endpoint, batchNum, limit,
483	)
484	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
485	assert.NoError(t, err)
486	batchNumExits := []exitAPI{}
487	for i := 0; i < len(tc.exits); i++ {
488		if tc.exits[i].BatchNum == batchNum {
489			batchNumExits = append(batchNumExits, tc.exits[i])
490		}
491	}
492	assertExitAPIs(t, batchNumExits, fetchedExits)
493	// Multiple filters
494	fetchedExits = []exitAPI{}
495	limit = 1
496	path = fmt.Sprintf(
497		"%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
498		endpoint, batchNum, tokenID, limit,
499	)
500	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
501	assert.NoError(t, err)
502	mixedExits := []exitAPI{}
503	flipedExits := []exitAPI{}
504	for i := 0; i < len(tc.exits); i++ {
505		if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
506			mixedExits = append(mixedExits, tc.exits[i])
507		}
508		flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
509	}
510	assertExitAPIs(t, mixedExits, fetchedExits)
511	// All, in reverse order
512	fetchedExits = []exitAPI{}
513	limit = 5
514	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
515	err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
516	assert.NoError(t, err)
517	assertExitAPIs(t, flipedExits, fetchedExits)
518	// 400
519	path = fmt.Sprintf(
520		"%s?accountIndex=%s&hermezEthereumAddress=%s",
521		endpoint, idx, tc.usrAddr,
522	)
523	err = doBadReq("GET", path, nil, 400)
524	assert.NoError(t, err)
525	path = fmt.Sprintf("%s?tokenId=X", endpoint)
526	err = doBadReq("GET", path, nil, 400)
527	assert.NoError(t, err)
528	// 404
529	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
530	err = doBadReq("GET", path, nil, 404)
531	assert.NoError(t, err)
532	path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
533	err = doBadReq("GET", path, nil, 404)
534	assert.NoError(t, err)
535}
536
537func TestGetExit(t *testing.T) {
538	// Get all txs by their ID
539	endpoint := apiURL + "exits/"
540	fetchedExits := []exitAPI{}
541	for _, exit := range tc.exits {
542		fetchedExit := exitAPI{}
543		assert.NoError(
544			t, doGoodReq(
545				"GET",
546				fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
547				nil, &fetchedExit,
548			),
549		)
550		fetchedExits = append(fetchedExits, fetchedExit)
551	}
552	assertExitAPIs(t, tc.exits, fetchedExits)
553	// 400
554	err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
555	assert.NoError(t, err)
556	err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
557	assert.NoError(t, err)
558	// 404
559	err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
560	assert.NoError(t, err)
561}
562
563func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
564	require.Equal(t, len(expected), len(actual))
565	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
566		actual[i].ItemID = 0
567		if expected[i].Token.USDUpdate == nil {
568			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
569		} else {
570			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
571			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
572		}
573		assert.Equal(t, expected[i], actual[i])
574	}
575}
576func TestGetConfig(t *testing.T) {
577	endpoint := apiURL + "config"
578	var configTest configAPI
579	assert.NoError(t, doGoodReq("GET", endpoint, nil, &configTest))
580	assert.Equal(t, config, configTest)
581	assert.Equal(t, cg, &configTest)
582}
583
584func TestAccountCreationAuth(t *testing.T) {
585	// POST
586	endpoint := apiURL + "account-creation-authorization"
587	for _, auth := range tc.auths {
588		jsonAuthBytes, err := json.Marshal(auth)
589		assert.NoError(t, err)
590		jsonAuthReader := bytes.NewReader(jsonAuthBytes)
591		fmt.Println(string(jsonAuthBytes))
592		assert.NoError(
593			t, doGoodReq(
594				"POST",
595				endpoint,
596				jsonAuthReader, nil,
597			),
598		)
599	}
600	// GET
601	endpoint += "/"
602	for _, auth := range tc.auths {
603		fetchedAuth := accountCreationAuthAPI{}
604		assert.NoError(
605			t, doGoodReq(
606				"GET",
607				endpoint+auth.EthAddr,
608				nil, &fetchedAuth,
609			),
610		)
611		assertAuth(t, auth, fetchedAuth)
612	}
613	// POST
614	// 400
615	// Wrong addr
616	badAuth := tc.auths[0]
617	badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
618	jsonAuthBytes, err := json.Marshal(badAuth)
619	assert.NoError(t, err)
620	jsonAuthReader := bytes.NewReader(jsonAuthBytes)
621	err = doBadReq("POST", endpoint, jsonAuthReader, 400)
622	assert.NoError(t, err)
623	// Wrong signature
624	badAuth = tc.auths[0]
625	badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
626	badAuth.Signature += "F"
627	jsonAuthBytes, err = json.Marshal(badAuth)
628	assert.NoError(t, err)
629	jsonAuthReader = bytes.NewReader(jsonAuthBytes)
630	err = doBadReq("POST", endpoint, jsonAuthReader, 400)
631	assert.NoError(t, err)
632	// GET
633	// 400
634	err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
635	assert.NoError(t, err)
636	// 404
637	err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
638	assert.NoError(t, err)
639}
640
641func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
642	// timestamp should be very close to now
643	assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
644	expected.Timestamp = actual.Timestamp
645	assert.Equal(t, expected, actual)
646}
647
648func doGoodReqPaginated(
649	path, order string,
650	iterStruct db.Paginationer,
651	appendIter func(res interface{}),
652) error {
653	next := 0
654	for {
655		// Call API to get this iteration items
656		iterPath := path
657		if next == 0 && order == historydb.OrderDesc {
658			// Fetch first item in reverse order
659			iterPath += "99999"
660		} else {
661			// Fetch from next item or 0 if it's ascending order
662			iterPath += strconv.Itoa(next)
663		}
664		if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
665			return err
666		}
667		appendIter(iterStruct)
668		// Keep iterating?
669		pag := iterStruct.GetPagination()
670		if order == historydb.OrderAsc {
671			if pag.LastReturnedItem == pag.LastItem { // No
672				break
673			} else { // Yes
674				next = pag.LastReturnedItem + 1
675			}
676		} else {
677			if pag.FirstReturnedItem == pag.FirstItem { // No
678				break
679			} else { // Yes
680				next = pag.FirstReturnedItem - 1
681			}
682		}
683	}
684	return nil
685}
686
687func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
688	ctx := context.Background()
689	client := &http.Client{}
690	httpReq, err := http.NewRequest(method, path, reqBody)
691	if err != nil {
692		return err
693	}
694	if reqBody != nil {
695		httpReq.Header.Add("Content-Type", "application/json")
696	}
697	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
698	if err != nil {
699		return err
700	}
701	// Validate request against swagger spec
702	requestValidationInput := &swagger.RequestValidationInput{
703		Request:    httpReq,
704		PathParams: pathParams,
705		Route:      route,
706	}
707	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
708		return err
709	}
710	// Do API call
711	resp, err := client.Do(httpReq)
712	if err != nil {
713		return err
714	}
715	if resp.Body == nil && returnStruct != nil {
716		return errors.New("Nil body")
717	}
718	//nolint
719	defer resp.Body.Close()
720	body, err := ioutil.ReadAll(resp.Body)
721	if err != nil {
722		return err
723	}
724	if resp.StatusCode != 200 {
725		return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
726	}
727	if returnStruct == nil {
728		return nil
729	}
730	// Unmarshal body into return struct
731	if err := json.Unmarshal(body, returnStruct); err != nil {
732		log.Error("invalid json: " + string(body))
733		return err
734	}
735	// Validate response against swagger spec
736	responseValidationInput := &swagger.ResponseValidationInput{
737		RequestValidationInput: requestValidationInput,
738		Status:                 resp.StatusCode,
739		Header:                 resp.Header,
740	}
741	responseValidationInput = responseValidationInput.SetBodyBytes(body)
742	return swagger.ValidateResponse(ctx, responseValidationInput)
743}
744
745func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
746	ctx := context.Background()
747	client := &http.Client{}
748	httpReq, _ := http.NewRequest(method, path, reqBody)
749	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
750	if err != nil {
751		return err
752	}
753	// Validate request against swagger spec
754	requestValidationInput := &swagger.RequestValidationInput{
755		Request:    httpReq,
756		PathParams: pathParams,
757		Route:      route,
758	}
759	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
760		if expectedResponseCode != 400 {
761			return err
762		}
763		log.Warn("The request does not match the API spec")
764	}
765	// Do API call
766	resp, err := client.Do(httpReq)
767	if err != nil {
768		return err
769	}
770	if resp.Body == nil {
771		return errors.New("Nil body")
772	}
773	//nolint
774	defer resp.Body.Close()
775	body, err := ioutil.ReadAll(resp.Body)
776	if err != nil {
777		return err
778	}
779	if resp.StatusCode != expectedResponseCode {
780		return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
781	}
782	// Validate response against swagger spec
783	responseValidationInput := &swagger.ResponseValidationInput{
784		RequestValidationInput: requestValidationInput,
785		Status:                 resp.StatusCode,
786		Header:                 resp.Header,
787	}
788	responseValidationInput = responseValidationInput.SetBodyBytes(body)
789	return swagger.ValidateResponse(ctx, responseValidationInput)
790}
791
792// test helpers
793
794func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
795	for i := 0; i < len(blocks); i++ {
796		if blocks[i].EthBlockNum == blockNum {
797			return blocks[i].Timestamp
798		}
799	}
800	panic("timesamp not found")
801}
802
803func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
804	for i := 0; i < len(tokens); i++ {
805		if tokens[i].TokenID == id {
806			return tokens[i]
807		}
808	}
809	panic("token not found")
810}
811
812func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
813	for _, acc := range accs {
814		if idx == acc.Idx {
815			return getTokenByID(acc.TokenID, tokens)
816		}
817	}
818	panic("token not found")
819}
820
821func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
822	for _, acc := range accs {
823		if acc.Idx == idx {
824			return &acc
825		}
826	}
827	panic("account not found")
828}
829
830func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
831	for _, b := range blocks {
832		if b.EthBlockNum == ethBlockNum {
833			return b
834		}
835	}
836	panic("block not found")
837}
838
839func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
840	for _, c := range coordinators {
841		if c.Bidder == bidder {
842			return c
843		}
844	}
845	panic("coordinator not found")
846}