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.

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