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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/Life-USTC/CLI
go 1.26.0

require (
github.com/Life-USTC/client-core v0.3.0
github.com/Life-USTC/client-core v0.4.0
github.com/PuerkitoBio/goquery v1.13.0
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/Life-USTC/client-core v0.3.0 h1:hpTnY1x6CIVQDIH0PoZdg7/9K+BEpaSss3BlaVdCHWs=
github.com/Life-USTC/client-core v0.3.0/go.mod h1:2SgYKZ3hbEFEigNGPdbfFS2m149evVw2t/mEjA/S/LM=
github.com/Life-USTC/client-core v0.4.0 h1:3Bif0F0mMt4EtievcIpGEeLHFnBnY4C2XP2byGwPySg=
github.com/Life-USTC/client-core v0.4.0/go.mod h1:2SgYKZ3hbEFEigNGPdbfFS2m149evVw2t/mEjA/S/LM=
github.com/PuerkitoBio/goquery v1.13.0 h1:mqHbjD7Jmnul4DTR24LKTjo1uUmHUh072kteGV+xpFM=
github.com/PuerkitoBio/goquery v1.13.0/go.mod h1:Hip5mdBL8K2wEGKJdr27sRaNwIdDajmCwB/ExUPwW+g=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
Expand Down
15 changes: 10 additions & 5 deletions internal/cmd/youngutil/youngutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/Life-USTC/CLI/internal/cmd/cmdutil"
"github.com/Life-USTC/client-core/calendar"
corepagination "github.com/Life-USTC/client-core/pagination"
)

