Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ListAll methods to resources with ListWithPagination methods #282

Merged
merged 3 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
// See: https://help.shopify.com/api/reference/customer
type CustomerService interface {
List(context.Context, interface{}) ([]Customer, error)
ListAll(context.Context, interface{}) ([]Customer, error)
ListWithPagination(ctx context.Context, options interface{}) ([]Customer, *Pagination, error)
Count(context.Context, interface{}) (int, error)
Get(context.Context, uint64, interface{}) (*Customer, error)
Expand Down Expand Up @@ -111,6 +112,29 @@ func (s *CustomerServiceOp) List(ctx context.Context, options interface{}) ([]Cu
return resource.Customers, err
}

// ListAll Lists all customers, iterating over pages
func (s *CustomerServiceOp) ListAll(ctx context.Context, options interface{}) ([]Customer, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry you had to write this 15 times.

One day I'll get around to redo a large part of this library using generics but today is not that day
(plus it'll break backwards compatibility with slightly older Golang versions)

collector := []Customer{}

for {
entities, pagination, err := s.ListWithPagination(ctx, options)

if err != nil {
return collector, err
}

collector = append(collector, entities...)

if pagination.NextPageOptions == nil {
break
}

options = pagination.NextPageOptions
}

return collector, nil
}

// ListWithPagination lists customers and return pagination to retrieve next/previous results.
func (s *CustomerServiceOp) ListWithPagination(ctx context.Context, options interface{}) ([]Customer, *Pagination, error) {
path := fmt.Sprintf("%s.json", customersBasePath)
Expand Down
112 changes: 112 additions & 0 deletions customer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,118 @@ func TestCustomerList(t *testing.T) {
}
}

func TestCustomerListAll(t *testing.T) {
setup()
defer teardown()

listURL := fmt.Sprintf("https://fooshop.myshopify.com/%s/customers.json", client.pathPrefix)

cases := []struct {
name string
expectedCustomers []Customer
expectedRequestURLs []string
expectedLinkHeaders []string
expectedBodies []string
expectedErr error
}{
{
name: "Pulls the next page",
expectedRequestURLs: []string{
listURL,
fmt.Sprintf("%s?page_info=pg2", listURL),
},
expectedLinkHeaders: []string{
`<http://valid.url?page_info=pg2>; rel="next"`,
`<http://valid.url?page_info=pg1>; rel="previous"`,
},
expectedBodies: []string{
`{"customers": [{"id":1},{"id":2}]}`,
`{"customers": [{"id":3},{"id":4}]}`,
},
expectedCustomers: []Customer{{Id: 1}, {Id: 2}, {Id: 3}, {Id: 4}},
expectedErr: nil,
},
{
name: "Stops when there is not a next page",
expectedRequestURLs: []string{
listURL,
},
expectedLinkHeaders: []string{
`<http://valid.url?page_info=pg2>; rel="previous"`,
},
expectedBodies: []string{
`{"customers": [{"id":1}]}`,
},
expectedCustomers: []Customer{{Id: 1}},
expectedErr: nil,
},
{
name: "Returns errors when required",
expectedRequestURLs: []string{
listURL,
},
expectedLinkHeaders: []string{
`<http://valid.url?paage_info=pg2>; rel="previous"`,
},
expectedBodies: []string{
`{"customers": []}`,
},
expectedCustomers: []Customer{},
expectedErr: errors.New("page_info is missing"),
},
}

for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
if len(c.expectedRequestURLs) != len(c.expectedLinkHeaders) {
t.Errorf(
"test case must have the same number of expected request urls (%d) as expected link headers (%d)",
len(c.expectedRequestURLs),
len(c.expectedLinkHeaders),
)

return
}

if len(c.expectedRequestURLs) != len(c.expectedBodies) {
t.Errorf(
"test case must have the same number of expected request urls (%d) as expected bodies (%d)",
len(c.expectedRequestURLs),
len(c.expectedBodies),
)

return
}

for i := range c.expectedRequestURLs {
response := &http.Response{
StatusCode: 200,
Body: httpmock.NewRespBodyFromString(c.expectedBodies[i]),
Header: http.Header{
"Link": {c.expectedLinkHeaders[i]},
},
}

httpmock.RegisterResponder("GET", c.expectedRequestURLs[i], httpmock.ResponderFromResponse(response))
}

customers, err := client.Customer.ListAll(context.Background(), nil)
if !reflect.DeepEqual(customers, c.expectedCustomers) {
t.Errorf("test %d Customer.ListAll orders returned %+v, expected %+v", i, customers, c.expectedCustomers)
}

if (c.expectedErr != nil || err != nil) && err.Error() != c.expectedErr.Error() {
t.Errorf(
"test %d Customer.ListAll err returned %+v, expected %+v",
i,
err,
c.expectedErr,
)
}
})
}
}

