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 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 []accountCreationAuthAPI
54 router *swagger.Router
55}
56
57var tc testCommon
58var config configAPI
59
60// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
61// emulating the task of the synchronizer in order to have data to be returned
62// by the API endpoints that will be tested
63func TestMain(m *testing.M) {
64 // Initializations
65 // Swagger
66 router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
67 // HistoryDB
68 pass := os.Getenv("POSTGRES_PASS")
69 database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
70 if err != nil {
71 panic(err)
72 }
73 hdb := historydb.NewHistoryDB(database)
74 err = hdb.Reorg(-1)
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.CleanL2DB(l2DB.DB())
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 // Clean DB
162 err = h.Reorg(0)
163 if err != nil {
164 panic(err)
165 }
166 // Gen blocks and add them to DB
167 const nBlocks = 5
168 blocks := test.GenBlocks(1, nBlocks+1)
169 err = h.AddBlocks(blocks)
170 if err != nil {
171 panic(err)
172 }
173 // Gen tokens and add them to DB
174 const nTokens = 10
175 tokens := test.GenTokens(nTokens, blocks)
176 err = h.AddTokens(tokens)
177 if err != nil {
178 panic(err)
179 }
180 // Set token value
181 tokensUSD := []historydb.TokenWithUSD{}
182 for i, tkn := range tokens {
183 token := historydb.TokenWithUSD{
184 TokenID: tkn.TokenID,
185 EthBlockNum: tkn.EthBlockNum,
186 EthAddr: tkn.EthAddr,
187 Name: tkn.Name,
188 Symbol: tkn.Symbol,
189 Decimals: tkn.Decimals,
190 }
191 // Set value of 50% of the tokens
192 if i%2 != 0 {
193 value := float64(i) * 1.234567
194 now := time.Now().UTC()
195 token.USD = &value
196 token.USDUpdate = &now
197 err = h.UpdateTokenValue(token.Symbol, value)
198 if err != nil {
199 panic(err)
200 }
201 }
202 tokensUSD = append(tokensUSD, token)
203 }
204 // Gen batches and add them to DB
205 const nBatches = 10
206 batches := test.GenBatches(nBatches, blocks)
207 err = h.AddBatches(batches)
208 if err != nil {
209 panic(err)
210 }
211 // Gen accounts and add them to HistoryDB and StateDB
212 const totalAccounts = 40
213 const userAccounts = 4
214 usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
215 privK := babyjub.NewRandPrivKey()
216 usrBjj := privK.Public()
217 accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
218 err = h.AddAccounts(accs)
219 if err != nil {
220 panic(err)
221 }
222 for i := 0; i < len(accs); i++ {
223 if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
224 panic(err)
225 }
226 }
227 // helper to vinculate user related resources
228 usrIdxs := []string{}
229 for _, acc := range accs {
230 if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
231 for _, token := range tokens {
232 if token.TokenID == acc.TokenID {
233 usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
234 }
235 }
236 }
237 }
238 // Gen exits and add them to DB
239 const totalExits = 40
240 exits := test.GenExitTree(totalExits, batches, accs)
241 err = h.AddExitTree(exits)
242 if err != nil {
243 panic(err)
244 }
245
246 // L1 and L2 txs need to be sorted in a combined way
247 // Gen L1Txs
248 const totalL1Txs = 40
249 const userL1Txs = 4
250 usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
251 // Gen L2Txs
252 const totalL2Txs = 20
253 const userL2Txs = 4
254 usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
255 // Sort txs
256 sortedTxs := []txSortFielder{}
257 for i := 0; i < len(usrL1Txs); i++ {
258 wL1 := wrappedL1(usrL1Txs[i])
259 sortedTxs = append(sortedTxs, &wL1)
260 }
261 for i := 0; i < len(othrL1Txs); i++ {
262 wL1 := wrappedL1(othrL1Txs[i])
263 sortedTxs = append(sortedTxs, &wL1)
264 }
265 for i := 0; i < len(usrL2Txs); i++ {
266 wL2 := wrappedL2(usrL2Txs[i])
267 sortedTxs = append(sortedTxs, &wL2)
268 }
269 for i := 0; i < len(othrL2Txs); i++ {
270 wL2 := wrappedL2(othrL2Txs[i])
271 sortedTxs = append(sortedTxs, &wL2)
272 }
273 sort.Sort(txsSort(sortedTxs))
274 // Store txs to DB
275 for _, genericTx := range sortedTxs {
276 l1 := genericTx.L1()
277 l2 := genericTx.L2()
278 if l1 != nil {
279 err = h.AddL1Txs([]common.L1Tx{*l1})
280 if err != nil {
281 panic(err)
282 }
283 } else if l2 != nil {
284 err = h.AddL2Txs([]common.L2Tx{*l2})
285 if err != nil {
286 panic(err)
287 }
288 } else {
289 panic("should be l1 or l2")
290 }
291 }
292
293 // Transform exits to API
294 exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
295 historyExits := []historydb.HistoryExit{}
296 for _, exit := range exits {
297 token := getTokenByIdx(exit.AccountIdx, tokensUSD, accs)
298 historyExits = append(historyExits, historydb.HistoryExit{
299 BatchNum: exit.BatchNum,
300 AccountIdx: exit.AccountIdx,
301 MerkleProof: exit.MerkleProof,
302 Balance: exit.Balance,
303 InstantWithdrawn: exit.InstantWithdrawn,
304 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
305 DelayedWithdrawn: exit.DelayedWithdrawn,
306 TokenID: token.TokenID,
307 TokenEthBlockNum: token.EthBlockNum,
308 TokenEthAddr: token.EthAddr,
309 TokenName: token.Name,
310 TokenSymbol: token.Symbol,
311 TokenDecimals: token.Decimals,
312 TokenUSD: token.USD,
313 TokenUSDUpdate: token.USDUpdate,
314 })
315 }
316 return historyExitsToAPI(historyExits)
317 }
318 apiExits := exitsToAPIExits(exits, accs, tokens)
319 // sort.Sort(apiExits)
320 usrExits := []exitAPI{}
321 for _, exit := range apiExits {
322 for _, idx := range usrIdxs {
323 if idx == exit.AccountIdx {
324 usrExits = append(usrExits, exit)
325 }
326 }
327 }
328
329 // Coordinators
330 const nCoords = 10
331 coords := test.GenCoordinators(nCoords, blocks)
332 err = hdb.AddCoordinators(coords)
333 if err != nil {
334 panic(err)
335 }
336 fromItem := uint(0)
337 limit := uint(99999)
338 coordinators, _, err := hdb.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
339 if err != nil {
340 panic(err)
341 }
342 // Account creation auth
343 const nAuths = 5
344 auths := test.GenAuths(nAuths)
345 // Transform auths to API format
346 apiAuths := []accountCreationAuthAPI{}
347 for _, auth := range auths {
348 apiAuth := accountCreationAuthToAPI(auth)
349 apiAuths = append(apiAuths, *apiAuth)
350 }
351 // Set testCommon
352 usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
353 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.
354 tc = testCommon{
355 blocks: blocks,
356 tokens: tokensUSD,
357 batches: genTestBatches(blocks, batches),
358 coordinators: coordinators,
359 usrAddr: ethAddrToHez(usrAddr),
360 usrBjj: bjjToString(usrBjj),
361 accs: accs,
362 usrTxs: usrTxs,
363 allTxs: allTxs,
364 exits: apiExits,
365 usrExits: usrExits,
366 poolTxsToSend: poolTxsToSend,
367 poolTxsToReceive: poolTxsToReceive,
368 auths: apiAuths,
369 router: router,
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 TestAccountCreationAuth(t *testing.T) {
575 // POST
576 endpoint := apiURL + "account-creation-authorization"
577 for _, auth := range tc.auths {
578 jsonAuthBytes, err := json.Marshal(auth)
579 assert.NoError(t, err)
580 jsonAuthReader := bytes.NewReader(jsonAuthBytes)
581 fmt.Println(string(jsonAuthBytes))
582 assert.NoError(
583 t, doGoodReq(
584 "POST",
585 endpoint,
586 jsonAuthReader, nil,
587 ),
588 )
589 }
590 // GET
591 endpoint += "/"
592 for _, auth := range tc.auths {
593 fetchedAuth := accountCreationAuthAPI{}
594 assert.NoError(
595 t, doGoodReq(
596 "GET",
597 endpoint+auth.EthAddr,
598 nil, &fetchedAuth,
599 ),
600 )
601 assertAuth(t, auth, fetchedAuth)
602 }
603 // POST
604 // 400
605 // Wrong addr
606 badAuth := tc.auths[0]
607 badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
608 jsonAuthBytes, err := json.Marshal(badAuth)
609 assert.NoError(t, err)
610 jsonAuthReader := bytes.NewReader(jsonAuthBytes)
611 err = doBadReq("POST", endpoint, jsonAuthReader, 400)
612 assert.NoError(t, err)
613 // Wrong signature
614 badAuth = tc.auths[0]
615 badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
616 badAuth.Signature += "F"
617 jsonAuthBytes, err = json.Marshal(badAuth)
618 assert.NoError(t, err)
619 jsonAuthReader = bytes.NewReader(jsonAuthBytes)
620 err = doBadReq("POST", endpoint, jsonAuthReader, 400)
621 assert.NoError(t, err)
622 // GET
623 // 400
624 err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
625 assert.NoError(t, err)
626 // 404
627 err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
628 assert.NoError(t, err)
629}
630
631func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
632 // timestamp should be very close to now
633 assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
634 expected.Timestamp = actual.Timestamp
635 assert.Equal(t, expected, actual)
636}
637
638func doGoodReqPaginated(
639 path, order string,
640 iterStruct db.Paginationer,
641 appendIter func(res interface{}),
642) error {
643 next := 0
644 for {
645 // Call API to get this iteration items
646 iterPath := path
647 if next == 0 && order == historydb.OrderDesc {
648 // Fetch first item in reverse order
649 iterPath += "99999"
650 } else {
651 // Fetch from next item or 0 if it's ascending order
652 iterPath += strconv.Itoa(next)
653 }
654 if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
655 return err
656 }
657 appendIter(iterStruct)
658 // Keep iterating?
659 pag := iterStruct.GetPagination()
660 if order == historydb.OrderAsc {
661 if pag.LastReturnedItem == pag.LastItem { // No
662 break
663 } else { // Yes
664 next = pag.LastReturnedItem + 1
665 }
666 } else {
667 if pag.FirstReturnedItem == pag.FirstItem { // No
668 break
669 } else { // Yes
670 next = pag.FirstReturnedItem - 1
671 }
672 }
673 }
674 return nil
675}
676
677func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
678 ctx := context.Background()
679 client := &http.Client{}
680 httpReq, err := http.NewRequest(method, path, reqBody)
681 if err != nil {
682 return err
683 }
684 if reqBody != nil {
685 httpReq.Header.Add("Content-Type", "application/json")
686 }
687 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
688 if err != nil {
689 return err
690 }
691 // Validate request against swagger spec
692 requestValidationInput := &swagger.RequestValidationInput{
693 Request: httpReq,
694 PathParams: pathParams,
695 Route: route,
696 }
697 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
698 return err
699 }
700 // Do API call
701 resp, err := client.Do(httpReq)
702 if err != nil {
703 return err
704 }
705 if resp.Body == nil && returnStruct != nil {
706 return 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 err
713 }
714 if resp.StatusCode != 200 {
715 return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
716 }
717 if returnStruct == nil {
718 return nil
719 }
720 // Unmarshal body into return struct
721 if err := json.Unmarshal(body, returnStruct); err != nil {
722 log.Error("invalid json: " + string(body))
723 return err
724 }
725 // Validate response against swagger spec
726 responseValidationInput := &swagger.ResponseValidationInput{
727 RequestValidationInput: requestValidationInput,
728 Status: resp.StatusCode,
729 Header: resp.Header,
730 }
731 responseValidationInput = responseValidationInput.SetBodyBytes(body)
732 return swagger.ValidateResponse(ctx, responseValidationInput)
733}
734
735func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
736 ctx := context.Background()
737 client := &http.Client{}
738 httpReq, _ := http.NewRequest(method, path, reqBody)
739 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
740 if err != nil {
741 return err
742 }
743 // Validate request against swagger spec
744 requestValidationInput := &swagger.RequestValidationInput{
745 Request: httpReq,
746 PathParams: pathParams,
747 Route: route,
748 }
749 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
750 if expectedResponseCode != 400 {
751 return err
752 }
753 log.Warn("The request does not match the API spec")
754 }
755 // Do API call
756 resp, err := client.Do(httpReq)
757 if err != nil {
758 return err
759 }
760 if resp.Body == nil {
761 return errors.New("Nil body")
762 }
763 //nolint
764 defer resp.Body.Close()
765 body, err := ioutil.ReadAll(resp.Body)
766 if err != nil {
767 return err
768 }
769 if resp.StatusCode != expectedResponseCode {
770 return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
771 }
772 // Validate response against swagger spec
773 responseValidationInput := &swagger.ResponseValidationInput{
774 RequestValidationInput: requestValidationInput,
775 Status: resp.StatusCode,
776 Header: resp.Header,
777 }
778 responseValidationInput = responseValidationInput.SetBodyBytes(body)
779 return swagger.ValidateResponse(ctx, responseValidationInput)
780}
781
782// test helpers
783
784func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
785 for i := 0; i < len(blocks); i++ {
786 if blocks[i].EthBlockNum == blockNum {
787 return blocks[i].Timestamp
788 }
789 }
790 panic("timesamp not found")
791}
792
793func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
794 for i := 0; i < len(tokens); i++ {
795 if tokens[i].TokenID == id {
796 return tokens[i]
797 }
798 }
799 panic("token not found")
800}
801
802func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
803 for _, acc := range accs {
804 if idx == acc.Idx {
805 return getTokenByID(acc.TokenID, tokens)
806 }
807 }
808 panic("token not found")
809}
810
811func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
812 for _, acc := range accs {
813 if acc.Idx == idx {
814 return &acc
815 }
816 }
817 panic("account not found")
818}