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