diff --git a/api/api_types.go b/api/api_types.go index 65f20aa5f..b5e1c2550 100644 --- a/api/api_types.go +++ b/api/api_types.go @@ -37,6 +37,12 @@ type ElectionSummary struct { ChainID string `json:"chainId"` } +// ElectionsList wraps the elections list to consistently return the list inside an object, +// return empty object if the list does not contains any result +type ElectionsList struct { + Elections []ElectionSummary `json:"elections"` +} + // ElectionResults is the struct used to wrap the results of an election type ElectionResults struct { // ABIEncoded is the abi encoded election results diff --git a/api/elections.go b/api/elections.go index 9a96e943b..d2408d305 100644 --- a/api/elections.go +++ b/api/elections.go @@ -186,19 +186,15 @@ func (a *API) electionFullListByPage(ctx *httprouter.HTTPContext, page int) erro return ErrCantFetchElectionList.WithErr(err) } - list := []ElectionSummary{} + list := ElectionsList{} for _, eid := range elections { e, err := a.indexer.ProcessInfo(eid) if err != nil { return ErrCantFetchElection.Withf("(%x): %v", eid, err) } - list = append(list, a.electionSummary(e)) + list.Elections = append(list.Elections, a.electionSummary(e)) } - // wrap list in a struct to consistently return list in an object, return empty - // object if the list does not contains any result - data, err := json.Marshal(struct { - Elections []ElectionSummary `json:"elections"` - }{list}) + data, err := json.Marshal(list) if err != nil { return ErrMarshalingServerJSONFailed.WithErr(err) } @@ -702,20 +698,16 @@ func (a *API) electionFilterPaginatedHandler(msg *apirest.APIdata, ctx *httprout return ErrElectionNotFound } - var list []ElectionSummary + var list ElectionsList // get election summary for _, eid := range elections { e, err := a.indexer.ProcessInfo(eid) if err != nil { return ErrCantFetchElection.WithErr(err) } - list = append(list, a.electionSummary(e)) + list.Elections = append(list.Elections, a.electionSummary(e)) } - data, err := json.Marshal(struct { - Elections []ElectionSummary `json:"elections"` - }{ - Elections: list, - }) + data, err := json.Marshal(list) if err != nil { return ErrMarshalingServerJSONFailed.WithErr(err) } diff --git a/test/api_test.go b/test/api_test.go index f28e70d1c..d854e1a18 100644 --- a/test/api_test.go +++ b/test/api_test.go @@ -86,7 +86,7 @@ func TestAPIcensusAndVote(t *testing.T) { qt.Assert(t, censusData.Weight.String(), qt.Equals, "1") electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - election := createElection(t, c, server.Account, electionParams, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false) + election := createElection(t, c, server.Account, electionParams, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false, 0) // Block 2 server.VochainAPP.AdvanceTestBlock() @@ -441,7 +441,7 @@ func runAPIElectionCostWithParams(t *testing.T, qt.Assert(t, requestAccount(t, c, signer.Address().String()).Balance, qt.Equals, initialBalance) - createElection(t, c, signer, electionParams, censusRoot, startBlock, server.VochainAPP.ChainID(), false) + createElection(t, c, signer, electionParams, censusRoot, startBlock, server.VochainAPP.ChainID(), false, 0) // Block 3 server.VochainAPP.AdvanceTestBlock() @@ -507,6 +507,7 @@ func createElection(t testing.TB, c *testutil.TestHTTPclient, startBlock uint32, chainID string, encryptedMetadata bool, + nonce uint32, ) api.ElectionCreate { metadataBytes, err := json.Marshal( &api.ElectionMetadata{ @@ -529,7 +530,7 @@ func createElection(t testing.TB, c *testutil.TestHTTPclient, tx := models.Tx_NewProcess{ NewProcess: &models.NewProcessTx{ Txtype: models.TxType_NEW_PROCESS, - Nonce: 0, + Nonce: nonce, Process: &models.Process{ StartBlock: startBlock, BlockCount: electionParams.ElectionDuration, @@ -733,7 +734,7 @@ func TestAPIBuildElectionID(t *testing.T) { // create a new election electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - response := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), false) + response := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), false, 0) // Block 4 server.VochainAPP.AdvanceTestBlock() @@ -804,7 +805,7 @@ func TestAPIEncryptedMetadata(t *testing.T) { // create a new election electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} - electionResponse := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), true) + electionResponse := createElection(t, c, signer, electionParams, censusRoot, 0, server.VochainAPP.ChainID(), true, 0) // Block 4 server.VochainAPP.AdvanceTestBlock() @@ -830,3 +831,77 @@ func TestAPIEncryptedMetadata(t *testing.T) { qt.Assert(t, err, qt.IsNil) qt.Assert(t, metadata.Title["default"], qt.Equals, "test election") } + +func TestAPIElectionsByPage(t *testing.T) { + server := testcommon.APIserver{} + server.Start(t, + api.ChainHandler, + api.CensusHandler, + api.VoteHandler, + api.AccountHandler, + api.ElectionHandler, + api.WalletHandler, + ) + // Block 1 + server.VochainAPP.AdvanceTestBlock() + + token1 := uuid.New() + c := testutil.NewTestHTTPclient(t, server.ListenAddr, &token1) + + // create a new census + resp, code := c.Request("POST", nil, "censuses", "weighted") + qt.Assert(t, code, qt.Equals, 200) + censusData := &api.Census{} + qt.Assert(t, json.Unmarshal(resp, censusData), qt.IsNil) + id1 := censusData.CensusID.String() + + // add a bunch of keys and values (weights) + rnd := testutil.NewRandom(1) + cparts := api.CensusParticipants{} + for i := 1; i < 10; i++ { + cparts.Participants = append(cparts.Participants, api.CensusParticipant{ + Key: rnd.RandomBytes(20), + Weight: (*types.BigInt)(big.NewInt(int64(1))), + }) + } + _, code = c.Request("POST", &cparts, "censuses", id1, "participants") + qt.Assert(t, code, qt.Equals, 200) + + resp, code = c.Request("POST", nil, "censuses", id1, "publish") + qt.Assert(t, code, qt.Equals, 200) + qt.Assert(t, json.Unmarshal(resp, censusData), qt.IsNil) + qt.Assert(t, censusData.CensusID, qt.IsNotNil) + + electionParams := electionprice.ElectionParameters{ElectionDuration: 100, MaxCensusSize: 100} + for nonce := uint32(0); nonce < 20; nonce++ { + createElection(t, c, server.Account, electionParams, censusData.CensusRoot, 0, server.VochainAPP.ChainID(), false, nonce) + } + + // Block 2 + server.VochainAPP.AdvanceTestBlock() + waitUntilHeight(t, c, 2) + + // Get the list of elections and check it + fetchEL := func(method string, jsonBody any, query string, urlPath ...string) api.ElectionsList { + resp, code = c.RequestWithQuery(method, jsonBody, query, urlPath...) + elections := api.ElectionsList{} + qt.Assert(t, code, qt.Equals, 200) + err := json.Unmarshal(resp, &elections) + qt.Assert(t, err, qt.IsNil) + return elections + } + + el := make(map[string]api.ElectionsList) + el["0"] = fetchEL("GET", nil, "", "elections") + el["1"] = fetchEL("GET", nil, "page=1", "elections") + el["p0"] = fetchEL("GET", nil, "", "elections", "page", "0") + el["p1"] = fetchEL("GET", nil, "", "elections", "page", "1") + + qt.Assert(t, el["0"], qt.Not(qt.DeepEquals), el["1"]) + qt.Assert(t, el["0"], qt.DeepEquals, el["p0"]) + qt.Assert(t, el["1"], qt.DeepEquals, el["p1"]) + + for _, item := range el { + qt.Assert(t, len(item.Elections), qt.Equals, api.MaxPageSize) + } +} diff --git a/test/testcommon/testutil/apiclient.go b/test/testcommon/testutil/apiclient.go index d7ae50a2f..7a560dbf7 100644 --- a/test/testcommon/testutil/apiclient.go +++ b/test/testcommon/testutil/apiclient.go @@ -21,16 +21,27 @@ type TestHTTPclient struct { t testing.TB } -func (c *TestHTTPclient) Request(method string, jsonBody any, urlPath ...string) ([]byte, int) { - body, err := json.Marshal(jsonBody) +func (c *TestHTTPclient) RequestWithQuery(method string, jsonBody any, query string, urlPath ...string) ([]byte, int) { + u, err := url.Parse(c.addr.String()) qt.Assert(c.t, err, qt.IsNil) + u.RawQuery = query + return c.request(method, u, jsonBody, urlPath...) +} + +func (c *TestHTTPclient) Request(method string, jsonBody any, urlPath ...string) ([]byte, int) { u, err := url.Parse(c.addr.String()) qt.Assert(c.t, err, qt.IsNil) + return c.request(method, u, jsonBody, urlPath...) +} + +func (c *TestHTTPclient) request(method string, u *url.URL, jsonBody any, urlPath ...string) ([]byte, int) { u.Path = path.Join(u.Path, path.Join(urlPath...)) headers := http.Header{} if c.token != nil { headers = http.Header{"Authorization": []string{"Bearer " + c.token.String()}} } + body, err := json.Marshal(jsonBody) + qt.Assert(c.t, err, qt.IsNil) c.t.Logf("querying %s", u) resp, err := c.c.Do(&http.Request{ Method: method,