Skip to content

Commit

Permalink
Merge pull request #51 from 3scale/product-pagination
Browse files Browse the repository at this point in the history
product list pagination
  • Loading branch information
eguzki authored Mar 10, 2023
2 parents f7d8d99 + 20a2ca0 commit d0bfbb0
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 15 deletions.
59 changes: 48 additions & 11 deletions client/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
)

const (
productListResourceEndpoint = "/admin/api/services.json"
productResourceEndpoint = "/admin/api/services/%d.json"
productMethodListResourceEndpoint = "/admin/api/services/%d/metrics/%d/methods.json"
productMethodResourceEndpoint = "/admin/api/services/%d/metrics/%d/methods/%d.json"
productMetricListResourceEndpoint = "/admin/api/services/%d/metrics.json"
productMetricResourceEndpoint = "/admin/api/services/%d/metrics/%d.json"
productMappingRuleListResourceEndpoint = "/admin/api/services/%d/proxy/mapping_rules.json"
productMappingRuleResourceEndpoint = "/admin/api/services/%d/proxy/mapping_rules/%d.json"
productProxyResourceEndpoint = "/admin/api/services/%d/proxy.json"
productProxyDeployResourceEndpoint = "/admin/api/services/%d/proxy/deploy.json"
productListResourceEndpoint = "/admin/api/services.json"
productResourceEndpoint = "/admin/api/services/%d.json"
productMethodListResourceEndpoint = "/admin/api/services/%d/metrics/%d/methods.json"
productMethodResourceEndpoint = "/admin/api/services/%d/metrics/%d/methods/%d.json"
productMetricListResourceEndpoint = "/admin/api/services/%d/metrics.json"
productMetricResourceEndpoint = "/admin/api/services/%d/metrics/%d.json"
productMappingRuleListResourceEndpoint = "/admin/api/services/%d/proxy/mapping_rules.json"
productMappingRuleResourceEndpoint = "/admin/api/services/%d/proxy/mapping_rules/%d.json"
productProxyResourceEndpoint = "/admin/api/services/%d/proxy.json"
productProxyDeployResourceEndpoint = "/admin/api/services/%d/proxy/deploy.json"
PRODUCTS_PER_PAGE int = 500
)

// BackendApi Read 3scale Backend
Expand Down Expand Up @@ -109,12 +111,47 @@ func (c *ThreeScaleClient) DeleteProduct(id int64) error {
return handleJsonResp(resp, http.StatusOK, nil)
}

// ListProducts List existing products
func (c *ThreeScaleClient) ListProducts() (*ProductList, error) {
// Keep asking until the results length is lower than "per_page" param
currentPage := 1
productList := &ProductList{}

allResultsPerPage := false
for next := true; next; next = allResultsPerPage {
tmpProductList, err := c.ListProductsPerPage(currentPage, PRODUCTS_PER_PAGE)
if err != nil {
return nil, err
}

productList.Products = append(productList.Products, tmpProductList.Products...)

allResultsPerPage = len(tmpProductList.Products) == PRODUCTS_PER_PAGE
currentPage += 1
}

return productList, nil

}

// ListProductsPerPage List existing products in a single page
// paginationValues[0] = Page in the paginated list. Defaults to 1 for the API, as the client will not send the page param.
// paginationValues[1] = Number of results per page. Default and max is 500 for the aPI, as the client will not send the per_page param.
func (c *ThreeScaleClient) ListProductsPerPage(paginationValues ...int) (*ProductList, error) {
queryValues := url.Values{}

if len(paginationValues) > 0 {
queryValues.Add("page", strconv.Itoa(paginationValues[0]))
}

if len(paginationValues) > 1 {
queryValues.Add("per_page", strconv.Itoa(paginationValues[1]))
}

req, err := c.buildGetReq(productListResourceEndpoint)
if err != nil {
return nil, err
}
req.URL.RawQuery = queryValues.Encode()

resp, err := c.httpClient.Do(req)
if err != nil {
Expand Down
137 changes: 133 additions & 4 deletions client/product_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"testing"
)
Expand Down Expand Up @@ -1146,18 +1147,58 @@ func TestDeleteProduct(t *testing.T) {
}

func TestListProducts(t *testing.T) {

productGenerator := func(startingIndex, n int) ProductList {
pList := ProductList{
Products: make([]Product, 0, n),
}

for idx := 0; idx < n; idx++ {
pList.Products = append(pList.Products, Product{
Element: ProductItem{ID: int64(idx + startingIndex)},
})
}

return pList
}

httpClient := NewTestClient(func(req *http.Request) *http.Response {
// Will serve: 3 pages
// page 1 => PRODUCTS_PER_PAGE
// page 2 => PRODUCTS_PER_PAGE
// page 3 => 51
if req.URL.Path != productListResourceEndpoint {
t.Fatalf("Path does not match. Expected [%s]; got [%s]", backendListResourceEndpoint, req.URL.Path)
t.Fatalf("Path does not match. Expected [%s]; got [%s]", productListResourceEndpoint, req.URL.Path)
}

if req.Method != http.MethodGet {
t.Fatalf("Method does not match. Expected [%s]; got [%s]", http.MethodGet, req.Method)
}

if req.URL.Query().Get("per_page") != strconv.Itoa(PRODUCTS_PER_PAGE) {
t.Fatalf("per_page param does not match. Expected [%d]; got [%s]", PRODUCTS_PER_PAGE, req.URL.Query().Get("per_page"))
}

var list ProductList

if req.URL.Query().Get("page") == "1" {
list = productGenerator(PRODUCTS_PER_PAGE*0, PRODUCTS_PER_PAGE)
} else if req.URL.Query().Get("page") == "2" {
list = productGenerator(PRODUCTS_PER_PAGE*1, PRODUCTS_PER_PAGE)
} else if req.URL.Query().Get("page") == "3" {
list = productGenerator(PRODUCTS_PER_PAGE*2, 51)
} else {
t.Fatalf("page param unexpected value; got [%s]", req.URL.Query().Get("page"))
}

responseBodyBytes, err := json.Marshal(list)
if err != nil {
t.Fatal(err)
}

return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(helperLoadBytes(t, "product_list_fixture.json"))),
Body: ioutil.NopCloser(bytes.NewBuffer(responseBodyBytes)),
Header: make(http.Header),
}
})
Expand All @@ -1173,7 +1214,95 @@ func TestListProducts(t *testing.T) {
t.Fatal("product list returned nil")
}

