UT for package utils

This commit is contained in:
Wenkai Yin 2016-08-16 13:45:59 +08:00
parent e0649f6d69
commit 7538bbf066
14 changed files with 977 additions and 225 deletions

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
set -e
echo "mode: set" >>profile.cov echo "mode: set" >>profile.cov
for dir in $(go list ./... | grep -v -E 'vendor|tests') for dir in $(go list ./... | grep -v -E 'vendor|tests')
do do

View File

@ -17,6 +17,7 @@ package log
import ( import (
"fmt" "fmt"
"strings"
) )
// Level ... // Level ...
@ -56,7 +57,7 @@ func (l Level) string() (lvl string) {
func parseLevel(lvl string) (level Level, err error) { func parseLevel(lvl string) (level Level, err error) {
switch lvl { switch strings.ToLower(lvl) {
case "debug": case "debug":
level = DebugLevel level = DebugLevel
case "info": case "info":

61
utils/log/level_test.go Normal file
View File

@ -0,0 +1,61 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"testing"
)
func TestString(t *testing.T) {
m := map[Level]string{
DebugLevel: "DEBUG",
InfoLevel: "INFO",
WarningLevel: "WARNING",
ErrorLevel: "ERROR",
FatalLevel: "FATAL",
-1: "UNKNOWN",
}
for level, str := range m {
if level.string() != str {
t.Errorf("unexpected string: %s != %s", level.string(), str)
}
}
}
func TestParseLevel(t *testing.T) {
m := map[string]Level{
"DEBUG": DebugLevel,
"INFO": InfoLevel,
"WARNING": WarningLevel,
"ERROR": ErrorLevel,
"FATAL": FatalLevel,
}
for str, level := range m {
l, err := parseLevel(str)
if err != nil {
t.Errorf("failed to parse level: %v", err)
}
if l != level {
t.Errorf("unexpected level: %d != %d", l, level)
}
}
if _, err := parseLevel("UNKNOWN"); err == nil {
t.Errorf("unexpected behaviour: should be error here")
}
}

View File

@ -1,9 +0,0 @@
package log
import (
"testing"
)
func TestMain(t *testing.T) {
}

156
utils/log/logger_test.go Normal file
View File

@ -0,0 +1,156 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package log
import (
"bytes"
"os"
"strings"
"testing"
)
var (
message = "message"
)
func TestSetx(t *testing.T) {
logger := New(nil, nil, WarningLevel)
logger.SetOutput(os.Stdout)
fmt := NewTextFormatter()
logger.SetFormatter(fmt)
logger.SetLevel(DebugLevel)
if logger.out != os.Stdout {
t.Errorf("unexpected outer: %v != %v", logger.out, os.Stdout)
}
if logger.fmtter != fmt {
t.Errorf("unexpected formatter: %v != %v", logger.fmtter, fmt)
}
if logger.lvl != DebugLevel {
t.Errorf("unexpected log level: %v != %v", logger.lvl, DebugLevel)
}
}
func TestDebug(t *testing.T) {
buf := enter()
defer exit()
Debug(message)
str := buf.String()
if len(str) != 0 {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestDebugf(t *testing.T) {
buf := enter()
defer exit()
Debugf("%s", message)
str := buf.String()
if len(str) != 0 {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestInfo(t *testing.T) {
buf := enter()
defer exit()
Info(message)
str := buf.String()
if strings.HasSuffix(str, "[INFO] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestInfof(t *testing.T) {
buf := enter()
defer exit()
Infof("%s", message)
str := buf.String()
if strings.HasSuffix(str, "[INFO] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestWarning(t *testing.T) {
buf := enter()
defer exit()
Warning(message)
str := buf.String()
if strings.HasSuffix(str, "[WARNING] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestWarningf(t *testing.T) {
buf := enter()
defer exit()
Warningf("%s", message)
str := buf.String()
if strings.HasSuffix(str, "[WARNING] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestError(t *testing.T) {
buf := enter()
defer exit()
Error(message)
str := buf.String()
if strings.HasSuffix(str, "[ERROR] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func TestErrorf(t *testing.T) {
buf := enter()
defer exit()
Errorf("%s", message)
str := buf.String()
if strings.HasSuffix(str, "[ERROR] message") {
t.Errorf("unexpected message: %s != %s", str, "")
}
}
func enter() *bytes.Buffer {
b := make([]byte, 0, 32)
buf := bytes.NewBuffer(b)
logger.SetOutput(buf)
return buf
}
func exit() {
logger.SetOutput(os.Stdout)
}

View File

@ -17,15 +17,26 @@ package auth
import ( import (
"net/http" "net/http"
"net/http/httptest"
"strings" "strings"
"testing" "testing"
"github.com/docker/distribution/registry/client/auth" "github.com/docker/distribution/registry/client/auth"
"github.com/vmware/harbor/utils/test"
) )
func TestNewAuthorizerStore(t *testing.T) { func TestNewAuthorizerStore(t *testing.T) {
server := newRegistryServer() handler := test.Handler(&test.Response{
StatusCode: http.StatusUnauthorized,
Headers: map[string]string{
"Www-Authenticate": "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\"",
},
})
server := test.NewServer(&test.RequestHandlerMapping{
Method: "GET",
Pattern: "/v2/",
Handler: handler,
})
defer server.Close() defer server.Close()
_, err := NewAuthorizerStore(server.URL, false, nil) _, err := NewAuthorizerStore(server.URL, false, nil)
@ -76,16 +87,3 @@ func TestModify(t *testing.T) {
t.Fatal("\"Authorization\" header does not start with \"Bearer\"") t.Fatal("\"Authorization\" header does not start with \"Bearer\"")
} }
} }
func newRegistryServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/v2/", handlePing)
return httptest.NewServer(mux)
}
func handlePing(w http.ResponseWriter, r *http.Request) {
challenge := "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\""
w.Header().Set("Www-Authenticate", challenge)
w.WriteHeader(http.StatusUnauthorized)
}

View File

@ -16,20 +16,29 @@
package auth package auth
import ( import (
"encoding/json"
"net/http" "net/http"
"net/http/httptest"
"testing" "testing"
"time"
)
var ( "github.com/vmware/harbor/utils/test"
token = "token"
) )
func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) { func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
tokenServer := newTokenServer() handler := test.Handler(&test.Response{
defer tokenServer.Close() Body: []byte(`
{
"token":"token",
"expires_in":300,
"issued_at":"2016-08-17T23:17:58+08:00"
}
`),
})
server := test.NewServer(&test.RequestHandlerMapping{
Method: "GET",
Pattern: "/token",
Handler: handler,
})
defer server.Close()
authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull") authorizer := NewStandardTokenAuthorizer(nil, false, "repository", "library/ubuntu", "pull")
req, err := http.NewRequest("GET", "http://registry", nil) req, err := http.NewRequest("GET", "http://registry", nil)
@ -38,7 +47,7 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
} }
params := map[string]string{ params := map[string]string{
"realm": tokenServer.URL + "/token", "realm": server.URL + "/token",
} }
if err := authorizer.Authorize(req, params); err != nil { if err := authorizer.Authorize(req, params); err != nil {
@ -46,8 +55,8 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
} }
tk := req.Header.Get("Authorization") tk := req.Header.Get("Authorization")
if tk != "Bearer "+token { if tk != "Bearer token" {
t.Errorf("unexpected token: %s != %s", tk, "Bearer "+token) t.Errorf("unexpected token: %s != %s", tk, "Bearer token")
} }
} }
@ -58,24 +67,3 @@ func TestSchemeOfStandardTokenAuthorizer(t *testing.T) {
} }
} }
func newTokenServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/token", handleToken)
return httptest.NewServer(mux)
}
func handleToken(w http.ResponseWriter, r *http.Request) {
result := map[string]interface{}{}
result["token"] = token
result["expires_in"] = 300
result["issued_at"] = time.Now().Format(time.RFC3339)
encoder := json.NewEncoder(w)
if err := encoder.Encode(result); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
}

View File

@ -0,0 +1,56 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"testing"
"github.com/docker/distribution/manifest/schema2"
)
func TestUnMarshal(t *testing.T) {
b := []byte(`{
"schemaVersion":2,
"mediaType":"application/vnd.docker.distribution.manifest.v2+json",
"config":{
"mediaType":"application/vnd.docker.container.image.v1+json",
"size":1473,
"digest":"sha256:c54a2cc56cbb2f04003c1cd4507e118af7c0d340fe7e2720f70976c4b75237dc"
},
"layers":[
{
"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip",
"size":974,
"digest":"sha256:c04b14da8d1441880ed3fe6106fb2cc6fa1c9661846ac0266b8a5ec8edf37b7c"
}
]
}`)
manifest, _, err := UnMarshal(schema2.MediaTypeManifest, b)
if err != nil {
t.Fatalf("failed to parse manifest: %v", err)
}
refs := manifest.References()
if len(refs) != 1 {
t.Fatalf("unexpected length of reference: %d != %d", len(refs), 1)
}
digest := "sha256:c04b14da8d1441880ed3fe6106fb2cc6fa1c9661846ac0266b8a5ec8edf37b7c"
if refs[0].Digest.String() != digest {
t.Errorf("unexpected digest: %s != %s", refs[0].Digest.String(), digest)
}
}

View File

@ -48,11 +48,6 @@ func NewRegistry(endpoint string, client *http.Client) (*Registry, error) {
// NewRegistryWithModifiers returns an instance of Registry according to the modifiers // NewRegistryWithModifiers returns an instance of Registry according to the modifiers
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) { func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
t := &http.Transport{ t := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure, InsecureSkipVerify: insecure,
@ -61,12 +56,9 @@ func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modif
transport := NewTransport(t, modifiers...) transport := NewTransport(t, modifiers...)
return &Registry{ return NewRegistry(endpoint, &http.Client{
Endpoint: u, Transport: transport,
client: &http.Client{ })
Transport: transport,
},
}, nil
} }
// Catalog ... // Catalog ...

View File

@ -0,0 +1,150 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"testing"
"github.com/vmware/harbor/utils/test"
)
func TestNewRegistryWithModifiers(t *testing.T) {
_, err := NewRegistryWithModifiers("http://registry.org", false, nil)
if err != nil {
t.Errorf("fail to crearte client of registry: %v", err)
}
}
func TestPing(t *testing.T) {
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: "/v2/",
Handler: test.Handler(nil),
})
defer server.Close()
client, err := newRegistryClient(server.URL)
if err != nil {
t.Fatalf("failed to create client for registry: %v", err)
}
if err = client.Ping(); err != nil {
t.Errorf("failed to ping registry: %v", err)
}
}
func TestCatalog(t *testing.T) {
repositories := make([]string, 0, 1001)
for i := 0; i < 1001; i++ {
repositories = append(repositories, strconv.Itoa(i))
}
handler := func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
last := q.Get("last")
n, err := strconv.Atoi(q.Get("n"))
if err != nil || n <= 0 {
n = 1000
}
length := len(repositories)
begin := length
if len(last) == 0 {
begin = 0
} else {
for i, repository := range repositories {
if repository == last {
begin = i + 1
break
}
}
}
end := begin + n
if end > length {
end = length
}
w.Header().Set(http.CanonicalHeaderKey("Content-Type"), "application/json")
if end < length {
u, err := url.Parse("/v2/_catalog")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
values := u.Query()
values.Add("last", repositories[end-1])
values.Add("n", strconv.Itoa(n))
u.RawQuery = values.Encode()
link := fmt.Sprintf("<%s>; rel=\"next\"", u.String())
w.Header().Set(http.CanonicalHeaderKey("link"), link)
}
repos := struct {
Repositories []string `json:"repositories"`
}{
Repositories: []string{},
}
if begin < length {
repos.Repositories = repositories[begin:end]
}
b, err := json.Marshal(repos)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(b)
}
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: "/v2/_catalog",
Handler: handler,
})
defer server.Close()
client, err := newRegistryClient(server.URL)
if err != nil {
t.Fatalf("failed to create client for registry: %v", err)
}
repos, err := client.Catalog()
if err != nil {
t.Fatalf("failed to catalog repositories: %v", err)
}
if len(repos) != len(repositories) {
t.Errorf("unexpected length of repositories: %d != %d", len(repos), len(repositories))
}
}
func newRegistryClient(url string) (*Registry, error) {
return NewRegistry(url, &http.Client{})
}

