You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

627 lines
17 KiB

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