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 account := tc.accounts[3]
120 path = fmt.Sprintf(
121 "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
122 endpoint, account.EthAddr, limit,
123 )
124 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
125 assert.NoError(t, err)
126 accountIdx := fetchedExits[0].AccountIdx
127 var hermezEthExits []testExit
128 for i := range tc.usrExits {
129 if tc.usrExits[i].AccountIdx == accountIdx {
130 hermezEthExits = append(hermezEthExits, tc.usrExits[i])
131 }
132 }
133 assertExitAPIs(t, hermezEthExits, fetchedExits)
134 // Get by bjj
135 fetchedExits = []testExit{}
136 limit = 6
137 path = fmt.Sprintf(
138 "%s?BJJ=%s&limit=%d&fromItem=",
139 endpoint, account.PublicKey, limit,
140 )
141 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
142 assert.NoError(t, err)
143 assertExitAPIs(t, hermezEthExits, fetchedExits)
144 // Get by tokenID
145 fetchedExits = []testExit{}
146 limit = 5
147 tokenID := tc.exits[0].Token.TokenID
148 path = fmt.Sprintf(
149 "%s?tokenId=%d&limit=%d&fromItem=",
150 endpoint, tokenID, limit,
151 )
152 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
153 assert.NoError(t, err)
154 tokenIDExits := []testExit{}
155 for i := 0; i < len(tc.exits); i++ {
156 if tc.exits[i].Token.TokenID == tokenID {
157 tokenIDExits = append(tokenIDExits, tc.exits[i])
158 }
159 }
160 assertExitAPIs(t, tokenIDExits, fetchedExits)
161 // idx
162 fetchedExits = []testExit{}
163 limit = 4
164 idx := tc.exits[0].AccountIdx
165 path = fmt.Sprintf(
166 "%s?accountIndex=%s&limit=%d&fromItem=",
167 endpoint, idx, limit,
168 )
169 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
170 assert.NoError(t, err)
171 idxExits := []testExit{}
172 for i := 0; i < len(tc.exits); i++ {
173 if tc.exits[i].AccountIdx[6:] == idx[6:] {
174 idxExits = append(idxExits, tc.exits[i])
175 }
176 }
177 assertExitAPIs(t, idxExits, fetchedExits)
178 // batchNum
179 fetchedExits = []testExit{}
180 limit = 3
181 batchNum := tc.exits[0].BatchNum
182 path = fmt.Sprintf(
183 "%s?batchNum=%d&limit=%d&fromItem=",
184 endpoint, batchNum, limit,
185 )
186 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
187 assert.NoError(t, err)
188 batchNumExits := []testExit{}
189 for i := 0; i < len(tc.exits); i++ {
190 if tc.exits[i].BatchNum == batchNum {
191 batchNumExits = append(batchNumExits, tc.exits[i])
192 }
193 }
194 assertExitAPIs(t, batchNumExits, fetchedExits)
195 // OnlyPendingWithdraws
196 fetchedExits = []testExit{}
197 limit = 7
198 path = fmt.Sprintf(
199 "%s?&onlyPendingWithdraws=%t&limit=%d&fromItem=",
200 endpoint, true, limit,
201 )
202 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
203 assert.NoError(t, err)
204 pendingExits := []testExit{}
205 for i := 0; i < len(tc.exits); i++ {
206 if tc.exits[i].InstantWithdrawn == nil && tc.exits[i].DelayedWithdrawn == nil {
207 pendingExits = append(pendingExits, tc.exits[i])
208 }
209 }
210 assertExitAPIs(t, pendingExits, fetchedExits)
211 // Multiple filters
212 fetchedExits = []testExit{}
213 limit = 1
214 path = fmt.Sprintf(
215 "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
216 endpoint, batchNum, tokenID, limit,
217 )
218 err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
219 assert.NoError(t, err)
220 mixedExits := []testExit{}
221 flipedExits := []testExit{}
222 for i := 0; i < len(tc.exits); i++ {
223 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
224 mixedExits = append(mixedExits, tc.exits[i])
225 }
226 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
227 }
228 assertExitAPIs(t, mixedExits, fetchedExits)
229 // All, in reverse order
230 fetchedExits = []testExit{}
231 limit = 5
232 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
233 err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter)
234 assert.NoError(t, err)
235 assertExitAPIs(t, flipedExits, fetchedExits)
236 // 400
237 path = fmt.Sprintf(
238 "%s?accountIndex=%s&hermezEthereumAddress=%s",
239 endpoint, idx, tc.usrAddr,
240 )
241 err = doBadReq("GET", path, nil, 400)
242 assert.NoError(t, err)
243 path = fmt.Sprintf("%s?tokenId=X", endpoint)
244 err = doBadReq("GET", path, nil, 400)
245 assert.NoError(t, err)
246 // 404
247 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
248 err = doBadReq("GET", path, nil, 404)
249 assert.NoError(t, err)
250 path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
251 err = doBadReq("GET", path, nil, 404)
252 assert.NoError(t, err)
253}
254
255func TestGetExit(t *testing.T) {
256 // Get all txs by their ID
257 endpoint := apiURL + "exits/"
258 fetchedExits := []testExit{}
259 for _, exit := range tc.exits {
260 fetchedExit := testExit{}
261 assert.NoError(
262 t, doGoodReq(
263 "GET",
264 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
265 nil, &fetchedExit,
266 ),
267 )
268 fetchedExits = append(fetchedExits, fetchedExit)
269 }
270 assertExitAPIs(t, tc.exits, fetchedExits)
271 // 400
272 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
273 assert.NoError(t, err)
274 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
275 assert.NoError(t, err)
276 // 404
277 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
278 assert.NoError(t, err)
279}
280
281func assertExitAPIs(t *testing.T, expected, actual []testExit) {
282 require.Equal(t, len(expected), len(actual))
283 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
284 actual[i].ItemID = 0
285 actual[i].Token.ItemID = 0
286 if expected[i].Token.USDUpdate == nil {
287 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
288 } else {
289 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
290 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
291 }
292 assert.Equal(t, expected[i], actual[i])
293 }
294}