API add bids endpointfeature/sql-semaphore1
@ -0,0 +1,47 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"net/http" |
||||
|
|
||||
|
"github.com/gin-gonic/gin" |
||||
|
"github.com/hermeznetwork/hermez-node/db" |
||||
|
"github.com/hermeznetwork/hermez-node/db/historydb" |
||||
|
) |
||||
|
|
||||
|
func getBids(c *gin.Context) { |
||||
|
slotNum, bidderAddr, err := parseBidFilters(c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
if slotNum == nil && bidderAddr == nil { |
||||
|
retBadReq(errors.New("It is necessary to add at least one filter: slotNum or/and bidderAddr"), c) |
||||
|
return |
||||
|
} |
||||
|
// Pagination
|
||||
|
fromItem, order, limit, err := parsePagination(c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
bids, pagination, err := h.GetBidsAPI( |
||||
|
slotNum, bidderAddr, fromItem, limit, order, |
||||
|
) |
||||
|
|
||||
|
if err != nil { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Build succesfull response
|
||||
|
type bidsResponse struct { |
||||
|
Bids []historydb.BidAPI `json:"bids"` |
||||
|
Pagination *db.Pagination `json:"pagination"` |
||||
|
} |
||||
|
c.JSON(http.StatusOK, &bidsResponse{ |
||||
|
Bids: bids, |
||||
|
Pagination: pagination, |
||||
|
}) |
||||
|
} |
@ -0,0 +1,165 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"testing" |
||||
|
"time" |
||||
|
|
||||
|
ethCommon "github.com/ethereum/go-ethereum/common" |
||||
|
"github.com/hermeznetwork/hermez-node/common" |
||||
|
"github.com/hermeznetwork/hermez-node/db" |
||||
|
"github.com/hermeznetwork/hermez-node/db/historydb" |
||||
|
"github.com/mitchellh/copystructure" |
||||
|
"github.com/stretchr/testify/assert" |
||||
|
) |
||||
|
|
||||
|
type testBid struct { |
||||
|
ItemID int `json:"itemId"` |
||||
|
SlotNum int64 `json:"slotNum"` |
||||
|
BidValue string `json:"bidValue"` |
||||
|
EthBlockNum int64 `json:"ethereumBlockNum"` |
||||
|
Bidder ethCommon.Address `json:"bidderAddr"` |
||||
|
Forger ethCommon.Address `json:"forgerAddr"` |
||||
|
URL string `json:"URL"` |
||||
|
Timestamp time.Time `json:"timestamp"` |
||||
|
} |
||||
|
|
||||
|
type testBidsResponse struct { |
||||
|
Bids []testBid `json:"bids"` |
||||
|
Pagination *db.Pagination `json:"pagination"` |
||||
|
} |
||||
|
|
||||
|
func (t testBidsResponse) GetPagination() *db.Pagination { |
||||
|
if t.Bids[0].ItemID < t.Bids[len(t.Bids)-1].ItemID { |
||||
|
t.Pagination.FirstReturnedItem = t.Bids[0].ItemID |
||||
|
t.Pagination.LastReturnedItem = t.Bids[len(t.Bids)-1].ItemID |
||||
|
} else { |
||||
|
t.Pagination.LastReturnedItem = t.Bids[0].ItemID |
||||
|
t.Pagination.FirstReturnedItem = t.Bids[len(t.Bids)-1].ItemID |
||||
|
} |
||||
|
return t.Pagination |
||||
|
} |
||||
|
|
||||
|
func (t testBidsResponse) Len() int { |
||||
|
return len(t.Bids) |
||||
|
} |
||||
|
|
||||
|
func genTestBids(blocks []common.Block, coordinators []historydb.CoordinatorAPI, bids []common.Bid) []testBid { |
||||
|
tBids := []testBid{} |
||||
|
for _, bid := range bids { |
||||
|
block := getBlockByNum(bid.EthBlockNum, blocks) |
||||
|
coordinator := getCoordinatorByBidder(bid.Bidder, coordinators) |
||||
|
tBid := testBid{ |
||||
|
SlotNum: bid.SlotNum, |
||||
|
BidValue: bid.BidValue.String(), |
||||
|
EthBlockNum: bid.EthBlockNum, |
||||
|
Bidder: bid.Bidder, |
||||
|
Forger: coordinator.Forger, |
||||
|
URL: coordinator.URL, |
||||
|
Timestamp: block.Timestamp, |
||||
|
} |
||||
|
tBids = append(tBids, tBid) |
||||
|
} |
||||
|
return tBids |
||||
|
} |
||||
|
|
||||
|
func TestGetBids(t *testing.T) { |
||||
|
endpoint := apiURL + "bids" |
||||
|
fetchedBids := []testBid{} |
||||
|
appendIter := func(intr interface{}) { |
||||
|
for i := 0; i < len(intr.(*testBidsResponse).Bids); i++ { |
||||
|
tmp, err := copystructure.Copy(intr.(*testBidsResponse).Bids[i]) |
||||
|
if err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
fetchedBids = append(fetchedBids, tmp.(testBid)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
limit := 3 |
||||
|
// bidderAddress
|
||||
|
fetchedBids = []testBid{} |
||||
|
bidderAddress := tc.bids[3].Bidder |
||||
|
path := fmt.Sprintf("%s?bidderAddr=%s&limit=%d&fromItem=", endpoint, bidderAddress.String(), limit) |
||||
|
err := doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
bidderAddrBids := []testBid{} |
||||
|
for i := 0; i < len(tc.bids); i++ { |
||||
|
if tc.bids[i].Bidder == bidderAddress { |
||||
|
bidderAddrBids = append(bidderAddrBids, tc.bids[i]) |
||||
|
} |
||||
|
} |
||||
|
assertBids(t, bidderAddrBids, fetchedBids) |
||||
|
|
||||
|
// slotNum
|
||||
|
fetchedBids = []testBid{} |
||||
|
slotNum := tc.bids[3].SlotNum |
||||
|
path = fmt.Sprintf("%s?slotNum=%d&limit=%d&fromItem=", endpoint, slotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
slotNumBids := []testBid{} |
||||
|
for i := 0; i < len(tc.bids); i++ { |
||||
|
if tc.bids[i].SlotNum == slotNum { |
||||
|
slotNumBids = append(slotNumBids, tc.bids[i]) |
||||
|
} |
||||
|
} |
||||
|
assertBids(t, slotNumBids, fetchedBids) |
||||
|
|
||||
|
// slotNum, in reverse order
|
||||
|
fetchedBids = []testBid{} |
||||
|
path = fmt.Sprintf("%s?slotNum=%d&limit=%d&fromItem=", endpoint, slotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
flippedBids := []testBid{} |
||||
|
for i := len(slotNumBids) - 1; i >= 0; i-- { |
||||
|
flippedBids = append(flippedBids, slotNumBids[i]) |
||||
|
} |
||||
|
assertBids(t, flippedBids, fetchedBids) |
||||
|
|
||||
|
// Mixed filters
|
||||
|
fetchedBids = []testBid{} |
||||
|
bidderAddress = tc.bids[9].Bidder |
||||
|
slotNum = tc.bids[4].SlotNum |
||||
|
path = fmt.Sprintf("%s?bidderAddr=%s&slotNum=%d&limit=%d&fromItem=", endpoint, bidderAddress.String(), slotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
slotNumBidderAddrBids := []testBid{} |
||||
|
for i := 0; i < len(tc.bids); i++ { |
||||
|
if tc.bids[i].Bidder == bidderAddress && tc.bids[i].SlotNum == slotNum { |
||||
|
slotNumBidderAddrBids = append(slotNumBidderAddrBids, tc.bids[i]) |
||||
|
} |
||||
|
} |
||||
|
assertBids(t, slotNumBidderAddrBids, fetchedBids) |
||||
|
|
||||
|
// 400
|
||||
|
// No filters
|
||||
|
path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Invalid slotNum
|
||||
|
path = fmt.Sprintf("%s?slotNum=%d", endpoint, -2) |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Invalid bidderAddress
|
||||
|
path = fmt.Sprintf("%s?bidderAddr=%s", endpoint, "0xG0000001") |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// 404
|
||||
|
path = fmt.Sprintf("%s?slotNum=%d&bidderAddr=%s", endpoint, tc.bids[0].SlotNum, tc.bids[1].Bidder.String()) |
||||
|
err = doBadReq("GET", path, nil, 404) |
||||
|
assert.NoError(t, err) |
||||
|
} |
||||
|
|
||||
|
func assertBids(t *testing.T, expected, actual []testBid) { |
||||
|
assert.Equal(t, len(expected), len(actual)) |
||||
|
for i := 0; i < len(expected); i++ { |
||||
|
assertBid(t, expected[i], actual[i]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func assertBid(t *testing.T, expected, actual testBid) { |
||||
|
assert.Equal(t, expected.Timestamp.Unix(), actual.Timestamp.Unix()) |
||||
|
expected.Timestamp = actual.Timestamp |
||||
|
actual.ItemID = expected.ItemID |
||||
|
assert.Equal(t, expected, actual) |
||||
|
} |