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/big"
 11	"net/http"
 12	"os"
 13	"sort"
 14	"strconv"
 15	"testing"
 16	"time"
 17
 18	ethCommon "github.com/ethereum/go-ethereum/common"
 19	swagger "github.com/getkin/kin-openapi/openapi3filter"
 20	"github.com/gin-gonic/gin"
 21	"github.com/hermeznetwork/hermez-node/common"
 22	dbUtils "github.com/hermeznetwork/hermez-node/db"
 23	"github.com/hermeznetwork/hermez-node/db/historydb"
 24	"github.com/hermeznetwork/hermez-node/db/l2db"
 25	"github.com/hermeznetwork/hermez-node/db/statedb"
 26	"github.com/hermeznetwork/hermez-node/log"
 27	"github.com/hermeznetwork/hermez-node/test"
 28	"github.com/iden3/go-iden3-crypto/babyjub"
 29	"github.com/mitchellh/copystructure"
 30	"github.com/stretchr/testify/assert"
 31	"github.com/stretchr/testify/require"
 32)
 33
 34const apiPort = ":4010"
 35const apiURL = "http://localhost" + apiPort + "/"
 36
 37type testCommon struct {
 38	blocks  []common.Block
 39	tokens  []historydb.TokenRead
 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, statedb.TypeTxSelector, 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	// Set token value
148	tokensUSD := []historydb.TokenRead{}
149	for i, tkn := range tokens {
150		token := historydb.TokenRead{
151			TokenID:     tkn.TokenID,
152			EthBlockNum: tkn.EthBlockNum,
153			EthAddr:     tkn.EthAddr,
154			Name:        tkn.Name,
155			Symbol:      tkn.Symbol,
156			Decimals:    tkn.Decimals,
157		}
158		// Set value of 50% of the tokens
159		if i%2 != 0 {
160			value := float64(i) * 1.234567
161			now := time.Now().UTC()
162			token.USD = &value
163			token.USDUpdate = &now
164			err = h.UpdateTokenValue(token.Symbol, value)
165			if err != nil {
166				panic(err)
167			}
168		}
169		tokensUSD = append(tokensUSD, token)
170	}
171	// Gen batches and add them to DB
172	const nBatches = 10
173	batches := test.GenBatches(nBatches, blocks)
174	err = h.AddBatches(batches)
175	if err != nil {
176		panic(err)
177	}
178	// Gen accounts and add them to DB
179	const totalAccounts = 40
180	const userAccounts = 4
181	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
182	privK := babyjub.NewRandPrivKey()
183	usrBjj := privK.Public()
184	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
185	err = h.AddAccounts(accs)
186	if err != nil {
187		panic(err)
188	}
189	// Gen L1Txs and add them to DB
190	const totalL1Txs = 40
191	const userL1Txs = 4
192	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
193	var l1Txs []common.L1Tx
194	l1Txs = append(l1Txs, usrL1Txs...)
195	l1Txs = append(l1Txs, othrL1Txs...)
196	err = h.AddL1Txs(l1Txs)
197	if err != nil {
198		panic(err)
199	}
200	// Gen L2Txs and add them to DB
201	const totalL2Txs = 20
202	const userL2Txs = 4
203	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
204	var l2Txs []common.L2Tx
205	l2Txs = append(l2Txs, usrL2Txs...)
206	l2Txs = append(l2Txs, othrL2Txs...)
207	err = h.AddL2Txs(l2Txs)
208	if err != nil {
209		panic(err)
210	}
211
212	// Set test commons
213	txsToAPITxs := func(l1Txs []common.L1Tx, l2Txs []common.L2Tx, blocks []common.Block, tokens []historydb.TokenRead) historyTxAPIs {
214		/* TODO: stop using l1tx.Tx() & l2tx.Tx()
215		// Transform L1Txs and L2Txs to generic Txs
216		genericTxs := []*common.Tx{}
217		for _, l1tx := range l1Txs {
218			genericTxs = append(genericTxs, l1tx.Tx())
219		}
220		for _, l2tx := range l2Txs {
221			genericTxs = append(genericTxs, l2tx.Tx())
222		}
223		// Transform generic Txs to HistoryTx
224		historyTxs := []historydb.HistoryTx{}
225		for _, genericTx := range genericTxs {
226			// find timestamp
227			var timestamp time.Time
228			for i := 0; i < len(blocks); i++ {
229				if blocks[i].EthBlockNum == genericTx.EthBlockNum {
230					timestamp = blocks[i].Timestamp
231					break
232				}
233			}
234			// find token
235			var token historydb.TokenRead
236			if genericTx.IsL1 {
237				tokenID := genericTx.TokenID
238				found := false
239				for i := 0; i < len(tokens); i++ {
240					if tokens[i].TokenID == tokenID {
241						token = tokens[i]
242						found = true
243						break
244					}
245				}
246				if !found {
247					panic("Token not found")
248				}
249			} else {
250				var id common.TokenID
251				found := false
252				for _, acc := range accs {
253					if acc.Idx == genericTx.FromIdx {
254						found = true
255						id = acc.TokenID
256						break
257					}
258				}
259				if !found {
260					panic("tokenID not found")
261				}
262				found = false
263				for i := 0; i < len(tokensUSD); i++ {
264					if tokensUSD[i].TokenID == id {
265						token = tokensUSD[i]
266						found = true
267						break
268					}
269				}
270				if !found {
271					panic("tokenID not found")
272				}
273			}
274			var usd, loadUSD, feeUSD *float64
275			if token.USD != nil {
276				noDecimalsUSD := *token.USD / math.Pow(10, float64(token.Decimals))
277				usd = new(float64)
278				*usd = noDecimalsUSD * genericTx.AmountFloat
279				if genericTx.IsL1 {
280					loadUSD = new(float64)
281					*loadUSD = noDecimalsUSD * *genericTx.LoadAmountFloat
282				} else {
283					feeUSD = new(float64)
284					*feeUSD = *usd * genericTx.Fee.Percentage()
285				}
286			}
287			historyTx := &historydb.HistoryTx{
288				IsL1:                  genericTx.IsL1,
289				TxID:                  genericTx.TxID,
290				Type:                  genericTx.Type,
291				Position:              genericTx.Position,
292				ToIdx:                 genericTx.ToIdx,
293				Amount:                genericTx.Amount,
294				HistoricUSD:           usd,
295				BatchNum:              genericTx.BatchNum,
296				EthBlockNum:           genericTx.EthBlockNum,
297				ToForgeL1TxsNum:       genericTx.ToForgeL1TxsNum,
298				UserOrigin:            genericTx.UserOrigin,
299				FromBJJ:               genericTx.FromBJJ,
300				LoadAmount:            genericTx.LoadAmount,
301				HistoricLoadAmountUSD: loadUSD,
302				Fee:                   genericTx.Fee,
303				HistoricFeeUSD:        feeUSD,
304				Nonce:                 genericTx.Nonce,
305				Timestamp:             timestamp,
306				TokenID:               token.TokenID,
307				TokenEthBlockNum:      token.EthBlockNum,
308				TokenEthAddr:          token.EthAddr,
309				TokenName:             token.Name,
310				TokenSymbol:           token.Symbol,
311				TokenDecimals:         token.Decimals,
312				TokenUSD:              token.USD,
313				TokenUSDUpdate:        token.USDUpdate,
314			}
315			if genericTx.FromIdx != 0 {
316				historyTx.FromIdx = &genericTx.FromIdx
317			}
318			if !bytes.Equal(genericTx.FromEthAddr.Bytes(), common.EmptyAddr.Bytes()) {
319				historyTx.FromEthAddr = &genericTx.FromEthAddr
320			}
321			historyTxs = append(historyTxs, historyTx)
322		}
323		return historyTxAPIs(historyTxsToAPI(historyTxs))
324		*/
325		return nil
326	}
327	usrTxs := txsToAPITxs(usrL1Txs, usrL2Txs, blocks, tokensUSD)
328	sort.Sort(usrTxs)
329	othrTxs := txsToAPITxs(othrL1Txs, othrL2Txs, blocks, tokensUSD)
330	sort.Sort(othrTxs)
331	allTxs := append(usrTxs, othrTxs...)
332	sort.Sort(allTxs)
333	tc = testCommon{
334		blocks:  blocks,
335		tokens:  tokensUSD,
336		batches: batches,
337		usrAddr: "hez:" + usrAddr.String(),
338		usrBjj:  bjjToString(usrBjj),
339		accs:    accs,
340		usrTxs:  usrTxs,
341		othrTxs: othrTxs,
342		allTxs:  allTxs,
343		router:  router,
344	}
345	// Run tests
346	result := m.Run()
347	// Stop server
348	if err := server.Shutdown(context.Background()); err != nil {
349		panic(err)
350	}
351	if err := db.Close(); err != nil {
352		panic(err)
353	}
354	os.Exit(result)
355}
356
357func TestGetHistoryTxs(t *testing.T) {
358	return
359	//nolint:govet this is a temp patch to avoid running the test
360	endpoint := apiURL + "transactions-history"
361	fetchedTxs := historyTxAPIs{}
362	appendIter := func(intr interface{}) {
363		for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
364			tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
365			if err != nil {
366				panic(err)
367			}
368			fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
369		}
370	}
371	// Get all (no filters)
372	limit := 8
373	path := fmt.Sprintf("%s?limit=%d&offset=", endpoint, limit)
374	err := doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
375	assert.NoError(t, err)
376	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
377	// Get by ethAddr
378	fetchedTxs = historyTxAPIs{}
379	limit = 7
380	path = fmt.Sprintf(
381		"%s?hermezEthereumAddress=%s&limit=%d&offset=",
382		endpoint, tc.usrAddr, limit,
383	)
384	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
385	assert.NoError(t, err)
386	assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
387	// Get by bjj
388	fetchedTxs = historyTxAPIs{}
389	limit = 6
390	path = fmt.Sprintf(
391		"%s?BJJ=%s&limit=%d&offset=",
392		endpoint, tc.usrBjj, limit,
393	)
394	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
395	assert.NoError(t, err)
396	assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
397	// Get by tokenID
398	fetchedTxs = historyTxAPIs{}
399	limit = 5
400	tokenID := tc.allTxs[0].Token.TokenID
401	path = fmt.Sprintf(
402		"%s?tokenId=%d&limit=%d&offset=",
403		endpoint, tokenID, limit,
404	)
405	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
406	assert.NoError(t, err)
407	tokenIDTxs := historyTxAPIs{}
408	for i := 0; i < len(tc.allTxs); i++ {
409		if tc.allTxs[i].Token.TokenID == tokenID {
410			tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
411		}
412	}
413	assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
414	// idx
415	fetchedTxs = historyTxAPIs{}
416	limit = 4
417	idx := tc.allTxs[0].ToIdx
418	path = fmt.Sprintf(
419		"%s?accountIndex=%s&limit=%d&offset=",
420		endpoint, idx, limit,
421	)
422	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
423	assert.NoError(t, err)
424	idxTxs := historyTxAPIs{}
425	for i := 0; i < len(tc.allTxs); i++ {
426		if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
427			tc.allTxs[i].ToIdx[6:] == idx[6:] {
428			idxTxs = append(idxTxs, tc.allTxs[i])
429		}
430	}
431	assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
432	// batchNum
433	fetchedTxs = historyTxAPIs{}
434	limit = 3
435	batchNum := tc.allTxs[0].BatchNum
436	path = fmt.Sprintf(
437		"%s?batchNum=%d&limit=%d&offset=",
438		endpoint, *batchNum, limit,
439	)
440	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
441	assert.NoError(t, err)
442	batchNumTxs := historyTxAPIs{}
443	for i := 0; i < len(tc.allTxs); i++ {
444		if tc.allTxs[i].BatchNum != nil &&
445			*tc.allTxs[i].BatchNum == *batchNum {
446			batchNumTxs = append(batchNumTxs, tc.allTxs[i])
447		}
448	}
449	assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
450	// type
451	txTypes := []common.TxType{
452		common.TxTypeExit,
453		common.TxTypeTransfer,
454		common.TxTypeDeposit,
455		common.TxTypeCreateAccountDeposit,
456		common.TxTypeCreateAccountDepositTransfer,
457		common.TxTypeDepositTransfer,
458		common.TxTypeForceTransfer,
459		common.TxTypeForceExit,
460		common.TxTypeTransferToEthAddr,
461		common.TxTypeTransferToBJJ,
462	}
463	for _, txType := range txTypes {
464		fetchedTxs = historyTxAPIs{}
465		limit = 2
466		path = fmt.Sprintf(
467			"%s?type=%s&limit=%d&offset=",
468			endpoint, txType, limit,
469		)
470		err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
471		assert.NoError(t, err)
472		txTypeTxs := historyTxAPIs{}
473		for i := 0; i < len(tc.allTxs); i++ {
474			if tc.allTxs[i].Type == txType {
475				txTypeTxs = append(txTypeTxs, tc.allTxs[i])
476			}
477		}
478		assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
479	}
480	// Multiple filters
481	fetchedTxs = historyTxAPIs{}
482	limit = 1
483	path = fmt.Sprintf(
484		"%s?batchNum=%d&tokeId=%d&limit=%d&offset=",
485		endpoint, *batchNum, tokenID, limit,
486	)
487	err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter)
488	assert.NoError(t, err)
489	mixedTxs := historyTxAPIs{}
490	for i := 0; i < len(tc.allTxs); i++ {
491		if tc.allTxs[i].BatchNum != nil {
492			if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
493				mixedTxs = append(mixedTxs, tc.allTxs[i])
494			}
495		}
496	}
497	assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
498	// All, in reverse order
499	fetchedTxs = historyTxAPIs{}
500	limit = 5
501	path = fmt.Sprintf("%s?", endpoint)
502	appendIterRev := func(intr interface{}) {
503		tmpAll := historyTxAPIs{}
504		for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
505			tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
506			if err != nil {
507				panic(err)
508			}
509			tmpAll = append(tmpAll, tmp.(historyTxAPI))
510		}
511		fetchedTxs = append(tmpAll, fetchedTxs...)
512	}
513	err = doGoodReqPaginatedReverse(path, &historyTxsAPI{}, appendIterRev, limit)
514	assert.NoError(t, err)
515	assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
516	// 400
517	path = fmt.Sprintf(
518		"%s?accountIndex=%s&hermezEthereumAddress=%s",
519		endpoint, idx, tc.usrAddr,
520	)
521	err = doBadReq("GET", path, nil, 400)
522	assert.NoError(t, err)
523	path = fmt.Sprintf("%s?tokenId=X", endpoint)
524	err = doBadReq("GET", path, nil, 400)
525	assert.NoError(t, err)
526	// 404
527	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
528	err = doBadReq("GET", path, nil, 404)
529	assert.NoError(t, err)
530	path = fmt.Sprintf("%s?limit=1000&offset=1000", endpoint)
531	err = doBadReq("GET", path, nil, 404)
532	assert.NoError(t, err)
533}
534
535//nolint:govet this is a temp patch to avoid running the test
536func assertHistoryTxAPIs(t *testing.T, expected, actual historyTxAPIs) {
537	require.Equal(t, len(expected), len(actual))
538	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
539		assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
540		expected[i].Timestamp = actual[i].Timestamp
541		if expected[i].Token.USDUpdate == nil {
542			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
543		} else {
544			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
545			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
546		}
547		test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
548		if expected[i].L2Info != nil {
549			test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
550		} else {
551			test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
552		}
553		assert.Equal(t, expected[i], actual[i])
554	}
555}
556
557//nolint:govet this is a temp patch to avoid running the test
558func doGoodReqPaginated(
559	path string,
560	iterStruct paginationer,
561	appendIter func(res interface{}),
562) error {
563	next := 0
564	for {
565		// Call API to get this iteration items
566		if err := doGoodReq("GET", path+strconv.Itoa(next), nil, iterStruct); err != nil {
567			return err
568		}
569		appendIter(iterStruct)
570		// Keep iterating?
571		pag := iterStruct.GetPagination()
572		if pag.LastReturnedItem == pag.TotalItems-1 { // No
573			break
574		} else { // Yes
575			next = int(pag.LastReturnedItem + 1)
576		}
577	}
578	return nil
579}
580
581//nolint:govet this is a temp patch to avoid running the test
582func doGoodReqPaginatedReverse(
583	path string,
584	iterStruct paginationer,
585	appendIter func(res interface{}),
586	limit int,
587) error {
588	next := 0
589	first := true
590	for {
591		// Call API to get this iteration items
592		if first {
593			first = false
594			pagQuery := fmt.Sprintf("last=true&limit=%d", limit)
595			if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil {
596				return err
597			}
598		} else {
599			pagQuery := fmt.Sprintf("offset=%d&limit=%d", next, limit)
600			if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil {
601				return err
602			}
603		}
604		appendIter(iterStruct)
605		// Keep iterating?
606		pag := iterStruct.GetPagination()
607		if iterStruct.Len() == pag.TotalItems || pag.LastReturnedItem-iterStruct.Len() == -1 { // No
608			break
609		} else { // Yes
610			prevOffset := next
611			next = pag.LastReturnedItem - iterStruct.Len() - limit + 1
612			if next < 0 {
613				next = 0
614				limit = prevOffset
615			}
616		}
617	}
618	return nil
619}
620
621//nolint:govet this is a temp patch to avoid running the test
622func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
623	ctx := context.Background()
624	client := &http.Client{}
625	httpReq, _ := http.NewRequest(method, path, reqBody)
626	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
627	if err != nil {
628		return err
629	}
630	// Validate request against swagger spec
631	requestValidationInput := &swagger.RequestValidationInput{
632		Request:    httpReq,
633		PathParams: pathParams,
634		Route:      route,
635	}
636	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
637		return err
638	}
639	// Do API call
640	resp, err := client.Do(httpReq)
641	if err != nil {
642		return err
643	}
644	if resp.Body == nil {
645		return errors.New("Nil body")
646	}
647	//nolint
648	defer resp.Body.Close()
649	body, err := ioutil.ReadAll(resp.Body)
650	if err != nil {
651		return err
652	}
653	if resp.StatusCode != 200 {
654		return fmt.Errorf("%d response: %s", resp.StatusCode, string(body))
655	}
656	// Unmarshal body into return struct
657	if err := json.Unmarshal(body, returnStruct); err != nil {
658		return err
659	}
660	// Validate response against swagger spec
661	responseValidationInput := &swagger.ResponseValidationInput{
662		RequestValidationInput: requestValidationInput,
663		Status:                 resp.StatusCode,
664		Header:                 resp.Header,
665	}
666	responseValidationInput = responseValidationInput.SetBodyBytes(body)
667	return swagger.ValidateResponse(ctx, responseValidationInput)
668}
669
670//nolint:govet this is a temp patch to avoid running the test
671func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
672	ctx := context.Background()
673	client := &http.Client{}
674	httpReq, _ := http.NewRequest(method, path, reqBody)
675	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
676	if err != nil {
677		return err
678	}
679	// Validate request against swagger spec
680	requestValidationInput := &swagger.RequestValidationInput{
681		Request:    httpReq,
682		PathParams: pathParams,
683		Route:      route,
684	}
685	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
686		if expectedResponseCode != 400 {
687			return err
688		}
689		log.Warn("The request does not match the API spec")
690	}
691	// Do API call
692	resp, err := client.Do(httpReq)
693	if err != nil {
694		return err
695	}
696	if resp.Body == nil {
697		return errors.New("Nil body")
698	}
699	//nolint
700	defer resp.Body.Close()
701	body, err := ioutil.ReadAll(resp.Body)
702	if err != nil {
703		return err
704	}
705	if resp.StatusCode != expectedResponseCode {
706		return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
707	}
708	// Validate response against swagger spec
709	responseValidationInput := &swagger.ResponseValidationInput{
710		RequestValidationInput: requestValidationInput,
711		Status:                 resp.StatusCode,
712		Header:                 resp.Header,
713	}
714	responseValidationInput = responseValidationInput.SetBodyBytes(body)
715	return swagger.ValidateResponse(ctx, responseValidationInput)
716}