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"
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 dbUtils "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/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)
33
34const apiPort = ":4010"
35const apiURL = "http://localhost" + apiPort + "/"
36
37type testCommon struct {
38 blocks []common.Block
39 tokens []common.Token
40 batches []common.Batch
41 usrAddr string
42 usrBjj string
43 accs []common.Account
44 usrTxs historyTxAPIs
45 othrTxs historyTxAPIs
46 allTxs historyTxAPIs
47 router *swagger.Router
48}
49
50type historyTxAPIs []historyTxAPI
51
52func (h historyTxAPIs) Len() int { return len(h) }
53func (h historyTxAPIs) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
54func (h historyTxAPIs) Less(i, j int) bool {
55 // i not forged yet
56 if h[i].BatchNum == nil {
57 if h[j].BatchNum != nil { // j is already forged
58 return false
59 }
60 // Both aren't forged, is i in a smaller position?
61 return h[i].Position < h[j].Position
62 }
63 // i is forged
64 if h[j].BatchNum == nil {
65 return true // j is not forged
66 }
67 // Both are forged
68 if *h[i].BatchNum == *h[j].BatchNum {
69 // At the same batch, is i in a smaller position?
70 return h[i].Position < h[j].Position
71 }
72 // At different batches, is i in a smaller batch?
73 return *h[i].BatchNum < *h[j].BatchNum
74}
75
76var tc testCommon
77
78func TestMain(m *testing.M) {
79 // Init swagger
80 router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
81 // Init DBs
82 // HistoryDB
83 pass := os.Getenv("POSTGRES_PASS")
84 db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
85 if err != nil {
86 panic(err)
87 }
88 hdb := historydb.NewHistoryDB(db)
89 err = hdb.Reorg(-1)
90 if err != nil {
91 panic(err)
92 }
93 // StateDB
94 dir, err := ioutil.TempDir("", "tmpdb")
95 if err != nil {
96 panic(err)
97 }
98 sdb, err := statedb.NewStateDB(dir, false, 0)
99 if err != nil {
100 panic(err)
101 }
102 // L2DB
103 l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
104 test.CleanL2DB(l2DB.DB())
105
106 // Init API
107 api := gin.Default()
108 if err := SetAPIEndpoints(
109 true,
110 true,
111 api,
112 hdb,
113 sdb,
114 l2DB,
115 ); err != nil {
116 panic(err)
117 }
118 // Start server
119 server := &http.Server{Addr: apiPort, Handler: api}
120 go func() {
121 if err := server.ListenAndServe(); err != nil &&
122 err != http.ErrServerClosed {
123 panic(err)
124 }
125 }()
126
127 // Populate DBs
128 // Clean DB
129 err = h.Reorg(0)
130 if err != nil {
131 panic(err)
132 }
133 // Gen blocks and add them to DB
134 const nBlocks = 5
135 blocks := test.GenBlocks(1, nBlocks+1)
136 err = h.AddBlocks(blocks)
137 if err != nil {
138 panic(err)
139 }
140 // Gen tokens and add them to DB
141 const nTokens = 10
142 tokens := test.GenTokens(nTokens, blocks)
143 err = h.AddTokens(tokens)
144 if err != nil {
145 panic(err)
146 }
147 // Gen batches and add them to DB
148 const nBatches = 10
149 batches := test.GenBatches(nBatches, blocks)
150 err = h.AddBatches(batches)
151 if err != nil {
152 panic(err)
153 }
154 // Gen accounts and add them to DB
155 const totalAccounts = 40
156 const userAccounts = 4
157 usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
158 privK := babyjub.NewRandPrivKey()
159 usrBjj := privK.Public()
160 accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
161 err = h.AddAccounts(accs)
162 if err != nil {
163 panic(err)
164 }
165 // Gen L1Txs and add them to DB
166 const totalL1Txs = 40
167 const userL1Txs = 4
168 usrL1Txs, othrL1Txs := test.GenL1Txs(0, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
169 var l1Txs []common.L1Tx
170 l1Txs = append(l1Txs, usrL1Txs...)
171 l1Txs = append(l1Txs, othrL1Txs...)
172 err = h.AddL1Txs(l1Txs)
173 if err != nil {
174 panic(err)
175 }
176 // Gen L2Txs and add them to DB
177 const totalL2Txs = 20
178 const userL2Txs = 4
179 usrL2Txs, othrL2Txs := test.GenL2Txs(totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
180 var l2Txs []common.L2Tx
181 l2Txs = append(l2Txs, usrL2Txs...)
182 l2Txs = append(l2Txs, othrL2Txs...)
183 err = h.AddL2Txs(l2Txs)
184 if err != nil {
185 panic(err)
186 }
187
188 // Set test commons
189 txsToAPITxs := func(l1Txs []common.L1Tx, l2Txs []common.L2Tx, blocks []common.Block, tokens []common.Token) historyTxAPIs {
190 // Transform L1Txs and L2Txs to generic Txs
191 genericTxs := []*common.Tx{}
192 for _, l1tx := range l1Txs {
193 genericTxs = append(genericTxs, l1tx.Tx())
194 }
195 for _, l2tx := range l2Txs {
196 genericTxs = append(genericTxs, l2tx.Tx())
197 }
198 // Transform generic Txs to HistoryTx
199 historyTxs := []*historydb.HistoryTx{}
200 for _, genericTx := range genericTxs {
201 // find timestamp
202 var timestamp time.Time
203 for i := 0; i < len(blocks); i++ {
204 if blocks[i].EthBlockNum == genericTx.EthBlockNum {
205 timestamp = blocks[i].Timestamp
206 break
207 }
208 }
209 // find token
210 var token common.Token
211 if genericTx.IsL1 {
212 tokenID := genericTx.TokenID
213 found := false
214 for i := 0; i < len(tokens); i++ {
215 if tokens[i].TokenID == tokenID {
216 token = tokens[i]
217 found = true
218 break
219 }
220 }
221 if !found {
222 panic("Token not found")
223 }
224 } else {
225 token = test.GetToken(genericTx.FromIdx, accs, tokens)
226 }
227 var usd, loadUSD, feeUSD *float64
228 if token.USD != nil {
229 noDecimalsUSD := *token.USD / math.Pow(10, float64(token.Decimals))
230 usd = new(float64)
231 *usd = noDecimalsUSD * genericTx.AmountFloat
232 if genericTx.IsL1 {
233 loadUSD = new(float64)
234 *loadUSD = noDecimalsUSD * *genericTx.LoadAmountFloat
235 } else {
236 feeUSD = new(float64)
237 *feeUSD = *usd * genericTx.Fee.Percentage()
238 }
239 }
240 historyTxs = append(historyTxs, &historydb.HistoryTx{
241 IsL1: genericTx.IsL1,
242 TxID: genericTx.TxID,
243 Type: genericTx.Type,
244 Position: genericTx.Position,
245 FromIdx: genericTx.FromIdx,
246 ToIdx: genericTx.ToIdx,
247 Amount: genericTx.Amount,
248 AmountFloat: genericTx.AmountFloat,
249 HistoricUSD: usd,
250 BatchNum: genericTx.BatchNum,
251 EthBlockNum: genericTx.EthBlockNum,
252 ToForgeL1TxsNum: genericTx.ToForgeL1TxsNum,
253 UserOrigin: genericTx.UserOrigin,
254 FromEthAddr: genericTx.FromEthAddr,
255 FromBJJ: genericTx.FromBJJ,
256 LoadAmount: genericTx.LoadAmount,
257 LoadAmountFloat: genericTx.LoadAmountFloat,
258 HistoricLoadAmountUSD: loadUSD,
259 Fee: genericTx.Fee,
260 HistoricFeeUSD: feeUSD,
261 Nonce: genericTx.Nonce,
262 Timestamp: timestamp,
263 TokenID: token.TokenID,
264 TokenEthBlockNum: token.EthBlockNum,
265 TokenEthAddr: token.EthAddr,
266 TokenName: token.Name,
267 TokenSymbol: token.Symbol,
268 TokenDecimals: token.Decimals,
269 TokenUSD: token.USD,
270 TokenUSDUpdate: token.USDUpdate,
271 })
272 }
273 return historyTxAPIs(historyTxsToAPI(historyTxs))
274 }
275 usrTxs := txsToAPITxs(usrL1Txs, usrL2Txs, blocks, tokens)
276 sort.Sort(usrTxs)
277 othrTxs := txsToAPITxs(othrL1Txs, othrL2Txs, blocks, tokens)
278 sort.Sort(othrTxs)
279 allTxs := append(usrTxs, othrTxs...)
280 sort.Sort(allTxs)
281 tc = testCommon{
282 blocks: blocks,
283 tokens: tokens,
284 batches: batches,
285 usrAddr: "hez:" + usrAddr.String(),
286 usrBjj: bjjToString(usrBjj),
287 accs: accs,
288 usrTxs: usrTxs,
289 othrTxs: othrTxs,
290 allTxs: allTxs,
291 router: router,
292 }
293 // Run tests
294 result := m.Run()
295 // Stop server
296 if err := server.Shutdown(context.Background()); err != nil {
297 panic(err)
298 }
299 if err := db.Close(); err != nil {
300 panic(err)
301 }
302 os.Exit(result)
303}
304
305func TestGetHistoryTxs(t *testing.T) {
306 endpoint := apiURL + "transactions-history"
307 fetchedTxs := historyTxAPIs{}
308 appendIter := func(intr interface{}) {
309 for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
310 tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
311 if err != nil {
312 panic(err)
313 }
314 fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
315 }
316 }
317 // Get all (no filters)
318 limit := 8
319 path := fmt.Sprintf("%s?limit=%d&offset=", endpoint, limit)
320 err := doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
321 assert.NoError(t, err)
322 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
323 // Get by ethAddr
324 fetchedTxs = historyTxAPIs{}
325 limit = 7
326 path = fmt.Sprintf(
327 "%s?hermezEthereumAddress=%s&limit=%d&offset=",
328 endpoint, tc.usrAddr, limit,
329 )
330 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
331 assert.NoError(t, err)
332 assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
333 // Get by bjj
334 fetchedTxs = historyTxAPIs{}
335 limit = 6
336 path = fmt.Sprintf(
337 "%s?BJJ=%s&limit=%d&offset=",
338 endpoint, tc.usrBjj, limit,
339 )
340 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
341 assert.NoError(t, err)
342 assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
343 // Get by tokenID
344 fetchedTxs = historyTxAPIs{}
345 limit = 5
346 tokenID := tc.allTxs[0].Token.TokenID
347 path = fmt.Sprintf(
348 "%s?tokenId=%d&limit=%d&offset=",
349 endpoint, tokenID, limit,
350 )
351 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
352 assert.NoError(t, err)
353 tokenIDTxs := historyTxAPIs{}
354 for i := 0; i < len(tc.allTxs); i++ {
355 if tc.allTxs[i].Token.TokenID == tokenID {
356 tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
357 }
358 }
359 assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
360 // idx
361 fetchedTxs = historyTxAPIs{}
362 limit = 4
363 idx := tc.allTxs[0].FromIdx
364 path = fmt.Sprintf(
365 "%s?accountIndex=%s&limit=%d&offset=",
366 endpoint, idx, limit,
367 )
368 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
369 assert.NoError(t, err)
370 idxTxs := historyTxAPIs{}
371 for i := 0; i < len(tc.allTxs); i++ {
372 if tc.allTxs[i].FromIdx == idx {
373 idxTxs = append(idxTxs, tc.allTxs[i])
374 }
375 }
376 assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
377 // batchNum
378 fetchedTxs = historyTxAPIs{}
379 limit = 3
380 batchNum := tc.allTxs[0].BatchNum
381 path = fmt.Sprintf(
382 "%s?batchNum=%d&limit=%d&offset=",
383 endpoint, *batchNum, limit,
384 )
385 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
386 assert.NoError(t, err)
387 batchNumTxs := historyTxAPIs{}
388 for i := 0; i < len(tc.allTxs); i++ {
389 if tc.allTxs[i].BatchNum != nil &&
390 *tc.allTxs[i].BatchNum == *batchNum {
391 batchNumTxs = append(batchNumTxs, tc.allTxs[i])
392 }
393 }
394 assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
395 // type
396 txTypes := []common.TxType{
397 common.TxTypeExit,
398 common.TxTypeWithdrawn,
399 common.TxTypeTransfer,
400 common.TxTypeDeposit,
401 common.TxTypeCreateAccountDeposit,
402 common.TxTypeCreateAccountDepositTransfer,
403 common.TxTypeDepositTransfer,
404 common.TxTypeForceTransfer,
405 common.TxTypeForceExit,
406 common.TxTypeTransferToEthAddr,
407 common.TxTypeTransferToBJJ,
408 }
409 for _, txType := range txTypes {
410 fetchedTxs = historyTxAPIs{}
411 limit = 2
412 path = fmt.Sprintf(
413 "%s?type=%s&limit=%d&offset=",
414 endpoint, txType, limit,
415 )
416 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
417 assert.NoError(t, err)
418 txTypeTxs := historyTxAPIs{}
419 for i := 0; i < len(tc.allTxs); i++ {
420 if tc.allTxs[i].Type == txType {
421 txTypeTxs = append(txTypeTxs, tc.allTxs[i])
422 }
423 }
424 assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
425 }
426 // Multiple filters
427 fetchedTxs = historyTxAPIs{}
428 limit = 1
429 path = fmt.Sprintf(
430 "%s?batchNum=%d&tokeId=%d&limit=%d&offset=",
431 endpoint, *batchNum, tokenID, limit,
432 )
433 err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
434 assert.NoError(t, err)
435 mixedTxs := historyTxAPIs{}
436 for i := 0; i < len(tc.allTxs); i++ {
437 if tc.allTxs[i].BatchNum != nil {
438 if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
439 mixedTxs = append(mixedTxs, tc.allTxs[i])
440 }
441 }
442 }
443 assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
444 // All, in reverse order
445 fetchedTxs = historyTxAPIs{}
446 limit = 5
447 path = fmt.Sprintf("%s?", endpoint)
448 appendIterRev := func(intr interface{}) {
449 tmpAll := historyTxAPIs{}
450 for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
451 tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
452 if err != nil {
453 panic(err)
454 }
455 tmpAll = append(tmpAll, tmp.(historyTxAPI))
456 }
457 fetchedTxs = append(tmpAll, fetchedTxs...)
458 }
459 err = doGoodReqPaginatedReverse(path, &historyTxsAPI{}, appendIterRev, limit)
460 assert.NoError(t, err)
461 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
462 // 400
463 path = fmt.Sprintf(
464 "%s?accountIndex=%s&hermezEthereumAddress=%s",
465 endpoint, idx, tc.usrAddr,
466 )
467 err = doBadReq("GET", path, nil, 400)
468 assert.NoError(t, err)
469 path = fmt.Sprintf("%s?tokenId=X", endpoint)
470 err = doBadReq("GET", path, nil, 400)
471 assert.NoError(t, err)
472 // 404
473 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
474 err = doBadReq("GET", path, nil, 404)
475 assert.NoError(t, err)
476 path = fmt.Sprintf("%s?limit=1000&offset=1000", endpoint)
477 err = doBadReq("GET", path, nil, 404)
478 assert.NoError(t, err)
479}
480
481func assertHistoryTxAPIs(t *testing.T, expected, actual historyTxAPIs) {
482 assert.Equal(t, len(expected), len(actual))
483 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
484 assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
485 expected[i].Timestamp = actual[i].Timestamp
486 if expected[i].Token.USDUpdate == nil {
487 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
488 } else {
489 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
490 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
491 }
492 test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
493 if expected[i].L2Info != nil {
494 test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
495 } else {
496 test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
497 }
498 assert.Equal(t, expected[i], actual[i])
499 }
500}
501
502func doGoodReqPaginated(
503 path string,
504 iterStruct paginationer,
505 appendIter func(res interface{}),
506) error {
507 next := 0
508 for {
509 // Call API to get this iteration items
510 if err := doGoodReq("GET", path+strconv.Itoa(next), nil, iterStruct); err != nil {
511 return err
512 }
513 appendIter(iterStruct)
514 // Keep iterating?
515 pag := iterStruct.GetPagination()
516 if pag.LastReturnedItem == pag.TotalItems-1 { // No
517 break
518 } else { // Yes
519 next = int(pag.LastReturnedItem + 1)
520 }
521 }
522 return nil
523}
524
525func doGoodReqPaginatedReverse(
526 path string,
527 iterStruct paginationer,
528 appendIter func(res interface{}),
529 limit int,
530) error {
531 next := 0
532 first := true
533 for {
534 // Call API to get this iteration items
535 if first {
536 first = false
537 pagQuery := fmt.Sprintf("last=true&limit=%d", limit)
538 if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil {
539 return err
540 }
541 } else {
542 pagQuery := fmt.Sprintf("offset=%d&limit=%d", next, limit)
543 if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil {
544 return err
545 }
546 }
547 appendIter(iterStruct)
548 // Keep iterating?
549 pag := iterStruct.GetPagination()
550 if iterStruct.Len() == pag.TotalItems || pag.LastReturnedItem-iterStruct.Len() == -1 { // No
551 break
552 } else { // Yes
553 prevOffset := next
554 next = pag.LastReturnedItem - iterStruct.Len() - limit + 1
555 if next < 0 {
556 next = 0
557 limit = prevOffset
558 }
559 }
560 }
561 return nil
562}
563
564func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
565 ctx := context.Background()
566 client := &http.Client{}
567 httpReq, _ := http.NewRequest(method, path, reqBody)
568 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
569 if err != nil {
570 return err
571 }
572 // Validate request against swagger spec
573 requestValidationInput := &swagger.RequestValidationInput{
574 Request: httpReq,
575 PathParams: pathParams,
576 Route: route,
577 }
578 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
579 return err
580 }
581 // Do API call
582 resp, err := client.Do(httpReq)
583 if err != nil {
584 return err
585 }
586 if resp.Body == nil {
587 return errors.New("Nil body")
588 }
589 //nolint
590 defer resp.Body.Close()
591 body, err := ioutil.ReadAll(resp.Body)
592 if err != nil {
593 return err
594 }
595 if resp.StatusCode != 200 {
596 return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
597 }
598 // Unmarshal body into return struct
599 if err := json.Unmarshal(body, returnStruct); err != nil {
600 return err
601 }
602 // Validate response against swagger spec
603 responseValidationInput := &swagger.ResponseValidationInput{
604 RequestValidationInput: requestValidationInput,
605 Status: resp.StatusCode,
606 Header: resp.Header,
607 }
608 responseValidationInput = responseValidationInput.SetBodyBytes(body)
609 return swagger.ValidateResponse(ctx, responseValidationInput)
610}
611
612func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
613 ctx := context.Background()
614 client := &http.Client{}
615 httpReq, _ := http.NewRequest(method, path, reqBody)
616 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
617 if err != nil {
618 return err
619 }
620 // Validate request against swagger spec
621 requestValidationInput := &swagger.RequestValidationInput{
622 Request: httpReq,
623 PathParams: pathParams,
624 Route: route,
625 }
626 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
627 if expectedResponseCode != 400 {
628 return err
629 }
630 log.Warn("The request does not match the API spec")
631 }
632 // Do API call
633 resp, err := client.Do(httpReq)
634 if err != nil {
635 return err
636 }
637 if resp.Body == nil {
638 return errors.New("Nil body")
639 }
640 //nolint
641 defer resp.Body.Close()
642 body, err := ioutil.ReadAll(resp.Body)
643 if err != nil {
644 return err
645 }
646 if resp.StatusCode != expectedResponseCode {
647 return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
648 }
649 // Validate response against swagger spec
650 responseValidationInput := &swagger.ResponseValidationInput{
651 RequestValidationInput: requestValidationInput,
652 Status: resp.StatusCode,
653 Header: resp.Header,
654 }
655 responseValidationInput = responseValidationInput.SetBodyBytes(body)
656 return swagger.ValidateResponse(ctx, responseValidationInput)
657}