View File

@ -61,13 +61,6 @@ func NewRepository(name, endpoint string, client *http.Client) (*Repository, err
// NewRepositoryWithModifiers returns an instance of Repository according to the modifiers // NewRepositoryWithModifiers returns an instance of Repository according to the modifiers
func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) { func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers ...Modifier) (*Repository, error) {
name = strings.TrimSpace(name)
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
t := &http.Transport{ t := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure, InsecureSkipVerify: insecure,
@ -76,13 +69,9 @@ func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers
transport := NewTransport(t, modifiers...) transport := NewTransport(t, modifiers...)
return &Repository{ return NewRepository(name, endpoint, &http.Client{
Name: name, Transport: transport,
Endpoint: u, })
client: &http.Client{
Transport: transport,
},
}, nil
} }
func parseError(err error) error { func parseError(err error) error {
@ -347,7 +336,7 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
data = resp.Body data = resp.Body
return return
} }
// can not close the connect if the status code is 200
defer resp.Body.Close() defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body) b, err := ioutil.ReadAll(resp.Body)
@ -428,7 +417,6 @@ func (r *Repository) PushBlob(digest string, size int64, data io.Reader) error {
if err != nil { if err != nil {
return err return err
} }
return r.monolithicBlobUpload(location, digest, size, data) return r.monolithicBlobUpload(location, digest, size, data)
} }
@ -482,5 +470,12 @@ func buildInitiateBlobUploadURL(endpoint, repoName string) string {
} }
func buildMonolithicBlobUploadURL(location, digest string) string { func buildMonolithicBlobUploadURL(location, digest string) string {
return fmt.Sprintf("%s&digest=%s", location, digest) query := ""
if strings.ContainsRune(location, '?') {
query = "&"
} else {
query = "?"
}
query += fmt.Sprintf("digest=%s", digest)
return fmt.Sprintf("%s%s", location, query)
} }