const (
Expand Down Expand Up @@ -101,8 +102,9 @@ func FetchAllPages(
return nil, fmt.Errorf("page size must be positive")
}

progress := corepagination.NewProgress(1)
firstParams := cloneValues(params)
firstParams.Set("page", "1")
firstParams.Set("page", strconv.Itoa(progress.NextPage()))
firstParams.Set("pageSize", strconv.Itoa(pageSize))
first, err := fetch(ctx, firstParams)
if err != nil {
Expand Down Expand Up @@ -131,11 +133,13 @@ func FetchAllPages(
if len(rows) != 0 || totalPages > 1 {
return nil, fmt.Errorf("%s returned inconsistent empty pagination", path)
}
progress = progress.AfterPage(len(rows), false)
} else {
if totalPages < 1 {
return nil, fmt.Errorf("%s returned no pages for %d results", path, total)
}
for page := 2; page <= totalPages; page++ {
progress = progress.AfterPage(len(rows), totalPages > 1)
for page := progress.NextPage(); page != 0; page = progress.NextPage() {
pageParams := cloneValues(params)
pageParams.Set("page", strconv.Itoa(page))
pageParams.Set("pageSize", strconv.Itoa(pageSize))
Expand All @@ -160,18 +164,19 @@ func FetchAllPages(
}
}
rows = append(rows, pageResult.Rows...)
progress = progress.AfterPage(len(pageResult.Rows), page < totalPages)
}
}
if len(rows) != total {
return nil, fmt.Errorf("%s returned %d of %d results", path, len(rows), total)
if progress.ReceivedCount() != total {
return nil, fmt.Errorf("%s returned %d of %d results", path, progress.ReceivedCount(), total)
}

result := cmdutil.NewListResult(first, key)
result.Raw = cmdutil.WithListRows(first, key, rows, total, 1)
if m := cmdutil.AsMap(result.Raw); m != nil {
if pg := cmdutil.AsMap(m["pagination"]); pg != nil {
pg["page"] = 1
resultPageSize := len(rows)
resultPageSize := progress.ReceivedCount()
if resultPageSize == 0 {
resultPageSize = pageSize
}
Expand Down
90 changes: 90 additions & 0 deletions internal/cmd/youngutil/youngutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -112,3 +114,91 @@ func TestFetchAllPagesRejectsIncompleteResponse(t *testing.T) {
t.Fatal("FetchAllPages accepted an incomplete response")
}
}

func TestFetchAllPagesPreservesPaginationPolicies(t *testing.T) {
page := func(total, pages int, ids ...string) any {
rows := make([]any, len(ids))
for i, id := range ids {
rows[i] = map[string]any{"youngId": id}
}
return map[string]any{"data": rows, "pagination": map[string]any{"total": total, "totalPages": pages}}
}
tests := []struct {
name string
responses []any
wantPages []string
wantCount int
wantError string
}{
{"empty zero pages", []any{page(0, 0)}, []string{"1"}, 0, ""},
{"empty one page", []any{page(0, 1)}, []string{"1"}, 0, ""},
{"empty middle page", []any{page(3, 3, "a"), page(3, 3), page(3, 3, "b", "c")}, []string{"1", "2", "3"}, 3, ""},
{"empty total with rows", []any{page(0, 1, "a")}, []string{"1"}, 0, "inconsistent empty pagination"},
{"empty total with more pages", []any{page(0, 2)}, []string{"1"}, 0, "inconsistent empty pagination"},
{"nonempty total without pages", []any{page(1, 0)}, []string{"1"}, 0, "returned no pages"},
{"page count limit", []any{page(1, 10001)}, []string{"1"}, 0, "invalid page count"},
{"changed total", []any{page(3, 2, "a"), page(4, 2, "b")}, []string{"1", "2"}, 0, "changed pagination"},
{"changed page count", []any{page(3, 2, "a"), page(3, 3, "b")}, []string{"1", "2"}, 0, "changed pagination"},
{"duplicate first page", []any{page(3, 2, "a", "a")}, []string{"1"}, 0, "duplicate youngId"},
{"duplicate later page", []any{page(3, 2, "a"), page(3, 2, "a", "b")}, []string{"1", "2"}, 0, "duplicate youngId"},
{"empty ID", []any{page(1, 1, "")}, []string{"1"}, 0, "row without youngId"},
{"oversized later page", []any{page(4, 2, "a"), page(4, 2, "b", "c", "d")}, []string{"1", "2"}, 0, "oversized page"},
{"incomplete total", []any{page(3, 2, "a"), page(3, 2, "b")}, []string{"1", "2"}, 0, "returned 2 of 3 results"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var requested []string
params := url.Values{"module": {"智"}, "active": {"false"}}
data, err := FetchAllPages(t.Context(), func(_ context.Context, query url.Values) (any, error) {
requested = append(requested, query.Get("page"))
if query.Get("pageSize") != "2" || query.Get("module") != "智" || query.Get("active") != "false" {
t.Fatalf("query = %v", query)
}
if len(requested) > len(tt.responses) {
t.Fatalf("unexpected extra page: %v", requested)
}
return tt.responses[len(requested)-1], nil
}, "/events", params, "data", 2, "youngId")
if !reflect.DeepEqual(requested, tt.wantPages) {
t.Fatalf("requested = %v, want %v", requested, tt.wantPages)
}
if params.Has("page") || params.Has("pageSize") {
t.Fatalf("caller parameters changed: %v", params)
}
if tt.wantError != "" {
if err == nil || !strings.Contains(err.Error(), tt.wantError) || data != nil {
t.Fatalf("data = %#v, err = %v; want no partial data and %q", data, err, tt.wantError)
}
return
}
if err != nil {
t.Fatal(err)
}
result := data.(map[string]any)
if len(result["data"].([]any)) != tt.wantCount {
t.Fatalf("data = %#v", result)
}
pagination := result["pagination"].(map[string]any)
if pagination["page"] != 1 || pagination["totalPages"] != 1 || pagination["total"] != tt.wantCount {
t.Fatalf("pagination = %#v", pagination)
}
})
}
}

func TestFetchAllIfUnpagedKeepsExplicitSinglePage(t *testing.T) {
for _, params := range []url.Values{{"page": {"2"}}, {"pageSize": {"1"}}} {
calls := 0
payload := map[string]any{"data": []any{}, "pagination": map[string]any{"page": 2, "total": 9, "totalPages": 9}}
data, err := FetchAllIfUnpaged(t.Context(), func(_ context.Context, query url.Values) (any, error) {
calls++
if !reflect.DeepEqual(query, params) {
t.Fatalf("query = %v, want %v", query, params)
}
return payload, nil
}, "/events", params, "data", 2, "youngId")
if err != nil || calls != 1 || !reflect.DeepEqual(data, payload) {
t.Fatalf("calls = %d, data = %#v, err = %v", calls, data, err)
}
}
}
Loading