mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
c6b822f3dd
fix(authz): fix isAdmin not using groups to determine if a user is admin. fix(authz): return 401 instead of 403 403 is correct as per HTTP spec However authz is not part of dist-spec and clients know only about 401 So this is a compromise. Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
1621 lines
44 KiB
Go
1621 lines
44 KiB
Go
//go:build sync && scrub && metrics && search && lint && mgmt
|
|
// +build sync,scrub,metrics,search,lint,mgmt
|
|
|
|
package api_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/project-zot/mockoidc"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"github.com/zitadel/oidc/pkg/client/rp"
|
|
"github.com/zitadel/oidc/pkg/oidc"
|
|
"golang.org/x/oauth2"
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/api"
|
|
"zotregistry.io/zot/pkg/api/config"
|
|
"zotregistry.io/zot/pkg/api/constants"
|
|
"zotregistry.io/zot/pkg/log"
|
|
mTypes "zotregistry.io/zot/pkg/meta/types"
|
|
reqCtx "zotregistry.io/zot/pkg/requestcontext"
|
|
storageTypes "zotregistry.io/zot/pkg/storage/types"
|
|
"zotregistry.io/zot/pkg/test"
|
|
"zotregistry.io/zot/pkg/test/mocks"
|
|
)
|
|
|
|
const sessionStr = "session"
|
|
|
|
func TestRoutes(t *testing.T) {
|
|
Convey("Make a new controller", t, func() {
|
|
port := test.GetFreePort()
|
|
baseURL := test.GetBaseURL(port)
|
|
conf := config.New()
|
|
conf.HTTP.Port = port
|
|
|
|
htpasswdPath := test.MakeHtpasswdFile()
|
|
defer os.Remove(htpasswdPath)
|
|
mockOIDCServer, err := mockoidc.Run()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer func() {
|
|
err := mockOIDCServer.Shutdown()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
|
|
mockOIDCConfig := mockOIDCServer.Config()
|
|
defaultVal := true
|
|
|
|
conf.HTTP.Auth = &config.AuthConfig{
|
|
HTPasswd: config.AuthHTPasswd{
|
|
Path: htpasswdPath,
|
|
},
|
|
OpenID: &config.OpenIDConfig{
|
|
Providers: map[string]config.OpenIDProviderConfig{
|
|
"oidc": {
|
|
ClientID: mockOIDCConfig.ClientID,
|
|
ClientSecret: mockOIDCConfig.ClientSecret,
|
|
KeyPath: "",
|
|
Issuer: mockOIDCConfig.Issuer,
|
|
Scopes: []string{"openid", "email"},
|
|
},
|
|
},
|
|
},
|
|
APIKey: defaultVal,
|
|
}
|
|
|
|
ctlr := api.NewController(conf)
|
|
|
|
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
|
ctlr.Config.Storage.Commit = true
|
|
|
|
cm := test.NewControllerManager(ctlr)
|
|
cm.StartAndWait(port)
|
|
defer cm.StopServer()
|
|
|
|
rthdlr := api.NewRouteHandler(ctlr)
|
|
|
|
// NOTE: the url or method itself doesn't matter below since we are calling the handlers directly,
|
|
// so path routing is bypassed
|
|
|
|
Convey("Test GithubCodeExchangeCallback", func() {
|
|
callback := rthdlr.GithubCodeExchangeCallback()
|
|
ctx := context.TODO()
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
tokens := &oidc.Tokens{}
|
|
relyingParty, err := rp.NewRelyingPartyOAuth(&oauth2.Config{})
|
|
So(err, ShouldBeNil)
|
|
|
|
callback(response, request, tokens, "state", relyingParty)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode, ShouldEqual, http.StatusUnauthorized)
|
|
})
|
|
|
|
Convey("Test OAuth2Callback errors", func() {
|
|
ctx := context.TODO()
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
_, err := api.OAuth2Callback(ctlr, response, request, "state", "email", []string{"group"})
|
|
So(err, ShouldEqual, zerr.ErrInvalidStateCookie)
|
|
|
|
session, _ := ctlr.CookieStore.Get(request, "statecookie")
|
|
|
|
session.Options.Secure = true
|
|
session.Options.HttpOnly = true
|
|
session.Options.SameSite = http.SameSiteDefaultMode
|
|
|
|
state := uuid.New().String()
|
|
|
|
session.Values["state"] = state
|
|
|
|
// let the session set its own id
|
|
err = session.Save(request, response)
|
|
So(err, ShouldBeNil)
|
|
|
|
_, err = api.OAuth2Callback(ctlr, response, request, "state", "email", []string{"group"})
|
|
So(err, ShouldEqual, zerr.ErrInvalidStateCookie)
|
|
})
|
|
|
|
Convey("List repositories authz error", func() {
|
|
var invalid struct{}
|
|
|
|
uacKey := reqCtx.GetContextKey()
|
|
ctx := context.WithValue(context.Background(), uacKey, invalid)
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, map[string]string{
|
|
"name": "test",
|
|
"reference": "b8b1231908844a55c251211c7a67ae3c809fb86a081a8eeb4a715e6d7d65625c",
|
|
})
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.ListRepositories(response, request)
|
|
|
|
resp := response.Result()
|
|
|
|
defer resp.Body.Close()
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("Delete manifest authz error", func() {
|
|
var invalid struct{}
|
|
|
|
uacKey := reqCtx.GetContextKey()
|
|
ctx := context.WithValue(context.Background(), uacKey, invalid)
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, map[string]string{
|
|
"name": "test",
|
|
"reference": "b8b1231908844a55c251211c7a67ae3c809fb86a081a8eeb4a715e6d7d65625c",
|
|
})
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.DeleteManifest(response, request)
|
|
|
|
resp := response.Result()
|
|
|
|
defer resp.Body.Close()
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("Get manifest", func() {
|
|
// overwrite controller storage
|
|
ctlr.StoreController.DefaultStore = &mocks.MockedImageStore{
|
|
GetImageManifestFn: func(repo string, reference string) ([]byte, godigest.Digest, string, error) {
|
|
return []byte{}, "", "", zerr.ErrRepoBadVersion
|
|
},
|
|
}
|
|
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, map[string]string{
|
|
"name": "test",
|
|
"reference": "b8b1231908844a55c251211c7a67ae3c809fb86a081a8eeb4a715e6d7d65625c",
|
|
})
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.GetManifest(response, request)
|
|
|
|
resp := response.Result()
|
|
|
|
defer resp.Body.Close()
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
|
|
})
|
|
|
|
Convey("UpdateManifest ", func() {
|
|
testUpdateManifest := func(urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
str := []byte("test")
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPut, baseURL, bytes.NewBuffer(str))
|
|
request = mux.SetURLVars(request, urlVars)
|
|
request.Header.Add("Content-Type", ispec.MediaTypeImageManifest)
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.UpdateManifest(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
// repo not found
|
|
statusCode := testUpdateManifest(
|
|
map[string]string{
|
|
"name": "test",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
|
godigest.Digest, error,
|
|
) {
|
|
return "", "", zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
// ErrManifestNotFound
|
|
statusCode = testUpdateManifest(
|
|
map[string]string{
|
|
"name": "test",
|
|
"reference": "reference",
|
|
},
|
|
|
|
&mocks.MockedImageStore{
|
|
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
|
godigest.Digest, error,
|
|
) {
|
|
return "", "", zerr.ErrManifestNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
// ErrBadManifest
|
|
statusCode = testUpdateManifest(
|
|
map[string]string{
|
|
"name": "test",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
|
godigest.Digest, error,
|
|
) {
|
|
return "", "", zerr.ErrBadManifest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
// ErrBlobNotFound
|
|
statusCode = testUpdateManifest(
|
|
map[string]string{
|
|
"name": "test",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
|
godigest.Digest, error,
|
|
) {
|
|
return "", "", zerr.ErrBlobNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrRepoBadVersion
|
|
statusCode = testUpdateManifest(
|
|
map[string]string{
|
|
"name": "test",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutImageManifestFn: func(repo, reference, mediaType string, body []byte) (godigest.Digest,
|
|
godigest.Digest, error,
|
|
) {
|
|
return "", "", zerr.ErrRepoBadVersion
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("DeleteManifest", func() {
|
|
testDeleteManifest := func(headers map[string]string, urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.Background(), http.MethodDelete, baseURL, nil)
|
|
request = mux.SetURLVars(request, urlVars)
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.DeleteManifest(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
// ErrRepoNotFound
|
|
statusCode := testDeleteManifest(
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "ErrManifestNotFound",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
|
return zerr.ErrRepoNotFound
|
|
},
|
|
},
|
|
)
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrManifestNotFound
|
|
statusCode = testDeleteManifest(
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "ErrManifestNotFound",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
|
return zerr.ErrManifestNotFound
|
|
},
|
|
},
|
|
)
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrUnexpectedError
|
|
statusCode = testDeleteManifest(
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "ErrUnexpectedError",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
// ErrBadManifest
|
|
statusCode = testDeleteManifest(
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "ErrBadManifest",
|
|
"reference": "reference",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteImageManifestFn: func(repo, reference string, detectCollision bool) error {
|
|
return zerr.ErrBadManifest
|
|
},
|
|
},
|
|
)
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
})
|
|
|
|
Convey("DeleteBlob", func() {
|
|
testDeleteBlob := func(urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodDelete, baseURL, nil)
|
|
request = mux.SetURLVars(request, urlVars)
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.DeleteBlob(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
// ErrUnexpectedError
|
|
statusCode := testDeleteBlob(
|
|
map[string]string{
|
|
"name": "ErrUnexpectedError",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobFn: func(repo string, digest godigest.Digest) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
statusCode = testDeleteBlob(
|
|
map[string]string{
|
|
"name": "ErrBadBlobDigest",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobFn: func(repo string, digest godigest.Digest) error {
|
|
return zerr.ErrBadBlobDigest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrBlobNotFound
|
|
statusCode = testDeleteBlob(
|
|
map[string]string{
|
|
"name": "ErrBlobNotFound",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobFn: func(repo string, digest godigest.Digest) error {
|
|
return zerr.ErrBlobNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrRepoNotFound
|
|
statusCode = testDeleteBlob(
|
|
map[string]string{
|
|
"name": "ErrRepoNotFound",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobFn: func(repo string, digest godigest.Digest) error {
|
|
return zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
})
|
|
|
|
// Check Blob
|
|
Convey("CheckBlob", func() {
|
|
testCheckBlob := func(urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodHead, baseURL, nil)
|
|
request = mux.SetURLVars(request, urlVars)
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.CheckBlob(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
// ErrBadBlobDigest
|
|
statusCode := testCheckBlob(
|
|
map[string]string{
|
|
"name": "ErrBadBlobDigest",
|
|
"digest": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrBadBlobDigest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrRepoNotFound
|
|
statusCode = testCheckBlob(
|
|
map[string]string{
|
|
"name": "ErrRepoNotFound",
|
|
"digest": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrBlobNotFound
|
|
statusCode = testCheckBlob(
|
|
map[string]string{
|
|
"name": "ErrBlobNotFound",
|
|
"digest": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrBlobNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrUnexpectedError
|
|
statusCode = testCheckBlob(
|
|
map[string]string{
|
|
"name": "ErrUnexpectedError",
|
|
"digest": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, ErrUnexpectedError
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
// Error Check Blob is not ok
|
|
statusCode = testCheckBlob(
|
|
map[string]string{
|
|
"name": "Check Blob Not Ok",
|
|
"digest": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return false, 0, nil
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
})
|
|
|
|
Convey("GetBlob", func() {
|
|
testGetBlob := func(urlVars map[string]string, ism *mocks.MockedImageStore) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, urlVars)
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.GetBlob(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
// ErrRepoNotFound
|
|
statusCode := testGetBlob(
|
|
map[string]string{
|
|
"name": "ErrRepoNotFound",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobFn: func(repo string, digest godigest.Digest, mediaType string) (io.ReadCloser, int64, error) {
|
|
return io.NopCloser(bytes.NewBuffer([]byte(""))), 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrRepoNotFound
|
|
statusCode = testGetBlob(
|
|
map[string]string{
|
|
"name": "ErrRepoNotFound",
|
|
"digest": "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobFn: func(repo string, digest godigest.Digest, mediaType string) (io.ReadCloser, int64, error) {
|
|
return io.NopCloser(bytes.NewBuffer([]byte(""))), 0, zerr.ErrBadBlobDigest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
})
|
|
|
|
Convey("CreateBlobUpload", func() {
|
|
testCreateBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPost, baseURL, nil)
|
|
request = mux.SetURLVars(request,
|
|
map[string]string{
|
|
"name": "test",
|
|
"mount": "1234",
|
|
})
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.CreateBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
// ErrRepoNotFound
|
|
statusCode := testCreateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"mount", "1234"},
|
|
},
|
|
map[string]string{},
|
|
&mocks.MockedImageStore{
|
|
NewBlobUploadFn: func(repo string) (string, error) {
|
|
return "", zerr.ErrRepoNotFound
|
|
},
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// a full blob upload if multiple digests are present
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "1234"},
|
|
{"digest", "5234"},
|
|
},
|
|
map[string]string{},
|
|
&mocks.MockedImageStore{
|
|
NewBlobUploadFn: func(repo string) (string, error) {
|
|
return "", zerr.ErrRepoNotFound
|
|
},
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// a full blob upload if content type is wrong
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "1234"},
|
|
},
|
|
map[string]string{
|
|
"Content-Type": "badContentType",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
NewBlobUploadFn: func(repo string) (string, error) {
|
|
return "", zerr.ErrRepoNotFound
|
|
},
|
|
CheckBlobFn: func(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
return true, 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusUnsupportedMediaType)
|
|
|
|
// digest prezent imgStore err
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "1234"},
|
|
},
|
|
map[string]string{
|
|
"Content-Type": constants.BinaryMediaType,
|
|
"Content-Length": "100",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest) (string, int64, error) {
|
|
return sessionStr, 0, zerr.ErrBadBlobDigest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
// digest prezent bad length
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "1234"},
|
|
},
|
|
map[string]string{
|
|
"Content-Type": constants.BinaryMediaType,
|
|
"Content-Length": "100",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FullBlobUploadFn: func(repo string, body io.Reader, digest godigest.Digest) (string, int64, error) {
|
|
return sessionStr, 20, nil
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
// newBlobUpload not found
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Type": constants.BinaryMediaType,
|
|
"Content-Length": "100",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
NewBlobUploadFn: func(repo string) (string, error) {
|
|
return "", zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// newBlobUpload unexpected error
|
|
statusCode = testCreateBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Type": constants.BinaryMediaType,
|
|
"Content-Length": "100",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
NewBlobUploadFn: func(repo string) (string, error) {
|
|
return "", ErrUnexpectedError
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("GetBlobUpload", func() {
|
|
testGetBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.GetBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
// ErrBadUploadRange
|
|
statusCode := testGetBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "test",
|
|
"session_id": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobUploadFn: func(repo, uuid string) (int64, error) {
|
|
return 0, zerr.ErrBadUploadRange
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrBadBlobDigest
|
|
statusCode = testGetBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"mount", "1234"},
|
|
},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "test",
|
|
"session_id": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobUploadFn: func(repo, uuid string) (int64, error) {
|
|
return 0, zerr.ErrBadBlobDigest
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
// ErrRepoNotFound
|
|
statusCode = testGetBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"mount", "1234"},
|
|
},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "test",
|
|
"session_id": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobUploadFn: func(repo, uuid string) (int64, error) {
|
|
return 0, zerr.ErrRepoNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrUploadNotFound
|
|
statusCode = testGetBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"mount", "1234"},
|
|
},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "test",
|
|
"session_id": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobUploadFn: func(repo, uuid string) (int64, error) {
|
|
return 0, zerr.ErrUploadNotFound
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusNotFound)
|
|
|
|
// ErrUploadNotFound
|
|
statusCode = testGetBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"mount", "1234"},
|
|
},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "test",
|
|
"session_id": "1234",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetBlobUploadFn: func(repo, uuid string) (int64, error) {
|
|
return 0, ErrUnexpectedError
|
|
},
|
|
})
|
|
So(statusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("PatchBlobUpload", func() {
|
|
testPatchBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPatch, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.PatchBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
status := testPatchBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Length": "abc",
|
|
"Content-Range": "abc",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{},
|
|
)
|
|
So(status, ShouldEqual, http.StatusBadRequest)
|
|
|
|
status = testPatchBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-50",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{},
|
|
)
|
|
So(status, ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
|
|
|
|
status = testPatchBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 100, zerr.ErrRepoNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testPatchBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 100, zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testPatchBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 100, ErrUnexpectedError
|
|
},
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("UpdateBlobUpload", func() {
|
|
testUpdateBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPatch, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.UpdateBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
status := testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{},
|
|
)
|
|
So(status, ShouldEqual, http.StatusBadRequest)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "badRange",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{},
|
|
)
|
|
So(status, ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 0, zerr.ErrBadUploadRange
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusBadRequest)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 0, zerr.ErrRepoNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 0, zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "100",
|
|
"Content-Range": "1-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
PutBlobChunkFn: func(repo, uuid string, from, to int64, body io.Reader) (int64, error) {
|
|
return 0, ErrUnexpectedError
|
|
},
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrBadBlobDigest
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusBadRequest)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrBadUploadRange
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusBadRequest)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrRepoNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("DeleteBlobUpload", func() {
|
|
testDeleteBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPatch, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.DeleteBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
status := testDeleteBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return zerr.ErrRepoNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testDeleteBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusNotFound)
|
|
|
|
status = testDeleteBlobUpload(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
DeleteBlobUploadFn: func(repo, uuid string) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("ListRepositories", func() {
|
|
testListRepositoriesWithSubstores := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
ctlr.StoreController.SubStore = map[string]storageTypes.ImageStore{
|
|
"test": &mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{}, ErrUnexpectedError
|
|
},
|
|
},
|
|
}
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodGet, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.ListRepositories(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
testListRepositories := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
ctlr.StoreController.SubStore = map[string]storageTypes.ImageStore{}
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPatch, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.ListRepositories(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
// with substores
|
|
status := testListRepositoriesWithSubstores(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{}, ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
|
|
status = testListRepositories(
|
|
[]struct{ k, v string }{},
|
|
map[string]string{},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{}, ErrUnexpectedError
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("ListRepositories with Authz", func() {
|
|
ctlr.StoreController.DefaultStore = &mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{"repo"}, nil
|
|
},
|
|
}
|
|
ctlr.StoreController.SubStore = map[string]storageTypes.ImageStore{
|
|
"test1": &mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{"repo1"}, nil
|
|
},
|
|
},
|
|
"test2": &mocks.MockedImageStore{
|
|
GetRepositoriesFn: func() ([]string, error) {
|
|
return []string{"repo2"}, nil
|
|
},
|
|
},
|
|
}
|
|
|
|
// make the user an admin
|
|
// acCtx := api.NewAccessControlContext(map[string]bool{}, true)
|
|
// ctx := context.WithValue(context.Background(), "ctx", acCtx)
|
|
ctx := context.Background()
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
request = mux.SetURLVars(request, map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
})
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.ListRepositories(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
|
})
|
|
|
|
Convey("Test API keys", func() {
|
|
Convey("CreateAPIKey invalid access control context", func() {
|
|
var invalid struct{}
|
|
|
|
uacKey := reqCtx.GetContextKey()
|
|
ctx := context.WithValue(context.Background(), uacKey, invalid)
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.CreateAPIKey(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
|
|
|
|
request, _ = http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
|
response = httptest.NewRecorder()
|
|
|
|
rthdlr.GetAPIKeys(response, request)
|
|
|
|
resp = response.Result()
|
|
defer resp.Body.Close()
|
|
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("CreateAPIKey bad request body", func() {
|
|
userAc := reqCtx.NewUserAccessControl()
|
|
userAc.SetUsername("test")
|
|
ctx := userAc.DeriveContext(context.Background())
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader([]byte{}))
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.CreateAPIKey(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
|
|
})
|
|
|
|
Convey("CreateAPIKey error on AddUserAPIKey", func() {
|
|
userAc := reqCtx.NewUserAccessControl()
|
|
userAc.SetUsername("test")
|
|
ctx := userAc.DeriveContext(context.Background())
|
|
|
|
payload := api.APIKeyPayload{
|
|
Label: "test",
|
|
Scopes: []string{"test"},
|
|
}
|
|
reqBody, err := json.Marshal(payload)
|
|
So(err, ShouldBeNil)
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewReader(reqBody))
|
|
response := httptest.NewRecorder()
|
|
|
|
ctlr.MetaDB = mocks.MetaDBMock{
|
|
AddUserAPIKeyFn: func(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
}
|
|
|
|
rthdlr.CreateAPIKey(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
|
|
Convey("Revoke error on DeleteUserAPIKeyFn", func() {
|
|
userAc := reqCtx.NewUserAccessControl()
|
|
userAc.SetUsername("test")
|
|
ctx := userAc.DeriveContext(context.Background())
|
|
|
|
request, _ := http.NewRequestWithContext(ctx, http.MethodDelete, baseURL, bytes.NewReader([]byte{}))
|
|
response := httptest.NewRecorder()
|
|
|
|
q := request.URL.Query()
|
|
q.Add("id", "apikeyid")
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
ctlr.MetaDB = mocks.MetaDBMock{
|
|
DeleteUserAPIKeyFn: func(ctx context.Context, id string) error {
|
|
return ErrUnexpectedError
|
|
},
|
|
}
|
|
|
|
rthdlr.RevokeAPIKey(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
So(resp.StatusCode, ShouldEqual, http.StatusInternalServerError)
|
|
})
|
|
})
|
|
|
|
Convey("Helper functions", func() {
|
|
testUpdateBlobUpload := func(
|
|
query []struct{ k, v string },
|
|
headers map[string]string,
|
|
vars map[string]string,
|
|
ism *mocks.MockedImageStore,
|
|
) int {
|
|
ctlr.StoreController.DefaultStore = ism
|
|
request, _ := http.NewRequestWithContext(context.TODO(), http.MethodPatch, baseURL, nil)
|
|
|
|
request = mux.SetURLVars(request, vars)
|
|
|
|
q := request.URL.Query()
|
|
for _, qe := range query {
|
|
q.Add(qe.k, qe.v)
|
|
}
|
|
request.URL.RawQuery = q.Encode()
|
|
|
|
for k, v := range headers {
|
|
request.Header.Add(k, v)
|
|
}
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
rthdlr.UpdateBlobUpload(response, request)
|
|
|
|
resp := response.Result()
|
|
defer resp.Body.Close()
|
|
|
|
return resp.StatusCode
|
|
}
|
|
|
|
status := testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "a-100",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "20-a",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
|
|
|
|
status = testUpdateBlobUpload(
|
|
[]struct{ k, v string }{
|
|
{"digest", "sha256:7b8437f04f83f084b7ed68ad8c4a4947e12fc4e1b006b38129bac89114ec3621"},
|
|
},
|
|
map[string]string{
|
|
"Content-Length": "0",
|
|
"Content-Range": "20-1",
|
|
},
|
|
map[string]string{
|
|
"name": "repo",
|
|
"session_id": "test",
|
|
},
|
|
&mocks.MockedImageStore{
|
|
FinishBlobUploadFn: func(repo, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
return zerr.ErrUploadNotFound
|
|
},
|
|
},
|
|
)
|
|
So(status, ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
|
|
})
|
|
})
|
|
}
|
|
|
|
type readerThatFails struct{}
|
|
|
|
func (r readerThatFails) Read(p []byte) (int, error) {
|
|
return 0, zerr.ErrInjected
|
|
}
|
|
|
|
func TestWriteDataFromReader(t *testing.T) {
|
|
Convey("", t, func() {
|
|
response := httptest.NewRecorder()
|
|
api.WriteDataFromReader(response, 200, 100, ispec.MediaTypeImageManifest, readerThatFails{},
|
|
log.NewLogger("debug", ""))
|
|
|
|
So(response.Code, ShouldEqual, 200)
|
|
})
|
|
}
|