hermez-node/api/exits_test.go
api/exits_test.goRaw
1package api
2
3import (
4 "fmt"
5 "testing"
6
7 "github.com/hermeznetwork/hermez-node/common"
8 "github.com/hermeznetwork/hermez-node/db/historydb"
9 "github.com/mitchellh/copystructure"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14type testCVP struct {
15 Root string
16 Siblings []string
17 OldKey string
18 OldValue string
19 IsOld0 bool
20 Key string
21 Value string
22 Fnc int
23}
24
25type testExit struct {
26 ItemID uint64 `json:"itemId"`
27 BatchNum common.BatchNum `json:"batchNum"`
28 AccountIdx string `json:"accountIndex"`
29 MerkleProof testCVP `json:"merkleProof"`
30 Balance string `json:"balance"`
31 InstantWithdrawn *int64 `json:"instantWithdrawn"`
32 DelayedWithdrawRequest *int64 `json:"delayedWithdrawRequest"`
33 DelayedWithdrawn *int64 `json:"delayedWithdrawn"`
34 Token historydb.TokenWithUSD `json:"token"`
35}
36
37type testExitsResponse struct {
38 Exits []testExit `json:"exits"`
39 PendingItems uint64 `json:"pendingItems"`
40}
41
42func (t testExitsResponse) GetPending() (pendingItems, lastItemID uint64) {
43 pendingItems = t.PendingItems
44 lastItemID = t.Exits[len(t.Exits)-1].ItemID
45 return pendingItems, lastItemID
46}
47
48func (t *testExitsResponse) Len() int {
49 return len(t.Exits)
50}
51
52func genTestExits(
53 commonExits []common.ExitInfo,
54 tokens []historydb.TokenWithUSD,
55 accs []common.Account,
56 usrIdxs []string,
57) (usrExits, allExits []testExit) {
58 allExits = []testExit{}
59 for _, exit := range commonExits {
60 token := getTokenByIdx(exit.AccountIdx, tokens, accs)
61 siblings := []string{}
62 for i := 0; i < len(exit.MerkleProof.Siblings); i++ {
63 siblings = append(siblings, exit.MerkleProof.Siblings[i].String())
64 }
65 allExits = append(allExits, testExit{
66 BatchNum: exit.BatchNum,
67 AccountIdx: idxToHez(exit.AccountIdx, token.Symbol),
68 MerkleProof: testCVP{
69 Root: exit.MerkleProof.Root.String(),
70 Siblings: siblings,
71 OldKey: exit.MerkleProof.OldKey.String(),
72 OldValue: exit.MerkleProof.OldValue.String(),
73 IsOld0: exit.MerkleProof.IsOld0,
74 Key: exit.MerkleProof.Key.String(),
75 Value: exit.MerkleProof.Value.String(),
76 Fnc: exit.MerkleProof.Fnc,
77 },
78 Balance: exit.Balance.String(),
79 InstantWithdrawn: exit.InstantWithdrawn,
80 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
81 DelayedWithdrawn: exit.DelayedWithdrawn,
82 Token: token,
83 })
84 }
85 usrExits = []testExit{}
86 for _, exit := range allExits {
87 for _, idx := range usrIdxs {
88 if idx == exit.AccountIdx {
89 usrExits = append(usrExits, exit)
90 break
91 }
92 }
93 }
94 return usrExits, allExits
95}
96
97func TestGetExits(t *testing.T) {
98 endpoint := apiURL + "exits"
99 fetchedExits := []testExit{}
100 appendIter := func(intr interface{}) {
101 for i := 0; i < len(intr.(*testExitsResponse).Exits); i++ {
102 tmp, err := copystructure.Copy(intr.(*testExitsResponse).Exits[i])
103 if err != nil {
104 panic(err)
105 }
106 fetchedExits = append(fetchedExits, tmp.(testExit))
107 }
108 }
109 // Get all (no filters)
110 limit := 8
111 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
112 err := doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
113 assert.NoError(t, err)
114 assertExitAPIs(t, tc.exits, fetchedExits)
115
116 // Get by ethAddr
117 fetchedExits = []testExit{}
118 limit = 7
119 path = fmt.Sprintf(
120 "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
121 endpoint, tc.usrAddr, limit,
122 )
123 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
124 assert.NoError(t, err)
125 assertExitAPIs(t, tc.usrExits, fetchedExits)
126 // Get by bjj
127 fetchedExits = []testExit{}
128 limit = 6
129 path = fmt.Sprintf(
130 "%s?BJJ=%s&limit=%d&fromItem=",
131 endpoint, tc.usrBjj, limit,
132 )
133 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
134 assert.NoError(t, err)
135 assertExitAPIs(t, tc.usrExits, fetchedExits)
136 // Get by tokenID
137 fetchedExits = []testExit{}
138 limit = 5
139 tokenID := tc.exits[0].Token.TokenID
140 path = fmt.Sprintf(
141 "%s?tokenId=%d&limit=%d&fromItem=",
142 endpoint, tokenID, limit,
143 )
144 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
145 assert.NoError(t, err)
146 tokenIDExits := []testExit{}
147 for i := 0; i < len(tc.exits); i++ {
148 if tc.exits[i].Token.TokenID == tokenID {
149 tokenIDExits = append(tokenIDExits, tc.exits[i])
150 }
151 }
152 assertExitAPIs(t, tokenIDExits, fetchedExits)
153 // idx
154 fetchedExits = []testExit{}
155 limit = 4
156 idx := tc.exits[0].AccountIdx
157 path = fmt.Sprintf(
158 "%s?accountIndex=%s&limit=%d&fromItem=",
159 endpoint, idx, limit,
160 )
161 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
162 assert.NoError(t, err)
163 idxExits := []testExit{}
164 for i := 0; i < len(tc.exits); i++ {
165 if tc.exits[i].AccountIdx[6:] == idx[6:] {
166 idxExits = append(idxExits, tc.exits[i])
167 }
168 }
169 assertExitAPIs(t, idxExits, fetchedExits)
170 // batchNum
171 fetchedExits = []testExit{}
172 limit = 3
173 batchNum := tc.exits[0].BatchNum
174 path = fmt.Sprintf(
175 "%s?batchNum=%d&limit=%d&fromItem=",
176 endpoint, batchNum, limit,
177 )
178 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
179 assert.NoError(t, err)
180 batchNumExits := []testExit{}
181 for i := 0; i < len(tc.exits); i++ {
182 if tc.exits[i].BatchNum == batchNum {
183 batchNumExits = append(batchNumExits, tc.exits[i])
184 }
185 }
186 assertExitAPIs(t, batchNumExits, fetchedExits)
187 // OnlyPendingWithdraws
188 fetchedExits = []testExit{}
189 limit = 7
190 path = fmt.Sprintf(
191 "%s?&onlyPendingWithdraws=%t&limit=%d&fromItem=",
192 endpoint, true, limit,
193 )
194 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
195 assert.NoError(t, err)
196 pendingExits := []testExit{}
197 for i := 0; i < len(tc.exits); i++ {
198 if tc.exits[i].InstantWithdrawn == nil && tc.exits[i].DelayedWithdrawn == nil {
199 pendingExits = append(pendingExits, tc.exits[i])
200 }
201 }
202 assertExitAPIs(t, pendingExits, fetchedExits)
203 // Multiple filters
204 fetchedExits = []testExit{}
205 limit = 1
206 path = fmt.Sprintf(
207 "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
208 endpoint, batchNum, tokenID, limit,
209 )
210 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
211 assert.NoError(t, err)
212 mixedExits := []testExit{}
213 flipedExits := []testExit{}
214 for i := 0; i < len(tc.exits); i++ {
215 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
216 mixedExits = append(mixedExits, tc.exits[i])
217 }
218 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
219 }
220 assertExitAPIs(t, mixedExits, fetchedExits)
221 // All, in reverse order
222 fetchedExits = []testExit{}
223 limit = 5
224 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
225 err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter)
226 assert.NoError(t, err)
227 assertExitAPIs(t, flipedExits, fetchedExits)
228 // 400
229 path = fmt.Sprintf(
230 "%s?accountIndex=%s&hermezEthereumAddress=%s",
231 endpoint, idx, tc.usrAddr,
232 )
233 err = doBadReq("GET", path, nil, 400)
234 assert.NoError(t, err)
235 path = fmt.Sprintf("%s?tokenId=X", endpoint)
236 err = doBadReq("GET", path, nil, 400)
237 assert.NoError(t, err)
238 // 404
239 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
240 err = doBadReq("GET", path, nil, 404)
241 assert.NoError(t, err)
242 path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
243 err = doBadReq("GET", path, nil, 404)
244 assert.NoError(t, err)
245}
246
247func TestGetExit(t *testing.T) {
248 // Get all txs by their ID
249 endpoint := apiURL + "exits/"
250 fetchedExits := []testExit{}
251 for _, exit := range tc.exits {
252 fetchedExit := testExit{}
253 assert.NoError(
254 t, doGoodReq(
255 "GET",
256 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
257 nil, &fetchedExit,
258 ),
259 )
260 fetchedExits = append(fetchedExits, fetchedExit)
261 }
262 assertExitAPIs(t, tc.exits, fetchedExits)
263 // 400
264 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
265 assert.NoError(t, err)
266 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
267 assert.NoError(t, err)
268 // 404
269 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
270 assert.NoError(t, err)
271}
272
273func assertExitAPIs(t *testing.T, expected, actual []testExit) {
274 require.Equal(t, len(expected), len(actual))
275 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
276 actual[i].ItemID = 0
277 actual[i].Token.ItemID = 0
278 if expected[i].Token.USDUpdate == nil {
279 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
280 } else {
281 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
282 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
283 }
284 assert.Equal(t, expected[i], actual[i])
285 }
286}