Add tests for tags list pagination
Signed-off-by: João Pereira <484633+joaodrp@users.noreply.github.com>
This commit is contained in:
parent
d80a63f1ea
commit
8ef268df25
3 changed files with 178 additions and 2 deletions
|
@ -128,7 +128,7 @@ func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildTagsURL constructs a url to list the tags in the named repository.
|
// BuildTagsURL constructs a url to list the tags in the named repository.
|
||||||
func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
|
func (ub *URLBuilder) BuildTagsURL(name reference.Named, values ...url.Values) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameTags)
|
route := ub.cloneRoute(RouteNameTags)
|
||||||
|
|
||||||
tagsURL, err := route.URL("name", name.Name())
|
tagsURL, err := route.URL("name", name.Name())
|
||||||
|
@ -136,7 +136,7 @@ func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagsURL.String(), nil
|
return appendValuesURL(tagsURL, values...).String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildManifestURL constructs a url for the manifest identified by name and
|
// BuildManifestURL constructs a url for the manifest identified by name and
|
||||||
|
|
|
@ -34,6 +34,26 @@ func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase {
|
||||||
return urlBuilder.BuildTagsURL(fooBarRef)
|
return urlBuilder.BuildTagsURL(fooBarRef)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "test tags url with n query parameter",
|
||||||
|
expectedPath: "/v2/foo/bar/tags/list?n=10",
|
||||||
|
expectedErr: nil,
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildTagsURL(fooBarRef, url.Values{
|
||||||
|
"n": []string{"10"},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test tags url with last query parameter",
|
||||||
|
expectedPath: "/v2/foo/bar/tags/list?last=abc-def",
|
||||||
|
expectedErr: nil,
|
||||||
|
build: func() (string, error) {
|
||||||
|
return urlBuilder.BuildTagsURL(fooBarRef, url.Values{
|
||||||
|
"last": []string{"abc-def"},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "test manifest url tagged ref",
|
description: "test manifest url tagged ref",
|
||||||
expectedPath: "/v2/foo/bar/manifests/tag",
|
expectedPath: "/v2/foo/bar/manifests/tag",
|
||||||
|
|
|
@ -196,6 +196,162 @@ func TestCatalogAPI(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTagsAPI tests the /v2/<name>/tags/list endpoint
|
||||||
|
func TestTagsAPI(t *testing.T) {
|
||||||
|
env := newTestEnv(t, false)
|
||||||
|
defer env.Shutdown()
|
||||||
|
|
||||||
|
imageName, err := reference.WithName("test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to parse reference: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := []string{
|
||||||
|
"2j2ar",
|
||||||
|
"asj9e",
|
||||||
|
"jyi7b",
|
||||||
|
"kb0j5",
|
||||||
|
"sb71y",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
createRepository(env, t, imageName.Name(), tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
queryParams url.Values
|
||||||
|
expectedStatusCode int
|
||||||
|
expectedBody tagsAPIResponse
|
||||||
|
expectedBodyErr *errcode.ErrorCode
|
||||||
|
expectedLinkHeader string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no query parameters",
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: tags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty last query parameter",
|
||||||
|
queryParams: url.Values{"last": []string{""}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: tags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty n query parameter",
|
||||||
|
queryParams: url.Values{"n": []string{""}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: tags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty last and n query parameters",
|
||||||
|
queryParams: url.Values{"last": []string{""}, "n": []string{""}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: tags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative n query parameter",
|
||||||
|
queryParams: url.Values{"n": []string{"-1"}},
|
||||||
|
expectedStatusCode: http.StatusBadRequest,
|
||||||
|
expectedBodyErr: &v2.ErrorCodePaginationNumberInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non integer n query parameter",
|
||||||
|
queryParams: url.Values{"n": []string{"foo"}},
|
||||||
|
expectedStatusCode: http.StatusBadRequest,
|
||||||
|
expectedBodyErr: &v2.ErrorCodePaginationNumberInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1st page",
|
||||||
|
queryParams: url.Values{"n": []string{"2"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: []string{
|
||||||
|
"2j2ar",
|
||||||
|
"asj9e",
|
||||||
|
}},
|
||||||
|
expectedLinkHeader: `</v2/test/tags/list?last=asj9e&n=2>; rel="next"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nth page",
|
||||||
|
queryParams: url.Values{"last": []string{"asj9e"}, "n": []string{"1"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: []string{
|
||||||
|
"jyi7b",
|
||||||
|
}},
|
||||||
|
expectedLinkHeader: `</v2/test/tags/list?last=jyi7b&n=1>; rel="next"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last page",
|
||||||
|
queryParams: url.Values{"last": []string{"jyi7b"}, "n": []string{"3"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: []string{
|
||||||
|
"kb0j5",
|
||||||
|
"sb71y",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "page size bigger than full list",
|
||||||
|
queryParams: url.Values{"n": []string{"100"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: tags},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "after marker",
|
||||||
|
queryParams: url.Values{"last": []string{"jyi7b"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: []string{
|
||||||
|
"kb0j5",
|
||||||
|
"sb71y",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "after non existent marker",
|
||||||
|
queryParams: url.Values{"last": []string{"does-not-exist"}, "n": []string{"3"}},
|
||||||
|
expectedStatusCode: http.StatusOK,
|
||||||
|
expectedBody: tagsAPIResponse{Name: imageName.Name(), Tags: []string{
|
||||||
|
"kb0j5",
|
||||||
|
"sb71y",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
tagsURL, err := env.builder.BuildTagsURL(imageName, test.queryParams)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error building tags URL: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(tagsURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error issuing request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != test.expectedStatusCode {
|
||||||
|
t.Fatalf("expected response status code to be %d, got %d", test.expectedStatusCode, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectedBodyErr != nil {
|
||||||
|
checkBodyHasErrorCodes(t, "invalid number of results requested", resp, *test.expectedBodyErr)
|
||||||
|
} else {
|
||||||
|
var body tagsAPIResponse
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
if err := dec.Decode(&body); err != nil {
|
||||||
|
t.Fatalf("unexpected error decoding response body: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(body, test.expectedBody) {
|
||||||
|
t.Fatalf("expected response body to be:\n%+v\ngot:\n%+v", test.expectedBody, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Header.Get("Link") != test.expectedLinkHeader {
|
||||||
|
t.Fatalf("expected response Link header to be %q, got %q", test.expectedLinkHeader, resp.Header.Get("Link"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkLink(t *testing.T, urlStr string, numEntries int, last string) url.Values {
|
func checkLink(t *testing.T, urlStr string, numEntries int, last string) url.Values {
|
||||||
re := regexp.MustCompile("<(/v2/_catalog.*)>; rel=\"next\"")
|
re := regexp.MustCompile("<(/v2/_catalog.*)>; rel=\"next\"")
|
||||||
matches := re.FindStringSubmatch(urlStr)
|
matches := re.FindStringSubmatch(urlStr)
|
||||||
|
|
Loading…
Reference in a new issue