Skip to content

Commit c4a15bf

Browse files
fix(bitbucket): paginate remote-scopes API to return all repositories (apache#8999) (apache#9004)
1 parent b70ac93 commit c4a15bf

3 files changed

Lines changed: 98 additions & 1 deletion

File tree

backend/plugins/bitbucket/api/remote_api.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ func listBitbucketWorkspaces(
107107
FullName: r.GroupName(),
108108
})
109109
}
110+
if resBody.Next != "" {
111+
nextPage = &BitbucketRemotePagination{
112+
Page: page.Page + 1,
113+
PageLen: page.PageLen,
114+
}
115+
}
110116
return
111117
}
112118

@@ -123,7 +129,7 @@ func listBitbucketRepos(
123129
var res *http.Response
124130
// list projects part
125131
res, err = apiClient.Get(fmt.Sprintf("/repositories/%s", workspace), url.Values{
126-
"fields": {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size"},
132+
"fields": {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size,next"},
127133
"page": {fmt.Sprintf("%v", page.Page)},
128134
"pagelen": {fmt.Sprintf("%v", page.PageLen)},
129135
}, nil)
@@ -153,6 +159,12 @@ func listBitbucketRepos(
153159
Data: r.ConvertApiScope(),
154160
})
155161
}
162+
if resBody.Next != "" {
163+
nextPage = &BitbucketRemotePagination{
164+
Page: page.Page + 1,
165+
PageLen: page.PageLen,
166+
}
167+
}
156168
return
157169
}
158170

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"io"
22+
"net/http"
23+
"net/url"
24+
"strings"
25+
"testing"
26+
27+
"github.com/stretchr/testify/assert"
28+
29+
"github.com/apache/incubator-devlake/core/errors"
30+
"github.com/apache/incubator-devlake/core/plugin"
31+
)
32+
33+
type fakeApiClient struct{ body string }
34+
35+
func (f *fakeApiClient) SetData(name string, data interface{}) {}
36+
func (f *fakeApiClient) GetData(name string) interface{} { return nil }
37+
func (f *fakeApiClient) SetHeaders(headers map[string]string) {}
38+
func (f *fakeApiClient) SetBeforeFunction(callback plugin.ApiClientBeforeRequest) {}
39+
func (f *fakeApiClient) GetBeforeFunction() plugin.ApiClientBeforeRequest { return nil }
40+
func (f *fakeApiClient) SetAfterFunction(callback plugin.ApiClientAfterResponse) {}
41+
func (f *fakeApiClient) GetAfterFunction() plugin.ApiClientAfterResponse { return nil }
42+
43+
func (f *fakeApiClient) Get(path string, query url.Values, headers http.Header) (*http.Response, errors.Error) {
44+
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(f.body))}, nil
45+
}
46+
func (f *fakeApiClient) Post(path string, query url.Values, body interface{}, headers http.Header) (*http.Response, errors.Error) {
47+
return nil, nil
48+
}
49+
50+
func TestListBitbucketRepos_ReturnsNextPage(t *testing.T) {
51+
client := &fakeApiClient{body: `{"pagelen":2,"page":1,"size":4,
52+
"next":"https://api.bitbucket.org/2.0/repositories/myworkspace?page=2",
53+
"values":[{"name":"repo-a","full_name":"myworkspace/repo-a"},
54+
{"name":"repo-b","full_name":"myworkspace/repo-b"}]}`}
55+
children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 1, PageLen: 2})
56+
assert.Nil(t, err)
57+
assert.Len(t, children, 2)
58+
if assert.NotNil(t, nextPage) {
59+
assert.Equal(t, 2, nextPage.Page)
60+
}
61+
}
62+
63+
func TestListBitbucketRepos_LastPageHasNoNextPage(t *testing.T) {
64+
client := &fakeApiClient{body: `{"pagelen":2,"page":2,"size":4,
65+
"values":[{"name":"repo-c","full_name":"myworkspace/repo-c"},
66+
{"name":"repo-d","full_name":"myworkspace/repo-d"}]}`}
67+
children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 2, PageLen: 2})
68+
assert.Nil(t, err)
69+
assert.Len(t, children, 2)
70+
assert.Nil(t, nextPage)
71+
}
72+
73+
func TestListBitbucketWorkspaces_ReturnsNextPage(t *testing.T) {
74+
client := &fakeApiClient{body: `{"pagelen":1,"page":1,"size":2,
75+
"next":"https://api.bitbucket.org/2.0/user/workspaces?page=2",
76+
"values":[{"workspace":{"slug":"ws-a","name":"Workspace A"}}]}`}
77+
children, nextPage, err := listBitbucketWorkspaces(client, BitbucketRemotePagination{Page: 1, PageLen: 1})
78+
assert.Nil(t, err)
79+
assert.Len(t, children, 1)
80+
if assert.NotNil(t, nextPage) {
81+
assert.Equal(t, 2, nextPage.Page)
82+
}
83+
}

backend/plugins/bitbucket/models/repo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ type WorkspaceResponse struct {
114114
Pagelen int `json:"pagelen"`
115115
Page int `json:"page"`
116116
Size int `json:"size"`
117+
Next string `json:"next"`
117118
Values []GroupResponse `json:"values"`
118119
}
119120

@@ -142,6 +143,7 @@ type ReposResponse struct {
142143
Pagelen int `json:"pagelen"`
143144
Page int `json:"page"`
144145
Size int `json:"size"`
146+
Next string `json:"next"`
145147
Values []BitbucketApiRepo `json:"values"`
146148
}
147149

0 commit comments

Comments
 (0)