View File

@ -16,179 +16,395 @@
package registry package registry
import ( import (
"encoding/json" "bytes"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/url"
"os" "strconv"
"strings" "strings"
"testing" "testing"
"time"
"github.com/vmware/harbor/utils/registry/auth" "github.com/docker/distribution/manifest/schema2"
registry_error "github.com/vmware/harbor/utils/registry/error" registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils/test"
) )
var ( var (
username = "user" repository = "library/hello-world"
password = "P@ssw0rd" tag = "latest"
repo = "samalba/my-app"
tags = tagResp{Tags: []string{"1.0", "2.0", "3.0"}} mediaType = schema2.MediaTypeManifest
validToken = "valid_token" manifest = []byte("manifest")
invalidToken = "invalid_token"
credential auth.Credential blob = []byte("blob")
registryServer *httptest.Server
tokenServer *httptest.Server uuid = "0663ff44-63bb-11e6-8b77-86f30ca893d3"
repositoryClient *Repository
digest = "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"
) )
type tagResp struct { func TestNewRepositoryWithModifiers(t *testing.T) {
Tags []string `json:"tags"` _, err := NewRepositoryWithModifiers("library/ubuntu",
} "http://registry.org", true, nil)
if err != nil {
func TestMain(m *testing.M) { t.Fatalf("failed to create client for repository: %v", err)
//log.SetLevel(log.DebugLevel)
credential = auth.NewBasicAuthCredential(username, password)
tokenServer = initTokenServer()
defer tokenServer.Close()
registryServer = initRegistryServer()
defer registryServer.Close()
os.Exit(m.Run())
}
func initRegistryServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/v2/", servePing)
mux.HandleFunc(fmt.Sprintf("/v2/%s/tags/list", repo), serveTaglisting)
return httptest.NewServer(mux)
}
//response ping request: http://registry/v2
func servePing(w http.ResponseWriter, r *http.Request) {
if !isTokenValid(r) {
challenge(w)
return
} }
} }
func serveTaglisting(w http.ResponseWriter, r *http.Request) { func TestBlobExist(t *testing.T) {
if !isTokenValid(r) { handler := func(w http.ResponseWriter, r *http.Request) {
challenge(w) path := r.URL.Path
return dgt := path[strings.LastIndex(path, "/")+1 : len(path)]
} if dgt == digest {
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(blob)))
if err := json.NewEncoder(w).Encode(tags); err != nil { w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
w.Write([]byte(err.Error())) w.Header().Add(http.CanonicalHeaderKey("Content-Type"), "application/octet-stream")
w.WriteHeader(http.StatusInternalServerError) return
return
}
}
func isTokenValid(r *http.Request) bool {
valid := false
auth := r.Header.Get(http.CanonicalHeaderKey("Authorization"))
if len(auth) != 0 {
auth = strings.TrimSpace(auth)
index := strings.Index(auth, "Bearer")
token := auth[index+6:]
token = strings.TrimSpace(token)
if token == validToken {
valid = true
} }
}
return valid
}
func challenge(w http.ResponseWriter) { w.WriteHeader(http.StatusNotFound)
challenge := "Bearer realm=\"" + tokenServer.URL + "/service/token\",service=\"token-service\""
w.Header().Set("Www-Authenticate", challenge)
w.WriteHeader(http.StatusUnauthorized)
return
}
func initTokenServer() *httptest.Server {
mux := http.NewServeMux()
mux.HandleFunc("/service/token", serveToken)
return httptest.NewServer(mux)
}
func serveToken(w http.ResponseWriter, r *http.Request) {
u, p, ok := r.BasicAuth()
if !ok || u != username || p != password {
w.WriteHeader(http.StatusUnauthorized)
return
} }
result := make(map[string]interface{}) server := test.NewServer(
result["token"] = validToken &test.RequestHandlerMapping{
result["expires_in"] = 300 Method: "HEAD",
result["issued_at"] = time.Now().Format(time.RFC3339) Pattern: fmt.Sprintf("/v2/%s/blobs/", repository),
Handler: handler,
})
defer server.Close()
encoder := json.NewEncoder(w) client, err := newRepository(server.URL)
if err := encoder.Encode(result); err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) err = parseError(err)
w.Write([]byte(err.Error())) t.Fatalf("failed to create client for repository: %v", err)
return }
exist, err := client.BlobExist(digest)
if err != nil {
t.Fatalf("failed to check the existence of blob: %v", err)
}
if !exist {
t.Errorf("blob should exist on registry, but it does not exist")
}
exist, err = client.BlobExist("invalid_digest")
if err != nil {
t.Fatalf("failed to check the existence of blob: %v", err)
}
if exist {
t.Errorf("blob should not exist on registry, but it exists")
}
}
func TestPullBlob(t *testing.T) {
handler := test.Handler(&test.Response{
Headers: map[string]string{
"Content-Length": strconv.Itoa(len(blob)),
"Docker-Content-Digest": digest,
"Content-Type": "application/octet-stream",
},
Body: blob,
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
size, reader, err := client.PullBlob(digest)
if err != nil {
t.Fatalf("failed to pull blob: %v", err)
}
if size != int64(len(blob)) {
t.Errorf("unexpected size of blob: %d != %d", size, len(blob))
}
b, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatalf("failed to read from reader: %v", err)
}
if bytes.Compare(b, blob) != 0 {
t.Errorf("unexpected blob: %s != %s", string(b), string(blob))
}
}
func TestPushBlob(t *testing.T) {
location := ""
initUploadHandler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), "0")
w.Header().Add(http.CanonicalHeaderKey("Location"), location)
w.Header().Add(http.CanonicalHeaderKey("Range"), "0-0")
w.Header().Add(http.CanonicalHeaderKey("Docker-Upload-UUID"), uuid)
w.WriteHeader(http.StatusAccepted)
}
monolithicUploadHandler := test.Handler(&test.Response{
StatusCode: http.StatusCreated,
Headers: map[string]string{
"Content-Length": "0",
"Location": fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
"Docker-Content-Digest": digest,
},
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "POST",
Pattern: fmt.Sprintf("/v2/%s/blobs/uploads/", repository),
Handler: initUploadHandler,
},
&test.RequestHandlerMapping{
Method: "PUT",
Pattern: fmt.Sprintf("/v2/%s/blobs/uploads/%s", repository, uuid),
Handler: monolithicUploadHandler,
})
defer server.Close()
location = fmt.Sprintf("%s/v2/%s/blobs/uploads/%s", server.URL, repository, uuid)
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
if err = client.PushBlob(digest, int64(len(blob)), bytes.NewReader(blob)); err != nil {
t.Fatalf("failed to push blob: %v", err)
}
}
func TestDeleteBlob(t *testing.T) {
handler := test.Handler(&test.Response{
StatusCode: http.StatusAccepted,
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "DELETE",
Pattern: fmt.Sprintf("/v2/%s/blobs/%s", repository, digest),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
if err = client.DeleteBlob(digest); err != nil {
t.Fatalf("failed to delete blob: %v", err)
}
}
func TestManifestExist(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
tg := path[strings.LastIndex(path, "/")+1 : len(path)]
if tg == tag {
w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
w.Header().Add(http.CanonicalHeaderKey("Content-Type"), mediaType)
return
}
w.WriteHeader(http.StatusNotFound)
}
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "HEAD",
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
d, exist, err := client.ManifestExist(tag)
if err != nil {
t.Fatalf("failed to check the existence of manifest: %v", err)
}
if !exist || d != digest {
t.Errorf("manifest should exist on registry, but it does not exist")
}
_, exist, err = client.ManifestExist("invalid_tag")
if err != nil {
t.Fatalf("failed to check the existence of manifest: %v", err)
}
if exist {
t.Errorf("manifest should not exist on registry, but it exists")
}
}
func TestPullManifest(t *testing.T) {
handler := test.Handler(&test.Response{
Headers: map[string]string{
"Docker-Content-Digest": digest,
"Content-Type": mediaType,
},
Body: manifest,
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
d, md, payload, err := client.PullManifest(tag, []string{mediaType})
if err != nil {
t.Fatalf("failed to pull manifest: %v", err)
}
if d != digest {
t.Errorf("unexpected digest of manifest: %s != %s", d, digest)
}
if md != mediaType {
t.Errorf("unexpected media type of manifest: %s != %s", md, mediaType)
}
if bytes.Compare(payload, manifest) != 0 {
t.Errorf("unexpected manifest: %s != %s", string(payload), string(manifest))
}
}
func TestPushManifest(t *testing.T) {
handler := test.Handler(&test.Response{
StatusCode: http.StatusCreated,
Headers: map[string]string{
"Content-Length": "0",
"Docker-Content-Digest": digest,
"Location": "",
},
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "PUT",
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, tag),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
d, err := client.PushManifest(tag, mediaType, manifest)
if err != nil {
t.Fatalf("failed to pull manifest: %v", err)
}
if d != digest {
t.Errorf("unexpected digest of manifest: %s != %s", d, digest)
}
}
func TestDeleteTag(t *testing.T) {
manifestExistHandler := test.Handler(&test.Response{
Headers: map[string]string{
"Docker-Content-Digest": digest,
"Content-Type": mediaType,
},
})
deleteManifestandler := test.Handler(&test.Response{
StatusCode: http.StatusAccepted,
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "HEAD",
Pattern: fmt.Sprintf("/v2/%s/manifests/", repository),
Handler: manifestExistHandler,
},
&test.RequestHandlerMapping{
Method: "DELETE",
Pattern: fmt.Sprintf("/v2/%s/manifests/%s", repository, digest),
Handler: deleteManifestandler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
if err = client.DeleteTag(tag); err != nil {
t.Fatalf("failed to delete tag: %v", err)
} }
} }
func TestListTag(t *testing.T) { func TestListTag(t *testing.T) {
client, err := newRepositoryClient(registryServer.URL, true, credential, handler := test.Handler(&test.Response{
repo, "repository", repo, "pull", "push", "*") Headers: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(fmt.Sprintf("{\"name\": \"%s\",\"tags\": [\"%s\"]}", repository, tag)),
})
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: fmt.Sprintf("/v2/%s/tags/list", repository),
Handler: handler,
})
defer server.Close()
client, err := newRepository(server.URL)
if err != nil { if err != nil {
t.Error(err) t.Fatalf("failed to create client for repository: %v", err)
} }
list, err := client.ListTag() tags, err := client.ListTag()
if err != nil { if err != nil {
t.Error(err) t.Fatalf("failed to list tags: %v", err)
return
}
if len(list) != len(tags.Tags) {
t.Errorf("expected length: %d, actual length: %d", len(tags.Tags), len(list))
return
} }
} if len(tags) != 1 {
t.Fatalf("unexpected length of tags: %d != %d", len(tags), 1)
func TestListTagWithInvalidCredential(t *testing.T) {
credential := auth.NewBasicAuthCredential(username, "wrong_password")
client, err := newRepositoryClient(registryServer.URL, true, credential,
repo, "repository", repo, "pull", "push", "*")
if err != nil {
t.Error(err)
} }
if _, err = client.ListTag(); err != nil { if tags[0] != tag {
e, ok := err.(*registry_error.Error) t.Errorf("unexpected tag: %s != %s", tags[0], tag)
if ok && e.StatusCode == http.StatusUnauthorized {
return
}
t.Error(err)
return
} }
} }
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string, func TestParseError(t *testing.T) {
scopeActions ...string) (*Repository, error) { err := &url.Error{
Err: &registry_error.Error{},
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, true, authorizer)
if err != nil {
return nil, err
} }
e := parseError(err)
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store) if _, ok := e.(*registry_error.Error); !ok {
if err != nil { t.Errorf("error type does not match registry error")
return nil, err
} }
return client, nil }
func newRepository(endpoint string) (*Repository, error) {
return NewRepository(repository, endpoint, &http.Client{})
} }

