Skip to content
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
26 changes: 13 additions & 13 deletions pkg/line/stickers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ type StickerSummary struct {
func (c *Client) GetOwnedStickerProducts(ctx context.Context, country string) ([]ShopProduct, error) {
const pageSize, maxProducts = 1000, 100000
var products []ShopProduct
for offset := 0; offset < maxProducts; {
for len(products) < maxProducts {
resp, err := c.callRPCWithBaseURLContext(ctx, ShopBaseURL, "ShopService", "getOwnedProductSummaries",
"stickershop", offset, pageSize, map[string]string{"language": "en", "country": country})
"stickershop", len(products), pageSize, map[string]string{"language": "en", "country": country})
if err != nil {
return nil, err
}
Expand All @@ -39,7 +39,6 @@ func (c *Client) GetOwnedStickerProducts(ctx context.Context, country string) ([
Message string `json:"message"`
Data *struct {
Products []ShopProduct `json:"productList"`
Offset int `json:"offset"`
Total int `json:"totalSize"`
} `json:"data"`
}
Expand All @@ -48,19 +47,20 @@ func (c *Client) GetOwnedStickerProducts(ctx context.Context, country string) ([
}
if result.Code != 0 {
return nil, fmt.Errorf("getOwnedProductSummaries failed: code %d: %s", result.Code, result.Message)
} else if result.Data == nil {
return nil, fmt.Errorf("getOwnedProductSummaries returned no data")
}
data := result.Data
if data == nil || data.Offset != offset || data.Total < offset || data.Total > maxProducts {
return nil, fmt.Errorf("invalid LINE shop pagination")
}
products = append(products, data.Products...)
offset += len(data.Products)
if offset >= data.Total {
page := result.Data.Products
products = append(products, page...)
if result.Data.Total > 0 {
if len(products) >= result.Data.Total {
return products, nil
} else if len(page) == 0 {
return nil, fmt.Errorf("getOwnedProductSummaries returned empty page at %d of %d products", len(products), result.Data.Total)
}
} else if len(page) < pageSize {
return products, nil
}
if len(data.Products) == 0 {
return nil, fmt.Errorf("empty LINE shop page before end of catalog")
}
}
return nil, fmt.Errorf("LINE shop catalog exceeded pagination limit")
}
76 changes: 76 additions & 0 deletions pkg/line/stickers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package line

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"slices"
"strings"
"testing"
)

func TestGetOwnedStickerProductsPaging(t *testing.T) {
page := func(total int, ids ...string) string {
products := make([]string, len(ids))
for i, id := range ids {
products[i] = fmt.Sprintf(`{"id":%q}`, id)
}
totalField := ""
if total >= 0 {
totalField = fmt.Sprintf(`,"totalSize":%d`, total)
}
return fmt.Sprintf(`{"code":0,"data":{"offset":0%s,"productList":[%s]}}`, totalField, strings.Join(products, ","))
}
fullPage := make([]string, 1000)
for i := range fullPage {
fullPage[i] = fmt.Sprint(i)
}
tests := []struct {
name string
pages []string
wantIDs []string
wantOffsets []int
wantErr bool
}{
{"ignores echoed offset and short pages", []string{page(3, "1", "2"), page(3, "3")}, []string{"1", "2", "3"}, []int{0, 2}, false},
{"errors on empty page before total", []string{page(5, "1", "2"), page(5)}, nil, []int{0, 2}, true},
{"errors on null data", []string{`{"code":0,"data":null}`}, nil, []int{0}, true},
{"missing total", []string{page(-1, "1")}, []string{"1"}, []int{0}, false},
{"zero total stops on empty page", []string{page(0)}, nil, []int{0}, false},
{"missing total pages until short page", []string{page(-1, fullPage...), page(-1, "1000")}, append(slices.Clone(fullPage), "1000"), []int{0, 1000}, false},
{"zero total pages until empty page", []string{page(0, fullPage...), page(0)}, fullPage, []int{0, 1000}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var offsets []int
client := NewClient("token")
client.HTTPClient = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
var args []json.RawMessage
body, _ := io.ReadAll(req.Body)
if err := json.Unmarshal(body, &args); err != nil || len(args) != 4 || string(args[0]) != `"stickershop"` {
t.Fatalf("unexpected request body %s", body)
}
var offset int
_ = json.Unmarshal(args[1], &offset)
offsets = append(offsets, offset)
if len(offsets) > len(tt.pages) {
t.Fatalf("too many requests")
}
return obsResponse(http.StatusOK, tt.pages[len(offsets)-1]), nil
})}
products, err := client.GetOwnedStickerProducts(context.Background(), "JP")
if (err != nil) != tt.wantErr {
t.Fatalf("err = %v, wantErr %v", err, tt.wantErr)
}
var ids []string
for _, product := range products {
ids = append(ids, product.ID)
}
if !slices.Equal(ids, tt.wantIDs) || !slices.Equal(offsets, tt.wantOffsets) {
t.Fatalf("ids = %v offsets = %v, want %v %v", ids, offsets, tt.wantIDs, tt.wantOffsets)
}
})
}
}
Loading