if len(productList.Products) != 2 {
t.Fatalf("Then number of products does not match. Expected [%d]; got [%d]", 2, len(productList.Products))
if len(productList.Products) != 2*PRODUCTS_PER_PAGE+51 {
t.Fatalf("Then number of products does not match. Expected [%d]; got [%d]", 2*PRODUCTS_PER_PAGE+51, len(productList.Products))
}
}

func TestListProductsPerPage(t *testing.T) {
t.Run("page and per_page params used", func(subT *testing.T) {
var (
pageNum int = 4
perPage int = 2
)
httpClient := NewTestClient(func(req *http.Request) *http.Response {
if req.URL.Path != productListResourceEndpoint {
subT.Fatalf("Path does not match. Expected [%s]; got [%s]", productListResourceEndpoint, req.URL.Path)
}

if req.Method != http.MethodGet {
subT.Fatalf("Method does not match. Expected [%s]; got [%s]", http.MethodGet, req.Method)
}

if req.URL.Query().Get("page") != strconv.Itoa(pageNum) {
subT.Fatalf("page param does not match. Expected [%d]; got [%s]", pageNum, req.URL.Query().Get("page"))
}

if req.URL.Query().Get("per_page") != strconv.Itoa(perPage) {
subT.Fatalf("page param does not match. Expected [%d]; got [%s]", perPage, req.URL.Query().Get("per_page"))
}

return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(helperLoadBytes(subT, "product_list_fixture.json"))),
Header: make(http.Header),
}
})

credential := "someAccessToken"
c := NewThreeScale(NewTestAdminPortal(subT), credential, httpClient)
productList, err := c.ListProductsPerPage(pageNum, perPage)
if err != nil {
subT.Fatal(err)
}

if productList == nil {
subT.Fatal("product list returned nil")
}

if len(productList.Products) != 2 {
subT.Fatalf("Then number of products does not match. Expected [%d]; got [%d]", 2, len(productList.Products))
}
})

t.Run("page and per_page params not used", func(subT *testing.T) {
httpClient := NewTestClient(func(req *http.Request) *http.Response {
if req.URL.Path != productListResourceEndpoint {
subT.Fatalf("Path does not match. Expected [%s]; got [%s]", productListResourceEndpoint, req.URL.Path)
}

if req.Method != http.MethodGet {
subT.Fatalf("Method does not match. Expected [%s]; got [%s]", http.MethodGet, req.Method)
}

if req.URL.Query().Get("page") != "" {
subT.Fatalf("Query param page does not match. Expected empty; got [%s]", req.URL.Query().Get("page"))
}

if req.URL.Query().Get("per_page") != "" {
subT.Fatalf("page param does not match. Expected empty; got [%s]", req.URL.Query().Get("per_page"))
}

return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(helperLoadBytes(subT, "product_list_fixture.json"))),
Header: make(http.Header),
}
})

credential := "someAccessToken"
c := NewThreeScale(NewTestAdminPortal(subT), credential, httpClient)
productList, err := c.ListProductsPerPage()
if err != nil {
subT.Fatal(err)
}

if productList == nil {
subT.Fatal("product list returned nil")
}

if len(productList.Products) != 2 {
subT.Fatalf("Then number of products does not match. Expected [%d]; got [%d]", 2, len(productList.Products))
}
})
}

0 comments on commit d0bfbb0

Please sign in to comment.