API add slots endpointsfeature/sql-semaphore1
@ -0,0 +1,346 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"database/sql" |
||||
|
"errors" |
||||
|
"net/http" |
||||
|
|
||||
|
"github.com/gin-gonic/gin" |
||||
|
"github.com/hermeznetwork/hermez-node/common" |
||||
|
"github.com/hermeznetwork/hermez-node/db" |
||||
|
"github.com/hermeznetwork/hermez-node/db/historydb" |
||||
|
) |
||||
|
|
||||
|
// SlotAPI is a repesentation of a slot information
|
||||
|
type SlotAPI struct { |
||||
|
ItemID int `json:"itemId"` |
||||
|
SlotNum int64 `json:"slotNum"` |
||||
|
FirstBlock int64 `json:"firstBlock"` |
||||
|
LastBlock int64 `json:"lastBlock"` |
||||
|
OpenAuction bool `json:"openAuction"` |
||||
|
WinnerBid *historydb.BidAPI `json:"winnerBid"` |
||||
|
TotalItems int `json:"-"` |
||||
|
FirstItem int `json:"-"` |
||||
|
LastItem int `json:"-"` |
||||
|
} |
||||
|
|
||||
|
func getFirstLastBlock(slotNum int64) (int64, int64) { |
||||
|
genesisBlock := cg.AuctionConstants.GenesisBlockNum |
||||
|
blocksPerSlot := int64(cg.AuctionConstants.BlocksPerSlot) |
||||
|
firstBlock := slotNum*blocksPerSlot + genesisBlock |
||||
|
lastBlock := (slotNum+1)*blocksPerSlot + genesisBlock - 1 |
||||
|
return firstBlock, lastBlock |
||||
|
} |
||||
|
|
||||
|
func getCurrentSlot(currentBlock int64) int64 { |
||||
|
genesisBlock := cg.AuctionConstants.GenesisBlockNum |
||||
|
blocksPerSlot := int64(cg.AuctionConstants.BlocksPerSlot) |
||||
|
currentSlot := (currentBlock - genesisBlock) / blocksPerSlot |
||||
|
return currentSlot |
||||
|
} |
||||
|
|
||||
|
func isOpenAuction(currentBlock, slotNum int64, auctionVars common.AuctionVariables) bool { |
||||
|
currentSlot := getCurrentSlot(currentBlock) |
||||
|
closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots) |
||||
|
openAuctionSlots := int64(auctionVars.OpenAuctionSlots) |
||||
|
if slotNum > closedAuctionSlots && slotNum <= (closedAuctionSlots+openAuctionSlots) { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func getPagination(totalItems int, minSlotNum, maxSlotNum *int64) *db.Pagination { |
||||
|
// itemID is slotNum
|
||||
|
firstItem := *minSlotNum |
||||
|
lastItem := *maxSlotNum |
||||
|
pagination := &db.Pagination{ |
||||
|
TotalItems: int(totalItems), |
||||
|
FirstItem: int(firstItem), |
||||
|
LastItem: int(lastItem), |
||||
|
} |
||||
|
return pagination |
||||
|
} |
||||
|
|
||||
|
func newSlotAPI(slotNum, currentBlockNum int64, bid *historydb.BidAPI, auctionVars *common.AuctionVariables) SlotAPI { |
||||
|
firstBlock, lastBlock := getFirstLastBlock(slotNum) |
||||
|
openAuction := isOpenAuction(currentBlockNum, slotNum, *auctionVars) |
||||
|
slot := SlotAPI{ |
||||
|
ItemID: int(slotNum), |
||||
|
SlotNum: slotNum, |
||||
|
FirstBlock: firstBlock, |
||||
|
LastBlock: lastBlock, |
||||
|
OpenAuction: openAuction, |
||||
|
WinnerBid: bid, |
||||
|
} |
||||
|
return slot |
||||
|
} |
||||
|
|
||||
|
func newSlotsAPIFromWinnerBids(fromItem *uint, order string, bids []historydb.BidAPI, currentBlockNum int64, auctionVars *common.AuctionVariables) (slots []SlotAPI) { |
||||
|
for i := range bids { |
||||
|
slotNum := bids[i].SlotNum |
||||
|
slot := newSlotAPI(slotNum, currentBlockNum, &bids[i], auctionVars) |
||||
|
if order == historydb.OrderAsc { |
||||
|
if slot.ItemID >= int(*fromItem) { |
||||
|
slots = append(slots, slot) |
||||
|
} |
||||
|
} else { |
||||
|
if slot.ItemID <= int(*fromItem) { |
||||
|
slots = append(slots, slot) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return slots |
||||
|
} |
||||
|
|
||||
|
func addEmptySlot(slots []SlotAPI, slotNum int64, currentBlockNum int64, auctionVars *common.AuctionVariables, fromItem *uint, order string) ([]SlotAPI, error) { |
||||
|
emptySlot := newSlotAPI(slotNum, currentBlockNum, nil, auctionVars) |
||||
|
if order == historydb.OrderAsc { |
||||
|
if emptySlot.ItemID >= int(*fromItem) { |
||||
|
slots = append(slots, emptySlot) |
||||
|
} |
||||
|
} else { |
||||
|
if emptySlot.ItemID <= int(*fromItem) { |
||||
|
slots = append([]SlotAPI{emptySlot}, slots...) |
||||
|
} |
||||
|
} |
||||
|
return slots, nil |
||||
|
} |
||||
|
|
||||
|
func getSlot(c *gin.Context) { |
||||
|
slotNumUint, err := parseParamUint("slotNum", nil, 0, maxUint32, c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
currentBlock, err := h.GetLastBlock() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
auctionVars, err := h.GetAuctionVars() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
slotNum := int64(*slotNumUint) |
||||
|
bid, err := h.GetBestBidAPI(&slotNum) |
||||
|
if err != nil && err != sql.ErrNoRows { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
var slot SlotAPI |
||||
|
if err == sql.ErrNoRows { |
||||
|
slot = newSlotAPI(slotNum, currentBlock.EthBlockNum, nil, auctionVars) |
||||
|
} else { |
||||
|
slot = newSlotAPI(bid.SlotNum, currentBlock.EthBlockNum, &bid, auctionVars) |
||||
|
} |
||||
|
|
||||
|
// JSON response
|
||||
|
c.JSON(http.StatusOK, slot) |
||||
|
} |
||||
|
|
||||
|
func getLimits(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) { |
||||
|
var minLim, maxLim int64 |
||||
|
if fromItem != nil { |
||||
|
if order == historydb.OrderAsc { |
||||
|
if int64(*fromItem) > *minSlotNum { |
||||
|
minLim = int64(*fromItem) |
||||
|
} else { |
||||
|
minLim = *minSlotNum |
||||
|
} |
||||
|
if (minLim + int64(*limit-1)) < *maxSlotNum { |
||||
|
maxLim = minLim + int64(*limit-1) |
||||
|
} else { |
||||
|
maxLim = *maxSlotNum |
||||
|
} |
||||
|
} else { |
||||
|
if int64(*fromItem) < *maxSlotNum { |
||||
|
maxLim = int64(*fromItem) |
||||
|
} else { |
||||
|
maxLim = *maxSlotNum |
||||
|
} |
||||
|
if (maxLim - int64(*limit-1)) < *minSlotNum { |
||||
|
minLim = *minSlotNum |
||||
|
} else { |
||||
|
minLim = maxLim - int64(*limit-1) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return minLim, maxLim |
||||
|
} |
||||
|
|
||||
|
func getLimitsWithAddr(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) { |
||||
|
var minLim, maxLim int64 |
||||
|
if fromItem != nil { |
||||
|
if order == historydb.OrderAsc { |
||||
|
maxLim = *maxSlotNum |
||||
|
if int64(*fromItem) > *minSlotNum { |
||||
|
minLim = int64(*fromItem) |
||||
|
} else { |
||||
|
minLim = *minSlotNum |
||||
|
} |
||||
|
} else { |
||||
|
minLim = *minSlotNum |
||||
|
if int64(*fromItem) < *maxSlotNum { |
||||
|
maxLim = int64(*fromItem) |
||||
|
} else { |
||||
|
maxLim = *maxSlotNum |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return minLim, maxLim |
||||
|
} |
||||
|
|
||||
|
func getSlots(c *gin.Context) { |
||||
|
var slots []SlotAPI |
||||
|
minSlotNumDflt := int64(0) |
||||
|
|
||||
|
// Get filters
|
||||
|
minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, err := parseSlotFilters(c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Pagination
|
||||
|
fromItem, order, limit, err := parsePagination(c) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
currentBlock, err := h.GetLastBlock() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
auctionVars, err := h.GetAuctionVars() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Check filters
|
||||
|
if maxSlotNum == nil && finishedAuction == nil { |
||||
|
retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c) |
||||
|
return |
||||
|
} else if finishedAuction != nil { |
||||
|
if maxSlotNum == nil && !*finishedAuction { |
||||
|
retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c) |
||||
|
return |
||||
|
} else if *finishedAuction { |
||||
|
currentBlock, err := h.GetLastBlock() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
currentSlot := getCurrentSlot(currentBlock.EthBlockNum) |
||||
|
auctionVars, err := h.GetAuctionVars() |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots) |
||||
|
if maxSlotNum == nil { |
||||
|
maxSlotNum = &closedAuctionSlots |
||||
|
} else if closedAuctionSlots < *maxSlotNum { |
||||
|
maxSlotNum = &closedAuctionSlots |
||||
|
} |
||||
|
} |
||||
|
} else if maxSlotNum != nil && minSlotNum != nil { |
||||
|
if *minSlotNum > *maxSlotNum { |
||||
|
retBadReq(errors.New("It is necessary to add valid filter (minSlotNum <= maxSlotNum)"), c) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
if minSlotNum == nil { |
||||
|
minSlotNum = &minSlotNumDflt |
||||
|
} |
||||
|
|
||||
|
// Get bids and pagination according to filters
|
||||
|
var slotMinLim, slotMaxLim int64 |
||||
|
var bids []historydb.BidAPI |
||||
|
var pag *db.Pagination |
||||
|
totalItems := 0 |
||||
|
if wonByEthereumAddress == nil { |
||||
|
slotMinLim, slotMaxLim = getLimits(minSlotNum, maxSlotNum, fromItem, limit, order) |
||||
|
// Get best bids in range maxSlotNum - minSlotNum
|
||||
|
bids, _, err = h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, nil, order) |
||||
|
if err != nil && err != sql.ErrNoRows { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
totalItems = int(*maxSlotNum) - int(*minSlotNum) + 1 |
||||
|
} else { |
||||
|
slotMinLim, slotMaxLim = getLimitsWithAddr(minSlotNum, maxSlotNum, fromItem, limit, order) |
||||
|
bids, pag, err = h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order) |
||||
|
if err != nil && err != sql.ErrNoRows { |
||||
|
retSQLErr(err, c) |
||||
|
return |
||||
|
} |
||||
|
if len(bids) > 0 { |
||||
|
totalItems = pag.TotalItems |
||||
|
*maxSlotNum = int64(pag.LastItem) |
||||
|
*minSlotNum = int64(pag.FirstItem) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Build the slot information with previous bids
|
||||
|
var slotsBids []SlotAPI |
||||
|
if len(bids) > 0 { |
||||
|
slotsBids = newSlotsAPIFromWinnerBids(fromItem, order, bids, currentBlock.EthBlockNum, auctionVars) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Build the other slots
|
||||
|
if wonByEthereumAddress == nil { |
||||
|
// Build hte information of the slots with bids or not
|
||||
|
for i := slotMinLim; i <= slotMaxLim; i++ { |
||||
|
found := false |
||||
|
for j := range slotsBids { |
||||
|
if slotsBids[j].SlotNum == i { |
||||
|
found = true |
||||
|
if order == historydb.OrderAsc { |
||||
|
if slotsBids[j].ItemID >= int(*fromItem) { |
||||
|
slots = append(slots, slotsBids[j]) |
||||
|
} |
||||
|
} else { |
||||
|
if slotsBids[j].ItemID <= int(*fromItem) { |
||||
|
slots = append([]SlotAPI{slotsBids[j]}, slots...) |
||||
|
} |
||||
|
} |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
if !found { |
||||
|
slots, err = addEmptySlot(slots, i, currentBlock.EthBlockNum, auctionVars, fromItem, order) |
||||
|
if err != nil { |
||||
|
retBadReq(err, c) |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else if len(slotsBids) > 0 { |
||||
|
slots = slotsBids |
||||
|
} |
||||
|
|
||||
|
if len(slots) == 0 { |
||||
|
retSQLErr(sql.ErrNoRows, c) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Build succesfull response
|
||||
|
type slotsResponse struct { |
||||
|
Slots []SlotAPI `json:"slots"` |
||||
|
Pagination *db.Pagination `json:"pagination"` |
||||
|
} |
||||
|
c.JSON(http.StatusOK, &slotsResponse{ |
||||
|
Slots: slots, |
||||
|
Pagination: getPagination(totalItems, minSlotNum, maxSlotNum), |
||||
|
}) |
||||
|
} |
@ -0,0 +1,305 @@ |
|||||
|
package api |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"strconv" |
||||
|
"testing" |
||||
|
|
||||
|
"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 testSlot struct { |
||||
|
ItemID int `json:"itemId"` |
||||
|
SlotNum int64 `json:"slotNum"` |
||||
|
FirstBlock int64 `json:"firstBlock"` |
||||
|
LastBlock int64 `json:"lastBlock"` |
||||
|
OpenAuction bool `json:"openAuction"` |
||||
|
WinnerBid *testBid `json:"winnerBid"` |
||||
|
} |
||||
|
|
||||
|
type testSlotsResponse struct { |
||||
|
Slots []testSlot `json:"slots"` |
||||
|
Pagination *db.Pagination `json:"pagination"` |
||||
|
} |
||||
|
|
||||
|
func (t testSlotsResponse) GetPagination() *db.Pagination { |
||||
|
if t.Slots[0].ItemID < t.Slots[len(t.Slots)-1].ItemID { |
||||
|
t.Pagination.FirstReturnedItem = int(t.Slots[0].ItemID) |
||||
|
t.Pagination.LastReturnedItem = int(t.Slots[len(t.Slots)-1].ItemID) |
||||
|
} else { |
||||
|
t.Pagination.LastReturnedItem = int(t.Slots[0].ItemID) |
||||
|
t.Pagination.FirstReturnedItem = int(t.Slots[len(t.Slots)-1].ItemID) |
||||
|
} |
||||
|
return t.Pagination |
||||
|
} |
||||
|
|
||||
|
func (t testSlotsResponse) Len() int { |
||||
|
return len(t.Slots) |
||||
|
} |
||||
|
|
||||
|
func genTestSlots(nSlots int, lastBlockNum int64, bids []testBid, auctionVars common.AuctionVariables) []testSlot { |
||||
|
tSlots := []testSlot{} |
||||
|
bestBids := make(map[int64]testBid) |
||||
|
// It's assumed that bids for each slot will be received in increasing order
|
||||
|
for i := range bids { |
||||
|
bestBids[bids[i].SlotNum] = bids[i] |
||||
|
} |
||||
|
|
||||
|
for i := int64(0); i < int64(nSlots); i++ { |
||||
|
bid, ok := bestBids[i] |
||||
|
firstBlock, lastBlock := getFirstLastBlock(int64(i)) |
||||
|
tSlot := testSlot{ |
||||
|
SlotNum: int64(i), |
||||
|
FirstBlock: firstBlock, |
||||
|
LastBlock: lastBlock, |
||||
|
OpenAuction: isOpenAuction(lastBlockNum, int64(i), auctionVars), |
||||
|
} |
||||
|
if ok { |
||||
|
tSlot.WinnerBid = &bid |
||||
|
} |
||||
|
tSlots = append(tSlots, tSlot) |
||||
|
} |
||||
|
return tSlots |
||||
|
} |
||||
|
|
||||
|
func getEmptyTestSlot(slotNum int64) testSlot { |
||||
|
firstBlock, lastBlock := getFirstLastBlock(slotNum) |
||||
|
slot := testSlot{ |
||||
|
SlotNum: slotNum, |
||||
|
FirstBlock: firstBlock, |
||||
|
LastBlock: lastBlock, |
||||
|
OpenAuction: false, |
||||
|
WinnerBid: nil, |
||||
|
} |
||||
|
return slot |
||||
|
} |
||||
|
|
||||
|
func TestGetSlot(t *testing.T) { |
||||
|
endpoint := apiURL + "slots/" |
||||
|
for _, slot := range tc.slots { |
||||
|
fetchedSlot := testSlot{} |
||||
|
assert.NoError( |
||||
|
t, doGoodReq( |
||||
|
"GET", |
||||
|
endpoint+strconv.Itoa(int(slot.SlotNum)), |
||||
|
nil, &fetchedSlot, |
||||
|
), |
||||
|
) |
||||
|
assertSlot(t, slot, fetchedSlot) |
||||
|
} |
||||
|
|
||||
|
// Slot with WinnerBid == nil
|
||||
|
slotNum := int64(15) |
||||
|
fetchedSlot := testSlot{} |
||||
|
assert.NoError( |
||||
|
t, doGoodReq( |
||||
|
"GET", |
||||
|
endpoint+strconv.Itoa(int(slotNum)), |
||||
|
nil, &fetchedSlot, |
||||
|
), |
||||
|
) |
||||
|
emptySlot := getEmptyTestSlot(slotNum) |
||||
|
assertSlot(t, emptySlot, fetchedSlot) |
||||
|
|
||||
|
// Invalid slotNum
|
||||
|
path := endpoint + strconv.Itoa(-2) |
||||
|
err := doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
} |
||||
|
|
||||
|
func TestGetSlots(t *testing.T) { |
||||
|
endpoint := apiURL + "slots" |
||||
|
fetchedSlots := []testSlot{} |
||||
|
appendIter := func(intr interface{}) { |
||||
|
for i := 0; i < len(intr.(*testSlotsResponse).Slots); i++ { |
||||
|
tmp, err := copystructure.Copy(intr.(*testSlotsResponse).Slots[i]) |
||||
|
if err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
fetchedSlots = append(fetchedSlots, tmp.(testSlot)) |
||||
|
} |
||||
|
} |
||||
|
// All slots with maxSlotNum filter
|
||||
|
maxSlotNum := tc.slots[len(tc.slots)-1].SlotNum + 5 |
||||
|
limit := 1 |
||||
|
path := fmt.Sprintf("%s?maxSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, limit) |
||||
|
err := doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
allSlots := tc.slots |
||||
|
for i := tc.slots[len(tc.slots)-1].SlotNum; i < maxSlotNum; i++ { |
||||
|
emptySlot := getEmptyTestSlot(i + 1) |
||||
|
allSlots = append(allSlots, emptySlot) |
||||
|
} |
||||
|
assertSlots(t, allSlots, fetchedSlots) |
||||
|
|
||||
|
// All slots with maxSlotNum filter, in reverse order
|
||||
|
fetchedSlots = []testSlot{} |
||||
|
limit = 3 |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
|
||||
|
flippedAllSlots := []testSlot{} |
||||
|
for i := len(allSlots) - 1; i >= 0; i-- { |
||||
|
flippedAllSlots = append(flippedAllSlots, allSlots[i]) |
||||
|
} |
||||
|
assertSlots(t, flippedAllSlots, fetchedSlots) |
||||
|
|
||||
|
// maxSlotNum & wonByEthereumAddress
|
||||
|
fetchedSlots = []testSlot{} |
||||
|
limit = 1 |
||||
|
bidderAddr := tc.coordinators[2].Bidder |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, bidderAddr.String(), limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
bidderAddressSlots := []testSlot{} |
||||
|
for i := 0; i < len(tc.slots); i++ { |
||||
|
if tc.slots[i].WinnerBid != nil { |
||||
|
if tc.slots[i].WinnerBid.Bidder == bidderAddr { |
||||
|
bidderAddressSlots = append(bidderAddressSlots, tc.slots[i]) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, bidderAddressSlots, fetchedSlots) |
||||
|
|
||||
|
// maxSlotNum & wonByEthereumAddress, in reverse order
|
||||
|
fetchedSlots = []testSlot{} |
||||
|
limit = 1 |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, bidderAddr.String(), limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
flippedBidderAddressSlots := []testSlot{} |
||||
|
for i := len(bidderAddressSlots) - 1; i >= 0; i-- { |
||||
|
flippedBidderAddressSlots = append(flippedBidderAddressSlots, bidderAddressSlots[i]) |
||||
|
} |
||||
|
assertSlots(t, flippedBidderAddressSlots, fetchedSlots) |
||||
|
|
||||
|
// finishedAuction
|
||||
|
fetchedSlots = []testSlot{} |
||||
|
limit = 15 |
||||
|
path = fmt.Sprintf("%s?finishedAuction=%t&limit=%d&fromItem=", endpoint, true, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
|
||||
|
currentSlot := getCurrentSlot(tc.blocks[len(tc.blocks)-1].EthBlockNum) |
||||
|
finishedAuctionSlots := []testSlot{} |
||||
|
for i := 0; i < len(tc.slots); i++ { |
||||
|
finishAuction := currentSlot + int64(tc.auctionVars.ClosedAuctionSlots) |
||||
|
if tc.slots[i].SlotNum <= finishAuction { |
||||
|
finishedAuctionSlots = append(finishedAuctionSlots, tc.slots[i]) |
||||
|
} else { |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, finishedAuctionSlots, fetchedSlots) |
||||
|
|
||||
|
//minSlot + maxSlot
|
||||
|
limit = 10 |
||||
|
minSlotNum := tc.slots[3].SlotNum |
||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum - 1 |
||||
|
fetchedSlots = []testSlot{} |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
minMaxBatchNumSlots := []testSlot{} |
||||
|
for i := 0; i < len(tc.slots); i++ { |
||||
|
if tc.slots[i].SlotNum >= minSlotNum && tc.slots[i].SlotNum <= maxSlotNum { |
||||
|
minMaxBatchNumSlots = append(minMaxBatchNumSlots, tc.slots[i]) |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, minMaxBatchNumSlots, fetchedSlots) |
||||
|
|
||||
|
//minSlot + maxSlot
|
||||
|
limit = 15 |
||||
|
minSlotNum = tc.slots[0].SlotNum |
||||
|
maxSlotNum = tc.slots[0].SlotNum |
||||
|
fetchedSlots = []testSlot{} |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
minMaxBatchNumSlots = []testSlot{} |
||||
|
for i := 0; i < len(tc.slots); i++ { |
||||
|
if tc.slots[i].SlotNum >= minSlotNum && tc.slots[i].SlotNum <= maxSlotNum { |
||||
|
minMaxBatchNumSlots = append(minMaxBatchNumSlots, tc.slots[i]) |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, minMaxBatchNumSlots, fetchedSlots) |
||||
|
|
||||
|
// Only empty Slots
|
||||
|
limit = 2 |
||||
|
minSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 1 |
||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 5 |
||||
|
fetchedSlots = []testSlot{} |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
emptySlots := []testSlot{} |
||||
|
for i := 0; i < len(allSlots); i++ { |
||||
|
if allSlots[i].SlotNum >= minSlotNum && allSlots[i].SlotNum <= maxSlotNum { |
||||
|
emptySlots = append(emptySlots, allSlots[i]) |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, emptySlots, fetchedSlots) |
||||
|
|
||||
|
// Only empty Slots, in reverse order
|
||||
|
limit = 4 |
||||
|
minSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 1 |
||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 5 |
||||
|
fetchedSlots = []testSlot{} |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit) |
||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter) |
||||
|
assert.NoError(t, err) |
||||
|
flippedEmptySlots := []testSlot{} |
||||
|
for i := 0; i < len(flippedAllSlots); i++ { |
||||
|
if flippedAllSlots[i].SlotNum >= minSlotNum && flippedAllSlots[i].SlotNum <= maxSlotNum { |
||||
|
flippedEmptySlots = append(flippedEmptySlots, flippedAllSlots[i]) |
||||
|
} |
||||
|
} |
||||
|
assertSlots(t, flippedEmptySlots, fetchedSlots) |
||||
|
|
||||
|
// 400
|
||||
|
// No filters
|
||||
|
path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Invalid maxSlotNum
|
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d", endpoint, -2) |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Invalid wonByEthereumAddress
|
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s", endpoint, maxSlotNum, "0xG0000001") |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// Invalid minSlotNum / maxSlotNum (minSlotNum > maxSlotNum)
|
||||
|
maxSlotNum = tc.slots[1].SlotNum |
||||
|
minSlotNum = tc.slots[4].SlotNum |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit) |
||||
|
err = doBadReq("GET", path, nil, 400) |
||||
|
assert.NoError(t, err) |
||||
|
// 404
|
||||
|
maxSlotNum = tc.slots[1].SlotNum |
||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, tc.coordinators[3].Bidder.String(), limit) |
||||
|
err = doBadReq("GET", path, nil, 404) |
||||
|
assert.NoError(t, err) |
||||
|
} |
||||
|
|
||||
|
func assertSlots(t *testing.T, expected, actual []testSlot) { |
||||
|
assert.Equal(t, len(expected), len(actual)) |
||||
|
for i := 0; i < len(expected); i++ { |
||||
|
assertSlot(t, expected[i], actual[i]) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func assertSlot(t *testing.T, expected, actual testSlot) { |
||||
|
if actual.WinnerBid != nil { |
||||
|
assert.Equal(t, expected.WinnerBid.Timestamp.Unix(), actual.WinnerBid.Timestamp.Unix()) |
||||
|
expected.WinnerBid.Timestamp = actual.WinnerBid.Timestamp |
||||
|
actual.WinnerBid.ItemID = expected.WinnerBid.ItemID |
||||
|
} |
||||
|
actual.ItemID = expected.ItemID |
||||
|
assert.Equal(t, expected, actual) |
||||
|
} |