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 "strconv"
14 "testing"
15 "time"
16
17 ethCommon "github.com/ethereum/go-ethereum/common"
18 swagger "github.com/getkin/kin-openapi/openapi3filter"
19 "github.com/gin-gonic/gin"
20 "github.com/hermeznetwork/hermez-node/common"
21 "github.com/hermeznetwork/hermez-node/db"
22 "github.com/hermeznetwork/hermez-node/db/historydb"
23 "github.com/hermeznetwork/hermez-node/db/l2db"
24 "github.com/hermeznetwork/hermez-node/db/statedb"
25 "github.com/hermeznetwork/hermez-node/log"
26 "github.com/hermeznetwork/hermez-node/test"
27 "github.com/hermeznetwork/hermez-node/test/til"
28 "github.com/hermeznetwork/hermez-node/test/txsets"
29 "github.com/hermeznetwork/tracerr"
30)
31
32// Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
33// when testing paginated endpoints.
34type Pendinger interface {
35 GetPending() (pendingItems, lastItemID uint64)
36 Len() int
37 New() Pendinger
38}
39
40const apiPort = ":4010"
41const apiURL = "http://localhost" + apiPort + "/"
42
43var SetBlockchain = `
44 Type: Blockchain
45
46 AddToken(1)
47 AddToken(2)
48 AddToken(3)
49 AddToken(4)
50 AddToken(5)
51 AddToken(6)
52 AddToken(7)
53 AddToken(8)
54 > block
55
56 // Coordinator accounts, Idxs: 256, 257
57 CreateAccountCoordinator(0) Coord
58 CreateAccountCoordinator(1) Coord
59
60 // close Block:0, Batch:1
61 > batch
62
63 CreateAccountDeposit(0) A: 11111111100000000000
64 CreateAccountDeposit(1) C: 22222222200000000000
65 CreateAccountCoordinator(0) C
66
67 // close Block:0, Batch:2
68 > batchL1
69 // Expected balances:
70 // Coord(0): 0, Coord(1): 0
71 // C(0): 0
72
73 CreateAccountDeposit(1) A: 33333333300000000000
74
75 // close Block:0, Batch:3
76 > batchL1
77
78 // close Block:0, Batch:4
79 > batchL1
80
81 CreateAccountDepositTransfer(0) B-A: 44444444400000000000, 123444444400000000000
82
83 // close Block:0, Batch:5
84 > batchL1
85 CreateAccountDeposit(0) D: 55555555500000000000
86
87 // close Block:0, Batch:6
88 > batchL1
89
90 CreateAccountCoordinator(1) B
91
92 Transfer(1) A-B: 11111100000000000 (2)
93 Transfer(0) B-C: 22222200000000000 (3)
94
95 // close Block:0, Batch:7
96 > batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2}
97
98 Deposit(0) C: 66666666600000000000
99 DepositTransfer(0) C-D: 77777777700000000000, 12377777700000000000
100
101 Transfer(0) A-B: 33333300000000000 (111)
102 Transfer(0) C-A: 44444400000000000 (222)
103 Transfer(1) B-C: 55555500000000000 (123)
104 Exit(0) A: 66666600000000000 (44)
105
106 ForceTransfer(0) D-B: 77777700000000000
107 ForceExit(0) B: 88888800000000000
108
109 // close Block:0, Batch:8
110 > batchL1
111 > block
112
113 Transfer(0) D-A: 99999900000000000 (77)
114 Transfer(0) B-D: 12312300000000000 (55)
115
116 // close Block:1, Batch:1
117 > batchL1
118
119 CreateAccountCoordinator(0) F
120
121 CreateAccountCoordinator(0) G
122 CreateAccountCoordinator(0) H
123 CreateAccountCoordinator(0) I
124 CreateAccountCoordinator(0) J
125 CreateAccountCoordinator(0) K
126 CreateAccountCoordinator(0) L
127 CreateAccountCoordinator(0) M
128 CreateAccountCoordinator(0) N
129 CreateAccountCoordinator(0) O
130 CreateAccountCoordinator(0) P
131
132 CreateAccountCoordinator(5) G
133 CreateAccountCoordinator(5) H
134 CreateAccountCoordinator(5) I
135 CreateAccountCoordinator(5) J
136 CreateAccountCoordinator(5) K
137 CreateAccountCoordinator(5) L
138 CreateAccountCoordinator(5) M
139 CreateAccountCoordinator(5) N
140 CreateAccountCoordinator(5) O
141 CreateAccountCoordinator(5) P
142
143 CreateAccountCoordinator(2) G
144 CreateAccountCoordinator(2) H
145 CreateAccountCoordinator(2) I
146 CreateAccountCoordinator(2) J
147 CreateAccountCoordinator(2) K
148 CreateAccountCoordinator(2) L
149 CreateAccountCoordinator(2) M
150 CreateAccountCoordinator(2) N
151 CreateAccountCoordinator(2) O
152 CreateAccountCoordinator(2) P
153
154
155 > batch
156 > block
157 > batch
158 > block
159 > batch
160 > block
161`
162
163type testCommon struct {
164 blocks []common.Block
165 tokens []historydb.TokenWithUSD
166 batches []testBatch
167 fullBatches []testFullBatch
168 coordinators []historydb.CoordinatorAPI
169 accounts []testAccount
170 txs []testTx
171 exits []testExit
172 poolTxsToSend []testPoolTxSend
173 poolTxsToReceive []testPoolTxReceive
174 auths []testAuth
175 router *swagger.Router
176 bids []testBid
177 slots []testSlot
178 auctionVars common.AuctionVariables
179 rollupVars common.RollupVariables
180 wdelayerVars common.WDelayerVariables
181 nextForgers []NextForger
182}
183
184var tc testCommon
185var config configAPI
186var api *API
187
188// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
189// emulating the task of the synchronizer in order to have data to be returned
190// by the API endpoints that will be tested
191func TestMain(m *testing.M) {
192 // Initializations
193 // Swagger
194 router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
195 // HistoryDB
196 pass := os.Getenv("POSTGRES_PASS")
197
198 database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
199 if err != nil {
200 panic(err)
201 }
202 hdb := historydb.NewHistoryDB(database)
203 if err != nil {
204 panic(err)
205 }
206 // StateDB
207 dir, err := ioutil.TempDir("", "tmpdb")
208 if err != nil {
209 panic(err)
210 }
211 defer func() {
212 if err := os.RemoveAll(dir); err != nil {
213 panic(err)
214 }
215 }()
216 sdb, err := statedb.NewStateDB(dir, 128, statedb.TypeTxSelector, 0)
217 if err != nil {
218 panic(err)
219 }
220 // L2DB
221 l2DB := l2db.NewL2DB(database, 10, 1000, 24*time.Hour)
222 test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
223 // Config (smart contract constants)
224 chainID := uint16(0)
225 _config := getConfigTest(chainID)
226 config = configAPI{
227 RollupConstants: *newRollupConstants(_config.RollupConstants),
228 AuctionConstants: _config.AuctionConstants,
229 WDelayerConstants: _config.WDelayerConstants,
230 }
231
232 // API
233 apiGin := gin.Default()
234 api, err = NewAPI(
235 true,
236 true,
237 apiGin,
238 hdb,
239 sdb,
240 l2DB,
241 &_config,
242 )
243 if err != nil {
244 panic(err)
245 }
246 // Start server
247 server := &http.Server{Addr: apiPort, Handler: apiGin}
248 go func() {
249 if err := server.ListenAndServe(); err != nil && tracerr.Unwrap(err) != http.ErrServerClosed {
250 panic(err)
251 }
252 }()
253
254 // Reset DB
255 test.WipeDB(api.h.DB())
256
257 // Genratre blockchain data with til
258 tcc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
259 tilCfgExtra := til.ConfigExtra{
260 BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
261 CoordUser: "Coord",
262 }
263 blocksData, err := tcc.GenerateBlocks(SetBlockchain)
264 if err != nil {
265 panic(err)
266 }
267 err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
268 if err != nil {
269 panic(err)
270 }
271 AddAditionalInformation(blocksData)
272 // Generate L2 Txs with til
273 commonPoolTxs, err := tcc.GeneratePoolL2Txs(txsets.SetPoolL2MinimumFlow0)
274 if err != nil {
275 panic(err)
276 }
277
278 // Extract til generated data, and add it to HistoryDB
279 var commonBlocks []common.Block
280 var commonBatches []common.Batch
281 var commonAccounts []common.Account
282 var commonExitTree []common.ExitInfo
283 var commonL1Txs []common.L1Tx
284 var commonL2Txs []common.L2Tx
285 // Add ETH token at the beginning of the array
286 testTokens := []historydb.TokenWithUSD{}
287 ethUSD := float64(500)
288 ethNow := time.Now()
289 testTokens = append(testTokens, historydb.TokenWithUSD{
290 TokenID: test.EthToken.TokenID,
291 EthBlockNum: test.EthToken.EthBlockNum,
292 EthAddr: test.EthToken.EthAddr,
293 Name: test.EthToken.Name,
294 Symbol: test.EthToken.Symbol,
295 Decimals: test.EthToken.Decimals,
296 USD: ðUSD,
297 USDUpdate: ðNow,
298 })
299 err = api.h.UpdateTokenValue(test.EthToken.Symbol, ethUSD)
300 if err != nil {
301 panic(err)
302 }
303 for _, block := range blocksData {
304 // Insert block into HistoryDB
305 // nolint reason: block is used as read only in the function
306 if err := api.h.AddBlockSCData(&block); err != nil { //nolint:gosec
307 log.Error(err)
308 panic(err)
309 }
310 // Extract data
311 commonBlocks = append(commonBlocks, block.Block)
312 for i, tkn := range block.Rollup.AddedTokens {
313 token := historydb.TokenWithUSD{
314 TokenID: tkn.TokenID,
315 EthBlockNum: tkn.EthBlockNum,
316 EthAddr: tkn.EthAddr,
317 Name: tkn.Name,
318 Symbol: tkn.Symbol,
319 Decimals: tkn.Decimals,
320 }
321 value := float64(i + 423)
322 now := time.Now().UTC()
323 token.USD = &value
324 token.USDUpdate = &now
325 // Set value in DB
326 err = api.h.UpdateTokenValue(token.Symbol, value)
327 if err != nil {
328 panic(err)
329 }
330 testTokens = append(testTokens, token)
331 }
332 // Set USD value for tokens in DB
333 commonL1Txs = append(commonL1Txs, block.Rollup.L1UserTxs...)
334 for _, batch := range block.Rollup.Batches {
335 commonL2Txs = append(commonL2Txs, batch.L2Txs...)
336 for i := range batch.CreatedAccounts {
337 batch.CreatedAccounts[i].Nonce = common.Nonce(i)
338 commonAccounts = append(commonAccounts, batch.CreatedAccounts[i])
339 }
340 commonBatches = append(commonBatches, batch.Batch)
341 commonExitTree = append(commonExitTree, batch.ExitTree...)
342 commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...)
343 }
344 }
345
346 // lastBlockNum2 := blocksData[len(blocksData)-1].Block.EthBlockNum
347
348 // Add accounts to StateDB
349 for i := 0; i < len(commonAccounts); i++ {
350 if _, err := api.s.CreateAccount(commonAccounts[i].Idx, &commonAccounts[i]); err != nil {
351 panic(err)
352 }
353 }
354
355 // Generate Coordinators and add them to HistoryDB
356 const nCoords = 10
357 commonCoords := test.GenCoordinators(nCoords, commonBlocks)
358 // Update one coordinator to test behaviour when bidder address is repeated
359 updatedCoordBlock := commonCoords[len(commonCoords)-1].EthBlockNum
360 commonCoords = append(commonCoords, common.Coordinator{
361 Bidder: commonCoords[0].Bidder,
362 Forger: commonCoords[0].Forger,
363 EthBlockNum: updatedCoordBlock,
364 URL: commonCoords[0].URL + ".new",
365 })
366 if err := api.h.AddCoordinators(commonCoords); err != nil {
367 panic(err)
368 }
369
370 // Test next forgers
371 // Set auction vars
372 // Slots 3 and 6 will have bids that will be invalidated because of minBid update
373 // Slots 4 and 7 will have valid bids, the rest will be cordinator slots
374 var slot3MinBid int64 = 3
375 var slot4MinBid int64 = 4
376 var slot6MinBid int64 = 6
377 var slot7MinBid int64 = 7
378 // First update will indicate how things behave from slot 0
379 var defaultSlotSetBid [6]*big.Int = [6]*big.Int{
380 big.NewInt(10), // Slot 0 min bid
381 big.NewInt(10), // Slot 1 min bid
382 big.NewInt(10), // Slot 2 min bid
383 big.NewInt(slot3MinBid), // Slot 3 min bid
384 big.NewInt(slot4MinBid), // Slot 4 min bid
385 big.NewInt(10), // Slot 5 min bid
386 }
387 auctionVars := common.AuctionVariables{
388 EthBlockNum: int64(2),
389 DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
390 DefaultSlotSetBid: defaultSlotSetBid,
391 DefaultSlotSetBidSlotNum: 0,
392 Outbidding: uint16(1),
393 SlotDeadline: uint8(20),
394 BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
395 BootCoordinatorURL: "https://boot.coordinator.io",
396 ClosedAuctionSlots: uint16(10),
397 OpenAuctionSlots: uint16(20),
398 }
399 if err := api.h.AddAuctionVars(&auctionVars); err != nil {
400 panic(err)
401 }
402 // Last update in auction vars will indicate how things will behave from slot 5
403 defaultSlotSetBid = [6]*big.Int{
404 big.NewInt(10), // Slot 5 min bid
405 big.NewInt(slot6MinBid), // Slot 6 min bid
406 big.NewInt(slot7MinBid), // Slot 7 min bid
407 big.NewInt(10), // Slot 8 min bid
408 big.NewInt(10), // Slot 9 min bid
409 big.NewInt(10), // Slot 10 min bid
410 }
411 auctionVars = common.AuctionVariables{
412 EthBlockNum: int64(3),
413 DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
414 DefaultSlotSetBid: defaultSlotSetBid,
415 DefaultSlotSetBidSlotNum: 5,
416 Outbidding: uint16(1),
417 SlotDeadline: uint8(20),
418 BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
419 BootCoordinatorURL: "https://boot.coordinator.io",
420 ClosedAuctionSlots: uint16(10),
421 OpenAuctionSlots: uint16(20),
422 }
423 if err := api.h.AddAuctionVars(&auctionVars); err != nil {
424 panic(err)
425 }
426
427 // Generate Bids and add them to HistoryDB
428 bids := []common.Bid{}
429 // Slot 1 and 2, no bids, wins boot coordinator
430 // Slot 3, below what's going to be the minimum (wins boot coordinator)
431 bids = append(bids, common.Bid{
432 SlotNum: 3,
433 BidValue: big.NewInt(slot3MinBid - 1),
434 EthBlockNum: commonBlocks[0].Num,
435 Bidder: commonCoords[0].Bidder,
436 })
437 // Slot 4, valid bid (wins bidder)
438 bids = append(bids, common.Bid{
439 SlotNum: 4,
440 BidValue: big.NewInt(slot4MinBid),
441 EthBlockNum: commonBlocks[0].Num,
442 Bidder: commonCoords[0].Bidder,
443 })
444 // Slot 5 no bids, wins boot coordinator
445 // Slot 6, below what's going to be the minimum (wins boot coordinator)
446 bids = append(bids, common.Bid{
447 SlotNum: 6,
448 BidValue: big.NewInt(slot6MinBid - 1),
449 EthBlockNum: commonBlocks[0].Num,
450 Bidder: commonCoords[0].Bidder,
451 })
452 // Slot 7, valid bid (wins bidder)
453 bids = append(bids, common.Bid{
454 SlotNum: 7,
455 BidValue: big.NewInt(slot7MinBid),
456 EthBlockNum: commonBlocks[0].Num,
457 Bidder: commonCoords[0].Bidder,
458 })
459 if err = api.h.AddBids(bids); err != nil {
460 panic(err)
461 }
462 bootForger := NextForger{
463 Coordinator: historydb.CoordinatorAPI{
464 Bidder: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"),
465 Forger: ethCommon.HexToAddress("0x0111111111111111111111111111111111111111"),
466 URL: "https://bootCoordinator",
467 },
468 }
469 // Set next forgers: set all as boot coordinator then replace the non boot coordinators
470 nextForgers := []NextForger{}
471 var initBlock int64 = 140
472 var deltaBlocks int64 = 40
473 for i := 1; i < int(auctionVars.ClosedAuctionSlots)+2; i++ {
474 fromBlock := initBlock + deltaBlocks*int64(i-1)
475 bootForger.Period = Period{
476 SlotNum: int64(i),
477 FromBlock: fromBlock,
478 ToBlock: fromBlock + deltaBlocks - 1,
479 }
480 nextForgers = append(nextForgers, bootForger)
481 }
482 // Set next forgers that aren't the boot coordinator
483 nonBootForger := historydb.CoordinatorAPI{
484 Bidder: commonCoords[0].Bidder,
485 Forger: commonCoords[0].Forger,
486 URL: commonCoords[0].URL + ".new",
487 }
488 // Slot 4
489 nextForgers[3].Coordinator = nonBootForger
490 // Slot 7
491 nextForgers[6].Coordinator = nonBootForger
492
493 var buckets [common.RollupConstNumBuckets]common.BucketParams
494 for i := range buckets {
495 buckets[i].CeilUSD = big.NewInt(int64(i) * 10)
496 buckets[i].Withdrawals = big.NewInt(int64(i) * 100)
497 buckets[i].BlockWithdrawalRate = big.NewInt(int64(i) * 1000)
498 buckets[i].MaxWithdrawals = big.NewInt(int64(i) * 10000)
499 }
500
501 // Generate SC vars and add them to HistoryDB (if needed)
502 rollupVars := common.RollupVariables{
503 EthBlockNum: int64(3),
504 FeeAddToken: big.NewInt(100),
505 ForgeL1L2BatchTimeout: int64(44),
506 WithdrawalDelay: uint64(3000),
507 Buckets: buckets,
508 SafeMode: false,
509 }
510
511 wdelayerVars := common.WDelayerVariables{
512 WithdrawalDelay: uint64(3000),
513 }
514
515 // Generate test data, as expected to be received/sended from/to the API
516 testCoords := genTestCoordinators(commonCoords)
517 testBids := genTestBids(commonBlocks, testCoords, bids)
518 testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
519 testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
520 testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
521 poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
522 tc = testCommon{
523 blocks: commonBlocks,
524 tokens: testTokens,
525 batches: testBatches,
526 fullBatches: testFullBatches,
527 coordinators: testCoords,
528 accounts: genTestAccounts(commonAccounts, testTokens),
529 txs: testTxs,
530 exits: testExits,
531 poolTxsToSend: poolTxsToSend,
532 poolTxsToReceive: poolTxsToReceive,
533 auths: genTestAuths(test.GenAuths(5, _config.ChainID, _config.HermezAddress)),
534 router: router,
535 bids: testBids,
536 slots: api.genTestSlots(
537 20,
538 commonBlocks[len(commonBlocks)-1].Num,
539 testBids,
540 auctionVars,
541 ),
542 auctionVars: auctionVars,
543 rollupVars: rollupVars,
544 wdelayerVars: wdelayerVars,
545 nextForgers: nextForgers,
546 }
547
548 // Run tests
549 result := m.Run()
550 // Fake server
551 if os.Getenv("FAKE_SERVER") == "yes" {
552 for {
553 log.Info("Running fake server at " + apiURL + " until ^C is received")
554 time.Sleep(30 * time.Second)
555 }
556 }
557 // Stop server
558 if err := server.Shutdown(context.Background()); err != nil {
559 panic(err)
560 }
561 if err := database.Close(); err != nil {
562 panic(err)
563 }
564 if err := os.RemoveAll(dir); err != nil {
565 panic(err)
566 }
567 os.Exit(result)
568}
569
570func doGoodReqPaginated(
571 path, order string,
572 iterStruct Pendinger,
573 appendIter func(res interface{}),
574) error {
575 var next uint64
576 firstIte := true
577 expectedTotal := 0
578 totalReceived := 0
579 for {
580 // Calculate fromItem
581 iterPath := path
582 if !firstIte {
583 iterPath += "&fromItem=" + strconv.Itoa(int(next))
584 }
585 // Call API to get this iteration items
586 iterStruct = iterStruct.New()
587 if err := doGoodReq(
588 "GET", iterPath+"&order="+order, nil,
589 iterStruct,
590 ); err != nil {
591 return tracerr.Wrap(err)
592 }
593 appendIter(iterStruct)
594 // Keep iterating?
595 remaining, lastID := iterStruct.GetPending()
596 if remaining == 0 {
597 break
598 }
599 if order == historydb.OrderDesc {
600 next = lastID - 1
601 } else {
602 next = lastID + 1
603 }
604 // Check that the expected amount of items is consistent across iterations
605 totalReceived += iterStruct.Len()
606 if firstIte {
607 firstIte = false
608 expectedTotal = totalReceived + int(remaining)
609 }
610 if expectedTotal != totalReceived+int(remaining) {
611 panic(fmt.Sprintf(
612 "pagination error, totalReceived + remaining should be %d, but is %d",
613 expectedTotal, totalReceived+int(remaining),
614 ))
615 }
616 }
617 return nil
618}
619
620func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
621 ctx := context.Background()
622 client := &http.Client{}
623 httpReq, err := http.NewRequest(method, path, reqBody)
624 if err != nil {
625 return tracerr.Wrap(err)
626 }
627 if reqBody != nil {
628 httpReq.Header.Add("Content-Type", "application/json")
629 }
630 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
631 if err != nil {
632 return tracerr.Wrap(err)
633 }
634 // Validate request against swagger spec
635 requestValidationInput := &swagger.RequestValidationInput{
636 Request: httpReq,
637 PathParams: pathParams,
638 Route: route,
639 }
640 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
641 return tracerr.Wrap(err)
642 }
643 // Do API call
644 resp, err := client.Do(httpReq)
645 if err != nil {
646 return tracerr.Wrap(err)
647 }
648 if resp.Body == nil && returnStruct != nil {
649 return tracerr.Wrap(errors.New("Nil body"))
650 }
651 //nolint
652 defer resp.Body.Close()
653 body, err := ioutil.ReadAll(resp.Body)
654 if err != nil {
655 return tracerr.Wrap(err)
656 }
657 if resp.StatusCode != 200 {
658 return tracerr.Wrap(fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body)))
659 }
660 if returnStruct == nil {
661 return nil
662 }
663 // Unmarshal body into return struct
664 if err := json.Unmarshal(body, returnStruct); err != nil {
665 log.Error("invalid json: " + string(body))
666 log.Error(err)
667 return tracerr.Wrap(err)
668 }
669 // log.Info(string(body))
670 // Validate response against swagger spec
671 responseValidationInput := &swagger.ResponseValidationInput{
672 RequestValidationInput: requestValidationInput,
673 Status: resp.StatusCode,
674 Header: resp.Header,
675 }
676 responseValidationInput = responseValidationInput.SetBodyBytes(body)
677 return swagger.ValidateResponse(ctx, responseValidationInput)
678}
679
680func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
681 ctx := context.Background()
682 client := &http.Client{}
683 httpReq, _ := http.NewRequest(method, path, reqBody)
684 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
685 if err != nil {
686 return tracerr.Wrap(err)
687 }
688 // Validate request against swagger spec
689 requestValidationInput := &swagger.RequestValidationInput{
690 Request: httpReq,
691 PathParams: pathParams,
692 Route: route,
693 }
694 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
695 if expectedResponseCode != 400 {
696 return tracerr.Wrap(err)
697 }
698 log.Warn("The request does not match the API spec")
699 }
700 // Do API call
701 resp, err := client.Do(httpReq)
702 if err != nil {
703 return tracerr.Wrap(err)
704 }
705 if resp.Body == nil {
706 return tracerr.Wrap(errors.New("Nil body"))
707 }
708 //nolint
709 defer resp.Body.Close()
710 body, err := ioutil.ReadAll(resp.Body)
711 if err != nil {
712 return tracerr.Wrap(err)
713 }
714 if resp.StatusCode != expectedResponseCode {
715 return tracerr.Wrap(fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body)))
716 }
717 // Validate response against swagger spec
718 responseValidationInput := &swagger.ResponseValidationInput{
719 RequestValidationInput: requestValidationInput,
720 Status: resp.StatusCode,
721 Header: resp.Header,
722 }
723 responseValidationInput = responseValidationInput.SetBodyBytes(body)
724 return swagger.ValidateResponse(ctx, responseValidationInput)
725}
726
727// test helpers
728
729func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
730 for i := 0; i < len(blocks); i++ {
731 if blocks[i].Num == blockNum {
732 return blocks[i].Timestamp
733 }
734 }
735 panic("timesamp not found")
736}
737
738func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
739 for i := 0; i < len(tokens); i++ {
740 if tokens[i].TokenID == id {
741 return tokens[i]
742 }
743 }
744 panic("token not found")
745}
746
747func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
748 for _, acc := range accs {
749 if idx == acc.Idx {
750 return getTokenByID(acc.TokenID, tokens)
751 }
752 }
753 panic("token not found")
754}
755
756func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
757 for _, acc := range accs {
758 if acc.Idx == idx {
759 return &acc
760 }
761 }
762 panic("account not found")
763}
764
765func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
766 for _, b := range blocks {
767 if b.Num == ethBlockNum {
768 return b
769 }
770 }
771 panic("block not found")
772}
773
774func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
775 var coordLastUpdate historydb.CoordinatorAPI
776 found := false
777 for _, c := range coordinators {
778 if c.Bidder == bidder {
779 coordLastUpdate = c
780 found = true
781 }
782 }
783 if !found {
784 panic("coordinator not found")
785 }
786 return coordLastUpdate
787}