Skip to content

Commit

Permalink
Merge pull request #84 from joshbeard/eb-3408-pagination
Browse files Browse the repository at this point in the history
Pagination: more consistent, add to changelogs (BREAKING CHANGE)
  • Loading branch information
joshbeard authored Aug 19, 2024
2 parents e78d6b5 + d762d59 commit cda2b9b
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 304 deletions.
2 changes: 1 addition & 1 deletion readme/api_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func Test_APIRegistry_Get(t *testing.T) {

t.Run("when API responds with 404", func(t *testing.T) {
// Arrange
expect := testdata.APISpecResponseVersionEmtpy
expect := testdata.APISpecResponseVersionEmpty
gock.New(TestClient.APIURL).
Get(readme.APIRegistryEndpoint + "/invalid").
Reply(404).
Expand Down
45 changes: 8 additions & 37 deletions readme/api_specification.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,47 +101,18 @@ type APISpecificationSaved struct {
//
// API Reference: https://docs.readme.com/reference/getapispecification
func (c APISpecificationClient) GetAll(options ...RequestOptions) ([]APISpecification, *APIResponse, error) {
var specifications []APISpecification
var apiResponse *APIResponse
var err error
hasNextPage := false

// Initialize pagination counter.
page := 1
if len(options) > 0 {
if options[0].Page != 0 {
page = options[0].Page
}
var results []APISpecification
opts := parseRequestOptions(options)
apiResponse, err := c.client.fetchAllPages(APISpecificationEndpoint, opts, &results)
if err != nil {
return results, apiResponse, fmt.Errorf("unable to retrieve specifications: %w", err)
}

for {
var specPaginatedResult []APISpecification

apiRequest := &APIRequest{
Method: "GET",
Endpoint: APISpecificationEndpoint,
UseAuth: true,
OkStatusCode: []int{200},
Response: &specPaginatedResult,
}
if len(options) > 0 {
apiRequest.RequestOptions = options[0]
}

apiResponse, hasNextPage, err = c.client.paginatedRequest(apiRequest, page)
if err != nil {
return specifications, apiResponse, fmt.Errorf("unable to retrieve specifications: %w", err)
}
specifications = append(specifications, specPaginatedResult...)

if !hasNextPage {
break
}

page = page + 1
if len(results) == 0 {
return nil, apiResponse, nil
}

return specifications, apiResponse, nil
return results, apiResponse, nil
}

// Get a single API specification with a provided ID.
Expand Down
6 changes: 3 additions & 3 deletions readme/api_specification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func Test_APISpecification_GetAll(t *testing.T) {
Reply(400).
AddHeader("Link", `<`+apiSpecEndpointPaginated+`&page=2>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "1").
JSON(testdata.APISpecResponseVersionEmtpy.APIErrorResponse)
JSON(testdata.APISpecResponseVersionEmpty.APIErrorResponse)
defer gock.Off()

// Act
Expand Down Expand Up @@ -272,7 +272,7 @@ func Test_APISpecification_Get(t *testing.T) {
gock.New(TestClient.APIURL).
Get(readme.APISpecificationEndpoint).
Reply(400).
JSON(testdata.APISpecResponseVersionEmtpy.APIErrorResponse)
JSON(testdata.APISpecResponseVersionEmpty.APIErrorResponse)
defer gock.Off()

expect := "unable to retrieve API specifications"
Expand Down Expand Up @@ -442,7 +442,7 @@ func Test_APISpecification_Delete(t *testing.T) {

t.Run("when called with invalid ID and API response with 400", func(t *testing.T) {
// Arrange
expect := testdata.APISpecResponseVersionEmtpy.APIErrorResponse
expect := testdata.APISpecResponseVersionEmpty.APIErrorResponse
gock.New(TestClient.APIURL).
Delete(readme.APISpecificationEndpoint + "/0123456789").
Reply(400).
Expand Down
47 changes: 9 additions & 38 deletions readme/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ type Category struct {
type CategoryParams struct {
// Title is a *required* short title for the category. This is what will show in the sidebar.
Title string `json:"title"`
// Type is tye type of category, which can be "reference" or "guide".
// Type is the type of category, which can be "reference" or "guide".
Type string `json:"type"`
}

Expand Down Expand Up @@ -186,47 +186,18 @@ func validCategoryType(categoryType string) bool {
//
// API Reference: https://docs.readme.com/reference/getcategories
func (c CategoryClient) GetAll(options ...RequestOptions) ([]Category, *APIResponse, error) {
var err error
var categories []Category
var apiResponse *APIResponse
hasNextPage := false

// Initialize pagination counter.
page := 1
if len(options) > 0 {
if options[0].Page != 0 {
page = options[0].Page
}
var results []Category
opts := parseRequestOptions(options)
apiResponse, err := c.client.fetchAllPages(CategoryEndpoint, opts, &results)
if err != nil {
return results, apiResponse, fmt.Errorf("unable to retrieve categories: %w", err)
}

for {
var paginatedResult []Category

apiRequest := &APIRequest{
Method: "GET",
Endpoint: CategoryEndpoint,
UseAuth: true,
OkStatusCode: []int{200},
Response: &paginatedResult,
}
if len(options) > 0 {
apiRequest.RequestOptions = options[0]
}

apiResponse, hasNextPage, err = c.client.paginatedRequest(apiRequest, page)
if err != nil {
return categories, apiResponse, fmt.Errorf("unable to retrieve categories: %w", err)
}
categories = append(categories, paginatedResult...)

if !hasNextPage {
break
}

page = page + 1
if len(results) == 0 {
return nil, apiResponse, nil
}

return categories, apiResponse, nil
return results, apiResponse, nil
}

// Get a single category on ReadMe.com.
Expand Down
93 changes: 0 additions & 93 deletions readme/category_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,99 +9,6 @@ import (
"github.com/stretchr/testify/assert"
)

func Test_Category_GetAll(t *testing.T) {
// Arrange
gock.New(TestClient.APIURL).
Get(readme.CategoryEndpoint).
Reply(200).
AddHeader("Link", `</categories?page=2>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "3").
JSON(testdata.Categories)
defer gock.Off()

// Act
got, _, err := TestClient.Category.GetAll(readme.RequestOptions{Page: 1})

// Assert
assert.NoError(t, err, "it does not return an error")
assert.Equal(t, testdata.Categories, got, "it returns expected data")
assert.True(t, gock.IsDone(), "it makes the expected API call")
}

func Test_Category_GetAll_Pagination_Invalid(t *testing.T) {
t.Run("when pagination header is invalid", func(t *testing.T) {
// Arrange
var expect []readme.Category
gock.New(TestClient.APIURL).
Get(readme.CategoryEndpoint).
Reply(200).
AddHeader("Link", `</categories?perPage=6&page=10>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "90").
JSON(expect)
defer gock.Off()

// Act
got, _, err := TestClient.Category.GetAll(readme.RequestOptions{PerPage: 6, Page: 15})

// Asert
assert.NoError(t, err, "it does not return an error")
assert.Equal(t, expect, got, "returns nil []Category")
assert.True(t, gock.IsDone(), "it makes the expected API call")
})

t.Run("when page >= (totalCount / perPage)", func(t *testing.T) {
// Arrange
var expect []readme.Category
gock.New(TestClient.APIURL).
Get(readme.CategoryEndpoint).
MatchParam("page", "14").
MatchParam("perPage", "6").
Reply(200).
AddHeader("Link", `</categories?perPage=6&page=15>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "90").
JSON(expect)
gock.New(TestClient.APIURL).
Get(readme.CategoryEndpoint).
MatchParam("page", "15").
MatchParam("perPage", "6").
Reply(200).
AddHeader("Link", `</categories?perPage=6&page=15>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "90").
JSON(expect)
defer gock.Off()

// Act
got, apiResponse, err := TestClient.Category.GetAll(readme.RequestOptions{PerPage: 6, Page: 14})

// Assert
assert.NoError(t, err, "it does not return an error")
assert.Equal(t, "/categories?perPage=6&page=15", apiResponse.Request.Endpoint, "it returns expected endpoint")
assert.Equal(t, expect, got, "it returns expected []Category response")
assert.True(t, gock.IsDone(), "it makes the expected API call")
})

t.Run("when total count header is not a number", func(t *testing.T) {
// Arrange
var expect []readme.Category
gock.New(TestClient.APIURL).
Get(readme.CategoryEndpoint).
Reply(200).
AddHeader("Link", `</categories?page=2>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "x").
JSON(expect)
defer gock.Off()

// Act
got, _, err := TestClient.Category.GetAll(readme.RequestOptions{PerPage: 6, Page: 15})

// Assert
assert.Error(t, err, "it returns an error")
assert.ErrorContains(t, err, "unable to parse 'x-total-count' header", "it returns the expected error")
assert.Equal(t, expect, got, "it returns nil []Category")
assert.True(t, gock.IsDone(), "it makes the expected API call")
})
}

func Test_Category_Get(t *testing.T) {
// Arrange
expect := testdata.Categories[0]
Expand Down
21 changes: 8 additions & 13 deletions readme/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,18 @@ func validChangelogType(changelogType string) bool {
//
// API Reference: https://docs.readme.com/main/reference/getchangelogs
func (c ChangelogClient) GetAll(options ...RequestOptions) ([]Changelog, *APIResponse, error) {
var response []Changelog

apiRequest := &APIRequest{
Method: "GET",
Endpoint: ChangelogEndpoint,
UseAuth: true,
OkStatusCode: []int{200},
Response: &response,
var results []Changelog
opts := parseRequestOptions(options)
apiResponse, err := c.client.fetchAllPages(ChangelogEndpoint, opts, &results)
if err != nil {
return nil, apiResponse, fmt.Errorf("unable to retrieve changelogs: %w", err)
}

if len(options) > 0 {
apiRequest.RequestOptions = options[0]
if len(results) == 0 {
return nil, apiResponse, nil
}

apiResponse, err := c.client.APIRequest(apiRequest)

return response, apiResponse, err
return results, apiResponse, err
}

// Get retrieves a single changelog from ReadMe.
Expand Down
56 changes: 41 additions & 15 deletions readme/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,47 @@ func Test_Changelog_Get(t *testing.T) {
}

func Test_Changelog_GetAll(t *testing.T) {
// Arrange
expect := testdata.Changelogs
gock.New(TestClient.APIURL).
Get(readme.ChangelogEndpoint).
Reply(200).
JSON(expect)
defer gock.Off()

// Act
got, _, err := TestClient.Changelog.GetAll(readme.RequestOptions{Page: 1})

// Assert
assert.NoError(t, err, "it does not return an error")
assert.Equal(t, expect, got, "it returns slice of Changelog structs")
assert.True(t, gock.IsDone(), "it makes the expected API call")
testCases := []struct {
name string
page int
expected []readme.Changelog
}{
{
name: "when called with no page",
page: 1,
expected: testdata.Changelogs,
},
{
name: "when called with page 2",
page: 2,
expected: testdata.Changelogs,
},
{
name: "when there are no changelogs",
page: 1,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Arrange
gock.New(TestClient.APIURL).
Get(readme.ChangelogEndpoint).
Reply(200).
AddHeader("Link", `</changelogs&page=2>; rel="next", <>; rel="prev", <>; rel="last"`).
AddHeader("x-total-count", "1").
JSON(tc.expected)
defer gock.Off()

// Act
got, _, err := TestClient.Changelog.GetAll(readme.RequestOptions{Page: 1})

// Assert
assert.NoError(t, err, "it does not return an error")
assert.Equal(t, tc.expected, got, "it returns slice of Changelog structs")
assert.True(t, gock.IsDone(), "it makes the expected API call")
})
}
}

func Test_Changelog_Create(t *testing.T) {
Expand Down
Loading

0 comments on commit cda2b9b

Please sign in to comment.