Merge pull request #4439 from geramirez/geramirez/codespaces-pagination

Adding pagination to list codespaces
This commit is contained in:
Gabriel Ramírez 2021-10-06 12:38:50 -05:00 committed by GitHub
commit 3d0d95ce2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 20 deletions

View file

@ -34,6 +34,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
@ -189,16 +190,43 @@ type CodespaceEnvironmentConnection struct {
HostPublicKeys []string `json:"hostPublicKeys"`
}
// codespacesListResponse is the response body for the `/user/codespaces` endpoint
type getCodespacesListResponse struct {
Codespaces []*Codespace `json:"codespaces"`
TotalCount int `json:"total_count"`
}
// ListCodespaces returns a list of codespaces for the user.
func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
// It consumes all pages returned by the API until all codespaces have been fetched.
func (a *API) ListCodespaces(ctx context.Context) (codespaces []*Codespace, err error) {
per_page := 100
for page := 1; ; page++ {
response, err := a.fetchCodespaces(ctx, page, per_page)
if err != nil {
return nil, err
}
codespaces = append(codespaces, response.Codespaces...)
if page*per_page >= response.TotalCount {
break
}
}
return codespaces, nil
}
func (a *API) fetchCodespaces(ctx context.Context, page int, per_page int) (response *getCodespacesListResponse, err error) {
req, err := http.NewRequest(
http.MethodGet, a.githubAPI+"/user/codespaces", nil,
)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}
a.setHeaders(req)
q := req.URL.Query()
q.Add("page", strconv.Itoa(page))
q.Add("per_page", strconv.Itoa(per_page))
req.URL.RawQuery = q.Encode()
resp, err := a.do(ctx, req, "/user/codespaces")
if err != nil {
return nil, fmt.Errorf("error making request: %w", err)
@ -214,13 +242,10 @@ func (a *API) ListCodespaces(ctx context.Context) ([]*Codespace, error) {
return nil, jsonErrorResponse(b)
}
var response struct {
Codespaces []*Codespace `json:"codespaces"`
}
if err := json.Unmarshal(b, &response); err != nil {
return nil, fmt.Errorf("error unmarshaling response: %w", err)
}
return response.Codespaces, nil
return response, nil
}
// GetCodespace returns the user codespace based on the provided name.

View file

@ -6,26 +6,61 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
)
func TestListCodespaces(t *testing.T) {
codespaces := []*Codespace{
{
Name: "testcodespace",
CreatedAt: "2021-08-09T10:10:24+02:00",
LastUsedAt: "2021-08-09T13:10:24+02:00",
},
func generateCodespaceList(start int, end int) []*Codespace {
codespacesList := []*Codespace{}
for i := start; i < end; i++ {
codespacesList = append(codespacesList, &Codespace{
Name: fmt.Sprintf("codespace-%d", i),
})
}
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return codespacesList
}
func createFakeListEndpointServer(t *testing.T, initalTotal int, finalTotal int) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/user/codespaces" {
t.Fatal("Incorrect path")
}
page := 1
if r.URL.Query().Get("page") != "" {
page, _ = strconv.Atoi(r.URL.Query().Get("page"))
}
per_page := 0
if r.URL.Query().Get("per_page") != "" {
per_page, _ = strconv.Atoi(r.URL.Query().Get("per_page"))
}
response := struct {
Codespaces []*Codespace `json:"codespaces"`
TotalCount int `json:"total_count"`
}{
Codespaces: codespaces,
Codespaces: []*Codespace{},
TotalCount: finalTotal,
}
if page == 1 {
response.Codespaces = generateCodespaceList(0, per_page)
response.TotalCount = initalTotal
} else if page == 2 {
response.Codespaces = generateCodespaceList(per_page, per_page*2)
response.TotalCount = finalTotal
} else {
t.Fatal("Should not check extra page")
}
data, _ := json.Marshal(response)
fmt.Fprint(w, string(data))
}))
}
func TestListCodespaces(t *testing.T) {
svr := createFakeListEndpointServer(t, 200, 200)
defer svr.Close()
api := API{
@ -38,13 +73,53 @@ func TestListCodespaces(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(codespaces) != 1 {
t.Fatalf("expected 1 codespace, got %d", len(codespaces))
if len(codespaces) != 200 {
t.Fatalf("expected 100 codespace, got %d", len(codespaces))
}
if codespaces[0].Name != "testcodespace" {
t.Fatalf("expected testcodespace, got %s", codespaces[0].Name)
if codespaces[0].Name != "codespace-0" {
t.Fatalf("expected codespace-0, got %s", codespaces[0].Name)
}
if codespaces[199].Name != "codespace-199" {
t.Fatalf("expected codespace-199, got %s", codespaces[0].Name)
}
}
func TestMidIterationDeletion(t *testing.T) {
svr := createFakeListEndpointServer(t, 200, 199)
defer svr.Close()
api := API{
githubAPI: svr.URL,
client: &http.Client{},
token: "faketoken",
}
ctx := context.TODO()
codespaces, err := api.ListCodespaces(ctx)
if err != nil {
t.Fatal(err)
}
if len(codespaces) != 200 {
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
}
}
func TestMidIterationAddition(t *testing.T) {
svr := createFakeListEndpointServer(t, 199, 200)
defer svr.Close()
api := API{
githubAPI: svr.URL,
client: &http.Client{},
token: "faketoken",
}
ctx := context.TODO()
codespaces, err := api.ListCodespaces(ctx)
if err != nil {
t.Fatal(err)
}
if len(codespaces) != 200 {
t.Fatalf("expected 200 codespace, got %d", len(codespaces))
}
}