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