func TestCustomerListWithPagination(t *testing.T) {
setup()
defer teardown()
Expand Down
24 changes: 24 additions & 0 deletions order.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
// See: https://help.shopify.com/api/reference/order
type OrderService interface {
List(context.Context, interface{}) ([]Order, error)
ListAll(context.Context, interface{}) ([]Order, error)
ListWithPagination(context.Context, interface{}) ([]Order, *Pagination, error)
Count(context.Context, interface{}) (int, error)
Get(context.Context, uint64, interface{}) (*Order, error)
Expand Down Expand Up @@ -538,6 +539,29 @@ func (s *OrderServiceOp) List(ctx context.Context, options interface{}) ([]Order
return orders, nil
}

// ListAll Lists all orders, iterating over pages
func (s *OrderServiceOp) ListAll(ctx context.Context, options interface{}) ([]Order, error) {
collector := []Order{}

for {
entities, pagination, err := s.ListWithPagination(ctx, options)

if err != nil {
return collector, err
}

collector = append(collector, entities...)

if pagination.NextPageOptions == nil {
break
}

options = pagination.NextPageOptions
}

return collector, nil
}

func (s *OrderServiceOp) ListWithPagination(ctx context.Context, options interface{}) ([]Order, *Pagination, error) {
path := fmt.Sprintf("%s.json", ordersBasePath)
resource := new(OrdersResource)
Expand Down
24 changes: 24 additions & 0 deletions order_risk.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
// See: https://shopify.dev/docs/api/admin-rest/2023-10/resources/order-risk
type OrderRiskService interface {
List(context.Context, uint64, interface{}) ([]OrderRisk, error)
ListAll(context.Context, uint64, interface{}) ([]OrderRisk, error)
ListWithPagination(context.Context, uint64, interface{}) ([]OrderRisk, *Pagination, error)
Get(context.Context, uint64, uint64, interface{}) (*OrderRisk, error)
Create(context.Context, uint64, OrderRisk) (*OrderRisk, error)
Expand Down Expand Up @@ -79,6 +80,29 @@ func (s *OrderRiskServiceOp) List(ctx context.Context, orderId uint64, options i
return orders, nil
}

// ListAll Lists all OrderRisk, iterating over pages
func (s *OrderRiskServiceOp) ListAll(ctx context.Context, orderId uint64, options interface{}) ([]OrderRisk, error) {
collector := []OrderRisk{}

for {
entities, pagination, err := s.ListWithPagination(ctx, orderId, options)

if err != nil {
return collector, err
}

collector = append(collector, entities...)

if pagination.NextPageOptions == nil {
break
}

options = pagination.NextPageOptions
}

return collector, nil
}

func (s *OrderRiskServiceOp) ListWithPagination(ctx context.Context, orderId uint64, options interface{}) ([]OrderRisk, *Pagination, error) {
path := fmt.Sprintf("%s/%d/%s.json", ordersRiskBasePath, orderId, ordersRiskResourceName)
resource := new(OrdersRisksResource)
Expand Down
112 changes: 112 additions & 0 deletions order_risk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,118 @@ func TestOrderRiskListError(t *testing.T) {
}
}

func TestOrderRiskListAll(t *testing.T) {
setup()
defer teardown()

listURL := fmt.Sprintf("https://fooshop.myshopify.com/%s/orders/450789469/risks.json", client.pathPrefix)

cases := []struct {
name string
expectedOrderRisks []OrderRisk
expectedRequestURLs []string
expectedLinkHeaders []string
expectedBodies []string
expectedErr error
}{
{
name: "Pulls the next page",
expectedRequestURLs: []string{
listURL,
fmt.Sprintf("%s?page_info=pg2", listURL),
},
expectedLinkHeaders: []string{
`<http://valid.url?page_info=pg2>; rel="next"`,
`<http://valid.url?page_info=pg1>; rel="previous"`,
},
expectedBodies: []string{
`{"risks": [{"id":1},{"id":2}]}`,
`{"risks": [{"id":3},{"id":4}]}`,
},
expectedOrderRisks: []OrderRisk{{Id: 1}, {Id: 2}, {Id: 3}, {Id: 4}},
expectedErr: nil,
},
{
name: "Stops when there is not a next page",
expectedRequestURLs: []string{
listURL,
},
expectedLinkHeaders: []string{
`<http://valid.url?page_info=pg2>; rel="previous"`,
},
expectedBodies: []string{
`{"risks": [{"id":1}]}`,
},
expectedOrderRisks: []OrderRisk{{Id: 1}},
expectedErr: nil,
},
{
name: "Returns errors when required",
expectedRequestURLs: []string{
listURL,
},
expectedLinkHeaders: []string{
`<http://valid.url?paage_info=pg2>; rel="previous"`,
},
expectedBodies: []string{
`{"risks": []}`,
},
expectedOrderRisks: []OrderRisk{},
expectedErr: errors.New("page_info is missing"),
},
}

for i, c := range cases {
t.Run(c.name, func(t *testing.T) {
if len(c.expectedRequestURLs) != len(c.expectedLinkHeaders) {
t.Errorf(
"test case must have the same number of expected request urls (%d) as expected link headers (%d)",
len(c.expectedRequestURLs),
len(c.expectedLinkHeaders),
)

return
}

if len(c.expectedRequestURLs) != len(c.expectedBodies) {
t.Errorf(
"test case must have the same number of expected request urls (%d) as expected bodies (%d)",
len(c.expectedRequestURLs),
len(c.expectedBodies),
)

return
}

for i := range c.expectedRequestURLs {
response := &http.Response{
StatusCode: 200,
Body: httpmock.NewRespBodyFromString(c.expectedBodies[i]),
Header: http.Header{
"Link": {c.expectedLinkHeaders[i]},
},
}

httpmock.RegisterResponder("GET", c.expectedRequestURLs[i], httpmock.ResponderFromResponse(response))
}

risks, err := client.OrderRisk.ListAll(context.Background(), 450789469, nil)
if !reflect.DeepEqual(risks, c.expectedOrderRisks) {
t.Errorf("test %d OrderRisk.ListAll orders returned %+v, expected %+v", i, risks, c.expectedOrderRisks)
}

if (c.expectedErr != nil || err != nil) && err.Error() != c.expectedErr.Error() {
t.Errorf(
"test %d OrderRisk.ListAll err returned %+v, expected %+v",
i,
err,
c.expectedErr,
)
}
})
}
}

func TestOrderRiskListWithPagination(t *testing.T) {
setup()
defer teardown()
Expand Down
Loading
Loading