Skip to content

Commit

Permalink
feat: add pagination tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Jul 23, 2020
1 parent 7fe0901 commit e3aa81b
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 12 deletions.
2 changes: 1 addition & 1 deletion identity/handler.go
Expand Up @@ -80,7 +80,7 @@ type listIdentityParameters struct {
// default: 100
// min: 1
// max: 500
ItemsPerPage int `json:"per_page"`
PerPage int `json:"per_page"`

// Pagination Page
//
Expand Down
2 changes: 1 addition & 1 deletion identity/handler_test.go
Expand Up @@ -237,7 +237,7 @@ func TestHandler(t *testing.T) {
t.Run("case=should list all identities", func(t *testing.T) {
res := get(t, "/identities", http.StatusOK)
assert.Empty(t, res.Get("0.credentials").String(), "%s", res.Raw)
assert.EqualValues(t, "baz", res.Get("0.traits.bar").String(), "%s", res.Raw)
assert.EqualValues(t, "baz", res.Get(`#(traits.bar=="baz").traits.bar`).String(), "%s", res.Raw)
})

t.Run("case=should not be able to update an identity that does not exist yet", func(t *testing.T) {
Expand Down
9 changes: 4 additions & 5 deletions persistence/sql/persister_identity.go
Expand Up @@ -199,16 +199,15 @@ func (p *Persister) CreateIdentity(ctx context.Context, i *identity.Identity) er
})
}

func (p *Persister) ListIdentities(ctx context.Context, page, limit int) ([]identity.Identity, error) {
func (p *Persister) ListIdentities(ctx context.Context, page, perPage int) ([]identity.Identity, error) {
is := make([]identity.Identity, 0)

/* #nosec G201 TableName is static */
if err := sqlcon.HandleError(p.GetConnection(ctx).
Paginate(page, limit).Order("id DESC").
Eager("VerifiableAddresses", "RecoveryAddresses").
All(&is)); err != nil {
if err := sqlcon.HandleError(p.GetConnection(ctx).Paginate(page, perPage).Order("id DESC").
Eager("VerifiableAddresses", "RecoveryAddresses").All(&is)); err != nil {
return nil, err
}

for i := range is {
if err := p.injectTraitsSchemaURL(&(is[i])); err != nil {
return nil, err
Expand Down
10 changes: 5 additions & 5 deletions x/pagination.go
Expand Up @@ -8,7 +8,8 @@ import (
"strings"
)

const paginationMaxItems = 500
const paginationMaxItems = 1000
const paginationDefaultItems = 250

// ParsePagination parses limit and page from *http.Request with given limits and defaults.
func ParsePagination(r *http.Request) (page, itemsPerPage int) {
Expand All @@ -23,10 +24,10 @@ func ParsePagination(r *http.Request) (page, itemsPerPage int) {
}

if limitParam := r.URL.Query().Get("per_page"); limitParam == "" {
itemsPerPage = paginationMaxItems
itemsPerPage = paginationDefaultItems
} else {
if limit64, err := strconv.ParseInt(limitParam, 10, 64); err != nil {
itemsPerPage = paginationMaxItems
itemsPerPage = paginationDefaultItems
} else {
itemsPerPage = int(limit64)
}
Expand All @@ -50,7 +51,7 @@ func ParsePagination(r *http.Request) (page, itemsPerPage int) {
func header(u *url.URL, rel string, limit, page int64) string {
q := u.Query()
q.Set("per_page", fmt.Sprintf("%d", limit))
q.Set("page", fmt.Sprintf("%d", page))
q.Set("page", fmt.Sprintf("%d", page/limit))
u.RawQuery = q.Encode()
return fmt.Sprintf("<%s>; rel=\"%s\"", u.String(), rel)
}
Expand Down Expand Up @@ -109,5 +110,4 @@ func PaginationHeader(w http.ResponseWriter, u *url.URL, total int64, page, item
header(u, "prev", itemsPerPage64, ((offset/itemsPerPage64)-1)*itemsPerPage64),
header(u, "last", itemsPerPage64, lastOffset),
}, ","))
return
}
133 changes: 133 additions & 0 deletions x/pagination_test.go
@@ -0,0 +1,133 @@
/*
* Copyright © 2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Aeneas Rekkas <aeneas+oss@aeneas.io>
* @copyright 2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
* @license Apache-2.0
*/
package x

import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/ory/x/urlx"
)

func TestPaginationHeader(t *testing.T) {
u := urlx.ParseOrPanic("http://example.com")

t.Run("Create previous and first but not next or last if at the end", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 120, 2, 50)

expect := strings.Join([]string{
"<http://example.com?page=0&per_page=50>; rel=\"first\"",
"<http://example.com?page=1&per_page=50>; rel=\"prev\"",
}, ",")

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})

t.Run("Create next and last, but not previous or first if at the beginning", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 120, 0, 50)

expect := strings.Join([]string{
"<http://example.com?page=1&per_page=50>; rel=\"next\"",
"<http://example.com?page=2&per_page=50>; rel=\"last\"",
}, ",")

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})

t.Run("Create previous, next, first, and last if in the middle", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 300, 3, 50)

expect := strings.Join([]string{
"<http://example.com?page=0&per_page=50>; rel=\"first\"",
"<http://example.com?page=4&per_page=50>; rel=\"next\"",
"<http://example.com?page=2&per_page=50>; rel=\"prev\"",
"<http://example.com?page=5&per_page=50>; rel=\"last\"",
}, ",")

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})

t.Run("Header should default limit to 1 no limit was provided", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 100, 20, 0)

expect := strings.Join([]string{
"<http://example.com?page=0&per_page=1>; rel=\"first\"",
"<http://example.com?page=21&per_page=1>; rel=\"next\"",
"<http://example.com?page=19&per_page=1>; rel=\"prev\"",
"<http://example.com?page=99&per_page=1>; rel=\"last\"",
}, ",")

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})

t.Run("Create previous, next, first, but not last if in the middle and no total was provided", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 0, 3, 50)

expect := strings.Join([]string{
"<http://example.com?page=0&per_page=50>; rel=\"first\"",
"<http://example.com?page=4&per_page=50>; rel=\"next\"",
"<http://example.com?page=2&per_page=50>; rel=\"prev\"",
}, ",")

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})

t.Run("Create only first if the limits provided exceeds the number of clients found", func(t *testing.T) {
r := httptest.NewRecorder()
PaginationHeader(r, u, 5, 0, 50)

expect := "<http://example.com?page=0&per_page=5>; rel=\"first\""

assert.EqualValues(t, expect, r.Result().Header.Get("Link"))
})
}

func TestParsePagination(t *testing.T) {
for _, tc := range []struct {
d string
url string
expectedItemsPerPage int
expectedPage int
}{
{"normal", "http://localhost/foo?per_page=10&page=10", 10, 10},
{"defaults", "http://localhost/foo", paginationDefaultItems, 0},
{"limits", "http://localhost/foo?per_page=2000", paginationMaxItems, 0},
{"negatives", "http://localhost/foo?per_page=-1&page=-1", 1, 0},
{"invalid_params", "http://localhost/foo?per_page=a&page=b", paginationDefaultItems, 0},
} {
t.Run(fmt.Sprintf("case=%s", tc.d), func(t *testing.T) {
u, _ := url.Parse(tc.url)
page, perPage := ParsePagination(&http.Request{URL: u})
assert.EqualValues(t, perPage, tc.expectedItemsPerPage, "per_page")
assert.EqualValues(t, page, tc.expectedPage, "page")
})
}
}

0 comments on commit e3aa81b

Please sign in to comment.