arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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}