diff --git a/cmd/agola/cmd/remotesourcelist.go b/cmd/agola/cmd/remotesourcelist.go index ee83afc36..4cdc4650b 100644 --- a/cmd/agola/cmd/remotesourcelist.go +++ b/cmd/agola/cmd/remotesourcelist.go @@ -36,19 +36,7 @@ var cmdRemoteSourceList = &cobra.Command{ Short: "list", } -type remoteSourceListOptions struct { - limit int - start string -} - -var remoteSourceListOpts remoteSourceListOptions - func init() { - flags := cmdRemoteSourceList.Flags() - - flags.IntVar(&remoteSourceListOpts.limit, "limit", 10, "max number of runs to show") - flags.StringVar(&remoteSourceListOpts.start, "start", "", "starting user name (excluded) to fetch") - cmdRemoteSource.AddCommand(cmdRemoteSourceList) } @@ -61,12 +49,19 @@ func printRemoteSources(remoteSources []*gwapitypes.RemoteSourceResponse) { func remoteSourceList(cmd *cobra.Command, args []string) error { gwClient := gwclient.NewClient(gatewayURL, token) - remouteSources, _, err := gwClient.GetRemoteSources(context.TODO(), remoteSourceListOpts.start, remoteSourceListOpts.limit, false) - if err != nil { - return errors.WithStack(err) - } + var cursor string + for { + remoteSources, resp, err := gwClient.GetRemoteSources(context.TODO(), &gwclient.ListOptions{Cursor: cursor}) + if err != nil { + return errors.Wrapf(err, "failed to get remote sources") + } + printRemoteSources(remoteSources) - printRemoteSources(remouteSources) + cursor = resp.Cursor + if cursor == "" { + break + } + } return nil } diff --git a/internal/services/configstore/action/remotesource.go b/internal/services/configstore/action/remotesource.go index 2c9dd1069..92d42a4cc 100644 --- a/internal/services/configstore/action/remotesource.go +++ b/internal/services/configstore/action/remotesource.go @@ -24,6 +24,49 @@ import ( "agola.io/agola/services/configstore/types" ) +type GetRemoteSourcesRequest struct { + StartRemoteSourceName string + + Limit int + SortDirection types.SortDirection +} + +type GetRemoteSourcesResponse struct { + RemoteSources []*types.RemoteSource + + HasMore bool +} + +func (h *ActionHandler) GetRemoteSources(ctx context.Context, req *GetRemoteSourcesRequest) (*GetRemoteSourcesResponse, error) { + limit := req.Limit + if limit > 0 { + limit += 1 + } + + var remoteSources []*types.RemoteSource + err := h.d.Do(ctx, func(tx *sql.Tx) error { + var err error + remoteSources, err = h.d.GetRemoteSources(tx, req.StartRemoteSourceName, limit, req.SortDirection) + return errors.WithStack(err) + }) + if err != nil { + return nil, errors.WithStack(err) + } + + var hasMore bool + if req.Limit > 0 { + hasMore = len(remoteSources) > req.Limit + if hasMore { + remoteSources = remoteSources[0:req.Limit] + } + } + + return &GetRemoteSourcesResponse{ + RemoteSources: remoteSources, + HasMore: hasMore, + }, nil +} + func (h *ActionHandler) ValidateRemoteSourceReq(ctx context.Context, req *CreateUpdateRemoteSourceRequest) error { if req.Name == "" { return util.NewAPIError(util.ErrBadRequest, errors.Errorf("remotesource name required")) diff --git a/internal/services/configstore/api/remotesource.go b/internal/services/configstore/api/remotesource.go index bd6686740..168b93613 100644 --- a/internal/services/configstore/api/remotesource.go +++ b/internal/services/configstore/api/remotesource.go @@ -17,7 +17,6 @@ package api import ( "encoding/json" "net/http" - "strconv" "github.com/gorilla/mux" "github.com/rs/zerolog" @@ -182,63 +181,46 @@ func (h *DeleteRemoteSourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Req } } -const ( - DefaultRemoteSourcesLimit = 10 - MaxRemoteSourcesLimit = 20 -) - type RemoteSourcesHandler struct { log zerolog.Logger - d *db.DB + ah *action.ActionHandler } -func NewRemoteSourcesHandler(log zerolog.Logger, d *db.DB) *RemoteSourcesHandler { - return &RemoteSourcesHandler{log: log, d: d} +func NewRemoteSourcesHandler(log zerolog.Logger, ah *action.ActionHandler) *RemoteSourcesHandler { + return &RemoteSourcesHandler{log: log, ah: ah} } func (h *RemoteSourcesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - query := r.URL.Query() - - limitS := query.Get("limit") - limit := DefaultRemoteSourcesLimit - if limitS != "" { - var err error - limit, err = strconv.Atoi(limitS) - if err != nil { - util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Wrapf(err, "cannot parse limit"))) - return - } - } - if limit < 0 { - util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("limit must be greater or equal than 0"))) + res, err := h.do(w, r) + if util.HTTPError(w, err) { + h.log.Err(err).Send() return } - if limit > MaxRemoteSourcesLimit { - limit = MaxRemoteSourcesLimit - } - asc := false - if _, ok := query["asc"]; ok { - asc = true + + if err := util.HTTPResponse(w, http.StatusOK, res); err != nil { + h.log.Err(err).Send() } +} - start := query.Get("start") +func (h *RemoteSourcesHandler) do(w http.ResponseWriter, r *http.Request) ([]*types.RemoteSource, error) { + ctx := r.Context() + query := r.URL.Query() - var remoteSources []*types.RemoteSource - err := h.d.Do(ctx, func(tx *sql.Tx) error { - var err error - remoteSources, err = h.d.GetRemoteSources(tx, start, limit, asc) - return errors.WithStack(err) - }) + ropts, err := parseRequestOptions(r) if err != nil { - h.log.Err(err).Send() - util.HTTPError(w, err) - return + return nil, errors.WithStack(err) } - if err := util.HTTPResponse(w, http.StatusOK, remoteSources); err != nil { - h.log.Err(err).Send() + startRemoteSourceName := query.Get("startremotesourcename") + + ares, err := h.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{StartRemoteSourceName: startRemoteSourceName, Limit: ropts.Limit, SortDirection: ropts.SortDirection}) + if err != nil { + return nil, errors.WithStack(err) } + + addHasMoreHeader(w, ares.HasMore) + + return ares.RemoteSources, nil } type LinkedAccountsHandler struct { diff --git a/internal/services/configstore/configstore.go b/internal/services/configstore/configstore.go index 8492eed27..b0783d607 100644 --- a/internal/services/configstore/configstore.go +++ b/internal/services/configstore/configstore.go @@ -195,7 +195,7 @@ func (s *Configstore) setupDefaultRouter() http.Handler { removeOrgMemberHandler := api.NewRemoveOrgMemberHandler(s.log, s.ah) remoteSourceHandler := api.NewRemoteSourceHandler(s.log, s.d) - remoteSourcesHandler := api.NewRemoteSourcesHandler(s.log, s.d) + remoteSourcesHandler := api.NewRemoteSourcesHandler(s.log, s.ah) createRemoteSourceHandler := api.NewCreateRemoteSourceHandler(s.log, s.ah) updateRemoteSourceHandler := api.NewUpdateRemoteSourceHandler(s.log, s.ah) deleteRemoteSourceHandler := api.NewDeleteRemoteSourceHandler(s.log, s.ah) diff --git a/internal/services/configstore/configstore_test.go b/internal/services/configstore/configstore_test.go index e28911e62..61fa0c34c 100644 --- a/internal/services/configstore/configstore_test.go +++ b/internal/services/configstore/configstore_test.go @@ -84,7 +84,7 @@ func getRemoteSources(ctx context.Context, cs *Configstore) ([]*types.RemoteSour var users []*types.RemoteSource err := cs.d.Do(ctx, func(tx *sql.Tx) error { var err error - users, err = cs.d.GetRemoteSources(tx, "", 0, true) + users, err = cs.d.GetRemoteSources(tx, "", 0, types.SortDirectionAsc) return errors.WithStack(err) }) @@ -1071,6 +1071,105 @@ func TestOrgMembers(t *testing.T) { }) } +func TestGetRemoteSources(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + ctx := context.Background() + log := testutil.NewLogger(t) + + cs := setupConfigstore(ctx, t, log, dir) + + t.Logf("starting cs") + go func() { _ = cs.Run(ctx) }() + + remoteSources := []*types.RemoteSource{} + for i := 1; i < 10; i++ { + remoteSource, err := cs.ah.CreateRemoteSource(ctx, &action.CreateUpdateRemoteSourceRequest{Name: fmt.Sprintf("rs%d", i), Type: types.RemoteSourceTypeGitea, AuthType: types.RemoteSourceAuthTypePassword, APIURL: "http://example.com"}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + remoteSources = append(remoteSources, remoteSource) + } + + t.Run("test get all remote sources", func(t *testing.T) { + res, err := cs.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{SortDirection: types.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 9 + if len(res.RemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(res.RemoteSources)) + } + if res.HasMore { + t.Fatalf("expected hasMore false, got %t", res.HasMore) + } + }) + + t.Run("test get remote sources with limit less than remote sources", func(t *testing.T) { + res, err := cs.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{Limit: 5, SortDirection: types.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 5 + if len(res.RemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(res.RemoteSources)) + } + if !res.HasMore { + t.Fatalf("expected hasMore true, got %t", res.HasMore) + } + }) + + t.Run("test get remote sources with limit less than remote sources continuation", func(t *testing.T) { + respAllRemoteSources := []*types.RemoteSource{} + + res, err := cs.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{Limit: 5, SortDirection: types.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + expectedRemoteSources := 5 + if len(res.RemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(res.RemoteSources)) + } + if !res.HasMore { + t.Fatalf("expected hasMore true, got %t", res.HasMore) + } + + respAllRemoteSources = append(respAllRemoteSources, res.RemoteSources...) + lastRemoteSource := res.RemoteSources[len(res.RemoteSources)-1] + + // fetch next results + for { + res, err = cs.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{StartRemoteSourceName: lastRemoteSource.Name, Limit: 5, SortDirection: types.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 5 + if res.HasMore && len(res.RemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(res.RemoteSources)) + } + + respAllRemoteSources = append(respAllRemoteSources, res.RemoteSources...) + + if !res.HasMore { + break + } + + lastRemoteSource = res.RemoteSources[len(res.RemoteSources)-1] + } + + expectedRemoteSources = 9 + if len(remoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(remoteSources)) + } + + if diff := cmpDiffObject(remoteSources, respAllRemoteSources); diff != "" { + t.Fatalf("mismatch (-want +got):\n%s", diff) + } + }) +} + func TestGetUsers(t *testing.T) { t.Parallel() diff --git a/internal/services/configstore/db/db.go b/internal/services/configstore/db/db.go index 6f81f3a7b..c06f52ceb 100644 --- a/internal/services/configstore/db/db.go +++ b/internal/services/configstore/db/db.go @@ -174,17 +174,20 @@ func (d *DB) GetRemoteSourceByName(tx *sql.Tx, name string) (*types.RemoteSource return out, errors.WithStack(err) } -func getRemoteSourcesFilteredQuery(startRemoteSourceName string, limit int, asc bool) *sq.SelectBuilder { +func getRemoteSourcesFilteredQuery(startRemoteSourceName string, limit int, sortDirection types.SortDirection) *sq.SelectBuilder { q := remoteSourceSelect() - if asc { - q = q.OrderBy("remotesource.name").Asc() - } else { - q = q.OrderBy("remotesource.name").Desc() + q = q.OrderBy("remotesource.name") + switch sortDirection { + case types.SortDirectionAsc: + q = q.Asc() + case types.SortDirectionDesc: + q = q.Desc() } if startRemoteSourceName != "" { - if asc { + switch sortDirection { + case types.SortDirectionAsc: q = q.Where(q.G("remotesource.name", startRemoteSourceName)) - } else { + case types.SortDirectionDesc: q = q.Where(q.L("remotesource.name", startRemoteSourceName)) } } @@ -195,8 +198,8 @@ func getRemoteSourcesFilteredQuery(startRemoteSourceName string, limit int, asc return q } -func (d *DB) GetRemoteSources(tx *sql.Tx, startRemoteSourceName string, limit int, asc bool) ([]*types.RemoteSource, error) { - q := getRemoteSourcesFilteredQuery(startRemoteSourceName, limit, asc) +func (d *DB) GetRemoteSources(tx *sql.Tx, startRemoteSourceName string, limit int, sortDirection types.SortDirection) ([]*types.RemoteSource, error) { + q := getRemoteSourcesFilteredQuery(startRemoteSourceName, limit, sortDirection) remoteSources, _, err := d.fetchRemoteSources(tx, q) return remoteSources, errors.WithStack(err) diff --git a/internal/services/gateway/action/remotesource.go b/internal/services/gateway/action/remotesource.go index 55d52b679..aefebe637 100644 --- a/internal/services/gateway/action/remotesource.go +++ b/internal/services/gateway/action/remotesource.go @@ -22,6 +22,7 @@ import ( "agola.io/agola/internal/services/gateway/common" "agola.io/agola/internal/util" csapitypes "agola.io/agola/services/configstore/api/types" + "agola.io/agola/services/configstore/client" cstypes "agola.io/agola/services/configstore/types" ) @@ -34,17 +35,47 @@ func (h *ActionHandler) GetRemoteSource(ctx context.Context, rsRef string) (*cst } type GetRemoteSourcesRequest struct { - Start string - Limit int - Asc bool + Cursor string + + Limit int + SortDirection SortDirection +} + +type GetRemoteSourcesResponse struct { + RemoteSources []*cstypes.RemoteSource + Cursor string } -func (h *ActionHandler) GetRemoteSources(ctx context.Context, req *GetRemoteSourcesRequest) ([]*cstypes.RemoteSource, error) { - remoteSources, _, err := h.configstoreClient.GetRemoteSources(ctx, req.Start, req.Limit, req.Asc) +func (h *ActionHandler) GetRemoteSources(ctx context.Context, req *GetRemoteSourcesRequest) (*GetRemoteSourcesResponse, error) { + inCursor := &StartCursor{} + sortDirection := req.SortDirection + if req.Cursor != "" { + if err := UnmarshalCursor(req.Cursor, inCursor); err != nil { + return nil, errors.WithStack(err) + } + sortDirection = inCursor.SortDirection + } + + remoteSources, resp, err := h.configstoreClient.GetRemoteSources(ctx, &client.GetRemoteSourcesOptions{ListOptions: &client.ListOptions{Limit: req.Limit, SortDirection: cstypes.SortDirection(sortDirection)}, StartRemoteSourceName: inCursor.Start}) if err != nil { return nil, errors.WithStack(err) } - return remoteSources, nil + + var outCursor string + if resp.HasMore && len(remoteSources) > 0 { + lastRemoteSourceName := remoteSources[len(remoteSources)-1].Name + outCursor, err = MarshalCursor(&StartCursor{Start: lastRemoteSourceName}) + if err != nil { + return nil, errors.WithStack(err) + } + } + + res := &GetRemoteSourcesResponse{ + RemoteSources: remoteSources, + Cursor: outCursor, + } + + return res, nil } type CreateRemoteSourceRequest struct { diff --git a/internal/services/gateway/api/remotesource.go b/internal/services/gateway/api/remotesource.go index 8a620ea1d..73f560a5d 100644 --- a/internal/services/gateway/api/remotesource.go +++ b/internal/services/gateway/api/remotesource.go @@ -17,7 +17,6 @@ package api import ( "encoding/json" "net/http" - "strconv" "github.com/gorilla/mux" "github.com/rs/zerolog" @@ -166,52 +165,38 @@ func NewRemoteSourcesHandler(log zerolog.Logger, ah *action.ActionHandler) *Remo } func (h *RemoteSourcesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - query := r.URL.Query() - - limitS := query.Get("limit") - limit := DefaultRunsLimit - if limitS != "" { - var err error - limit, err = strconv.Atoi(limitS) - if err != nil { - util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Wrapf(err, "cannot parse limit"))) - return - } - } - if limit < 0 { - util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, errors.Errorf("limit must be greater or equal than 0"))) + res, err := h.do(w, r) + if util.HTTPError(w, err) { + h.log.Err(err).Send() return } - if limit > MaxRunsLimit { - limit = MaxRunsLimit - } - asc := false - if _, ok := query["asc"]; ok { - asc = true + + if err := util.HTTPResponse(w, http.StatusOK, res); err != nil { + h.log.Err(err).Send() } +} - start := query.Get("start") +func (h *RemoteSourcesHandler) do(w http.ResponseWriter, r *http.Request) ([]*gwapitypes.RemoteSourceResponse, error) { + ctx := r.Context() - areq := &action.GetRemoteSourcesRequest{ - Start: start, - Limit: limit, - Asc: asc, + ropts, err := parseRequestOptions(r) + if err != nil { + return nil, errors.WithStack(err) } - csRemoteSources, err := h.ah.GetRemoteSources(ctx, areq) - if util.HTTPError(w, err) { - h.log.Err(err).Send() - return + + ares, err := h.ah.GetRemoteSources(ctx, &action.GetRemoteSourcesRequest{Cursor: ropts.Cursor, Limit: ropts.Limit, SortDirection: action.SortDirection(ropts.SortDirection)}) + if err != nil { + return nil, errors.WithStack(err) } - remoteSources := make([]*gwapitypes.RemoteSourceResponse, len(csRemoteSources)) - for i, rs := range csRemoteSources { + remoteSources := make([]*gwapitypes.RemoteSourceResponse, len(ares.RemoteSources)) + for i, rs := range ares.RemoteSources { remoteSources[i] = createRemoteSourceResponse(rs) } - if err := util.HTTPResponse(w, http.StatusOK, remoteSources); err != nil { - h.log.Err(err).Send() - } + addCursorHeader(w, ares.Cursor) + + return remoteSources, nil } type DeleteRemoteSourceHandler struct { diff --git a/services/configstore/client/client.go b/services/configstore/client/client.go index c7de739f9..3d338e0a6 100644 --- a/services/configstore/client/client.go +++ b/services/configstore/client/client.go @@ -534,17 +534,23 @@ func (c *Client) GetRemoteSource(ctx context.Context, rsRef string) (*cstypes.Re return rs, resp, errors.WithStack(err) } -func (c *Client) GetRemoteSources(ctx context.Context, start string, limit int, asc bool) ([]*cstypes.RemoteSource, *Response, error) { - q := url.Values{} - if start != "" { - q.Add("start", start) - } - if limit > 0 { - q.Add("limit", strconv.Itoa(limit)) - } - if asc { - q.Add("asc", "") +type GetRemoteSourcesOptions struct { + *ListOptions + + StartRemoteSourceName string +} + +func (o *GetRemoteSourcesOptions) Add(q url.Values) { + o.ListOptions.Add(q) + + if o.StartRemoteSourceName != "" { + q.Add("startremotesourcename", o.StartRemoteSourceName) } +} + +func (c *Client) GetRemoteSources(ctx context.Context, opts *GetRemoteSourcesOptions) ([]*cstypes.RemoteSource, *Response, error) { + q := url.Values{} + opts.Add(q) rss := []*cstypes.RemoteSource{} resp, err := c.GetParsedResponse(ctx, "GET", "/remotesources", q, common.JSONContent, nil, &rss) diff --git a/services/gateway/client/client.go b/services/gateway/client/client.go index 9f9b207f8..7dbb380a6 100644 --- a/services/gateway/client/client.go +++ b/services/gateway/client/client.go @@ -604,17 +604,9 @@ func (c *Client) GetRemoteSource(ctx context.Context, rsRef string) (*gwapitypes return rs, resp, errors.WithStack(err) } -func (c *Client) GetRemoteSources(ctx context.Context, start string, limit int, asc bool) ([]*gwapitypes.RemoteSourceResponse, *Response, error) { +func (c *Client) GetRemoteSources(ctx context.Context, opts *ListOptions) ([]*gwapitypes.RemoteSourceResponse, *Response, error) { q := url.Values{} - if start != "" { - q.Add("start", start) - } - if limit > 0 { - q.Add("limit", strconv.Itoa(limit)) - } - if asc { - q.Add("asc", "") - } + opts.Add(q) rss := []*gwapitypes.RemoteSourceResponse{} resp, err := c.getParsedResponse(ctx, "GET", "/remotesources", q, jsonContent, nil, &rss) diff --git a/tests/setup_test.go b/tests/setup_test.go index e3e0b4c8e..cd3ebd03f 100644 --- a/tests/setup_test.go +++ b/tests/setup_test.go @@ -3697,6 +3697,109 @@ func TestGetUsersPermissions(t *testing.T) { } } +func TestGetRemoteSources(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sc := setup(ctx, t, dir) + defer sc.stop() + + gwClient := gwclient.NewClient(sc.config.Gateway.APIExposedURL, sc.config.Gateway.AdminToken) + + remoteSources := []*gwapitypes.RemoteSourceResponse{} + for i := 1; i < 10; i++ { + remoteSource, _, err := gwClient.CreateRemoteSource(ctx, &gwapitypes.CreateRemoteSourceRequest{ + Name: fmt.Sprintf("rs%d", i), + APIURL: "http://apiurl", + Type: "gitea", + AuthType: "password", + SkipSSHHostKeyCheck: true, + }) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + remoteSources = append(remoteSources, remoteSource) + } + + t.Run("test get remote sources with default limit greater than remote sources", func(t *testing.T) { + remoteSources, resp, err := gwClient.GetRemoteSources(ctx, &gwclient.ListOptions{SortDirection: gwapitypes.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 9 + if len(remoteSources) != expectedRemoteSources { + t.Fatalf("expected %d members, got %d members", expectedRemoteSources, len(remoteSources)) + } + if resp.Cursor != "" { + t.Fatalf("expected no cursor, got cursor %q", resp.Cursor) + } + }) + + t.Run("test get remote sources with limit less than remote sources", func(t *testing.T) { + remoteSources, resp, err := gwClient.GetRemoteSources(ctx, &gwclient.ListOptions{Limit: 5, SortDirection: gwapitypes.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 5 + if len(remoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(remoteSources)) + } + if resp.Cursor == "" { + t.Fatalf("expected cursor, got no cursor") + } + }) + + t.Run("test get remote sources with limit less than remote sources continuation", func(t *testing.T) { + respAllRemoteSources := []*gwapitypes.RemoteSourceResponse{} + + respRemoteSources, resp, err := gwClient.GetRemoteSources(ctx, &gwclient.ListOptions{Limit: 5, SortDirection: gwapitypes.SortDirectionAsc}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + + expectedRemoteSources := 5 + if len(respRemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(respRemoteSources)) + } + if resp.Cursor == "" { + t.Fatalf("expected cursor, got no cursor") + } + + respAllRemoteSources = append(respAllRemoteSources, respRemoteSources...) + + // fetch next results + for { + respRemoteSources, resp, err = gwClient.GetRemoteSources(ctx, &gwclient.ListOptions{Cursor: resp.Cursor, Limit: 5}) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + expectedRemoteSources := 5 + if resp.Cursor != "" && len(respRemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(respRemoteSources)) + } + + respAllRemoteSources = append(respAllRemoteSources, respRemoteSources...) + + if resp.Cursor == "" { + break + } + } + + expectedRemoteSources = 9 + if len(respAllRemoteSources) != expectedRemoteSources { + t.Fatalf("expected %d remote sources, got %d remote sources", expectedRemoteSources, len(respAllRemoteSources)) + } + + if diff := cmp.Diff(remoteSources, respAllRemoteSources); diff != "" { + t.Fatalf("mismatch (-want +got):\n%s", diff) + } + }) +} + func TestGetUsers(t *testing.T) { t.Parallel() @@ -4380,7 +4483,7 @@ func TestExportImport(t *testing.T) { t.Fatalf("unexpected err: %v", err) } - remotesources, _, err := gwClient.GetRemoteSources(ctx, "", 0, false) + remotesources, _, err := gwClient.GetRemoteSources(ctx, nil) if err != nil { t.Fatalf("unexpected err: %v", err) } @@ -4528,7 +4631,7 @@ func TestExportImport(t *testing.T) { t.Fatalf("projectgroup mismatch (-want +got):\n%s", diff) } - impRemotesources, _, err := gwClient.GetRemoteSources(ctx, "", 0, false) + impRemotesources, _, err := gwClient.GetRemoteSources(ctx, nil) if err != nil { t.Fatalf("unexpected err: %v", err) }