View File

@ -0,0 +1,60 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"fmt"
"net/http"
"testing"
"github.com/vmware/harbor/utils/test"
)
type simpleModifier struct {
}
func (s *simpleModifier) Modify(req *http.Request) error {
req.Header.Set("Authorization", "token")
return nil
}
func TestRoundTrip(t *testing.T) {
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "GET",
Pattern: "/",
Handler: test.Handler(nil),
})
transport := NewTransport(&http.Transport{}, &simpleModifier{})
client := &http.Client{
Transport: transport,
}
req, err := http.NewRequest("GET", fmt.Sprintf("%s/", server.URL), nil)
if err != nil {
t.Fatalf("failed to create request: %v", err)
}
if _, err := client.Do(req); err != nil {
t.Fatalf("failed to send request: %s", err)
}
header := req.Header.Get("Authorization")
if header != "token" {
t.Errorf("unexpected header: %s != %s", header, "token")
}
}

88
utils/test/test.go Normal file
View File

@ -0,0 +1,88 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package test
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"strings"
)
// RequestHandlerMapping is a mapping between request and its handler
type RequestHandlerMapping struct {
// Method is the method the request used
Method string
// Pattern is the pattern the request must match
Pattern string
// Handler is the handler which handles the request
Handler func(http.ResponseWriter, *http.Request)
}
// ServeHTTP ...
func (rhm *RequestHandlerMapping) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if len(rhm.Method) != 0 && r.Method != strings.ToUpper(rhm.Method) {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
rhm.Handler(w, r)
}
// Response is a response used for unit test
type Response struct {
// StatusCode is the status code of the response
StatusCode int
// Headers are the headers of the response
Headers map[string]string
// Boby is the body of the response
Body []byte
}
// Handler returns a handler function which handle requst according to
// the response provided
func Handler(resp *Response) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if resp == nil {
return
}
for k, v := range resp.Headers {
w.Header().Add(http.CanonicalHeaderKey(k), v)
}
if resp.StatusCode == 0 {
resp.StatusCode = http.StatusOK
}
w.WriteHeader(resp.StatusCode)
if len(resp.Body) != 0 {
io.Copy(w, bytes.NewReader(resp.Body))
}
}
}
// NewServer creates a HTTP server for unit test
func NewServer(mappings ...*RequestHandlerMapping) *httptest.Server {
mux := http.NewServeMux()
for _, mapping := range mappings {
mux.Handle(mapping.Pattern, mapping)
}
return httptest.NewServer(mux)
}