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
set -e
echo "mode: set" >>profile.cov
for dir in $(go list ./... | grep -v -E 'vendor|tests')
do

View File

@ -17,6 +17,7 @@ package log
import (
"fmt"
"strings"
)
// Level ...
@ -56,7 +57,7 @@ func (l Level) string() (lvl string) {
func parseLevel(lvl string) (level Level, err error) {
switch lvl {
switch strings.ToLower(lvl) {
case "debug":
level = DebugLevel
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 (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/docker/distribution/registry/client/auth"
"github.com/vmware/harbor/utils/test"
)
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()
_, err := NewAuthorizerStore(server.URL, false, nil)
@ -76,16 +87,3 @@ func TestModify(t *testing.T) {
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
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
var (
token = "token"
"github.com/vmware/harbor/utils/test"
)
func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
tokenServer := newTokenServer()
defer tokenServer.Close()
handler := test.Handler(&test.Response{
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")
req, err := http.NewRequest("GET", "http://registry", nil)
@ -38,7 +47,7 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
}
params := map[string]string{
"realm": tokenServer.URL + "/token",
"realm": server.URL + "/token",
}
if err := authorizer.Authorize(req, params); err != nil {
@ -46,8 +55,8 @@ func TestAuthorizeOfStandardTokenAuthorizer(t *testing.T) {
}
tk := req.Header.Get("Authorization")
if tk != "Bearer "+token {
t.Errorf("unexpected token: %s != %s", tk, "Bearer "+token)
if 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
func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modifier) (*Registry, error) {
u, err := utils.ParseEndpoint(endpoint)
if err != nil {
return nil, err
}
t := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
@ -61,12 +56,9 @@ func NewRegistryWithModifiers(endpoint string, insecure bool, modifiers ...Modif
transport := NewTransport(t, modifiers...)
return &Registry{
Endpoint: u,
client: &http.Client{
Transport: transport,
},
}, nil
return NewRegistry(endpoint, &http.Client{
Transport: transport,
})
}
// 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
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{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: insecure,
@ -76,13 +69,9 @@ func NewRepositoryWithModifiers(name, endpoint string, insecure bool, modifiers
transport := NewTransport(t, modifiers...)
return &Repository{
Name: name,
Endpoint: u,
client: &http.Client{
Transport: transport,
},
}, nil
return NewRepository(name, endpoint, &http.Client{
Transport: transport,
})
}
func parseError(err error) error {
@ -347,7 +336,7 @@ func (r *Repository) PullBlob(digest string) (size int64, data io.ReadCloser, er
data = resp.Body
return
}
// can not close the connect if the status code is 200
defer resp.Body.Close()
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 {
return err
}
return r.monolithicBlobUpload(location, digest, size, data)
}
@ -482,5 +470,12 @@ func buildInitiateBlobUploadURL(endpoint, repoName 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
import (
"encoding/json"
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"net/url"
"strconv"
"strings"
"testing"
"time"
"github.com/vmware/harbor/utils/registry/auth"
"github.com/docker/distribution/manifest/schema2"
registry_error "github.com/vmware/harbor/utils/registry/error"
"github.com/vmware/harbor/utils/test"
)
var (
username = "user"
password = "P@ssw0rd"
repo = "samalba/my-app"
tags = tagResp{Tags: []string{"1.0", "2.0", "3.0"}}
validToken = "valid_token"
invalidToken = "invalid_token"
credential auth.Credential
registryServer *httptest.Server
tokenServer *httptest.Server
repositoryClient *Repository
repository = "library/hello-world"
tag = "latest"
mediaType = schema2.MediaTypeManifest
manifest = []byte("manifest")
blob = []byte("blob")
uuid = "0663ff44-63bb-11e6-8b77-86f30ca893d3"
digest = "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"
)
type tagResp struct {
Tags []string `json:"tags"`
}
func TestMain(m *testing.M) {
//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 TestNewRepositoryWithModifiers(t *testing.T) {
_, err := NewRepositoryWithModifiers("library/ubuntu",
"http://registry.org", true, nil)
if err != nil {
t.Fatalf("failed to create client for repository: %v", err)
}
}
func serveTaglisting(w http.ResponseWriter, r *http.Request) {
if !isTokenValid(r) {
challenge(w)
return
}
if err := json.NewEncoder(w).Encode(tags); err != nil {
w.Write([]byte(err.Error()))
w.WriteHeader(http.StatusInternalServerError)
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
func TestBlobExist(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
dgt := path[strings.LastIndex(path, "/")+1 : len(path)]
if dgt == digest {
w.Header().Add(http.CanonicalHeaderKey("Content-Length"), strconv.Itoa(len(blob)))
w.Header().Add(http.CanonicalHeaderKey("Docker-Content-Digest"), digest)
w.Header().Add(http.CanonicalHeaderKey("Content-Type"), "application/octet-stream")
return
}
}
return valid
}
func challenge(w http.ResponseWriter) {
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
w.WriteHeader(http.StatusNotFound)
}
result := make(map[string]interface{})
result["token"] = validToken
result["expires_in"] = 300
result["issued_at"] = time.Now().Format(time.RFC3339)
server := test.NewServer(
&test.RequestHandlerMapping{
Method: "HEAD",
Pattern: fmt.Sprintf("/v2/%s/blobs/", repository),
Handler: handler,
})
defer server.Close()
encoder := json.NewEncoder(w)
if err := encoder.Encode(result); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
client, err := newRepository(server.URL)
if err != nil {
err = parseError(err)
t.Fatalf("failed to create client for repository: %v", err)
}
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) {
client, err := newRepositoryClient(registryServer.URL, true, credential,
repo, "repository", repo, "pull", "push", "*")
handler := test.Handler(&test.Response{
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 {
t.Error(err)
t.Fatalf("failed to create client for repository: %v", err)
}
list, err := client.ListTag()
tags, err := client.ListTag()
if err != nil {
t.Error(err)
return
}
if len(list) != len(tags.Tags) {
t.Errorf("expected length: %d, actual length: %d", len(tags.Tags), len(list))
return
t.Fatalf("failed to list tags: %v", err)
}
}
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 len(tags) != 1 {
t.Fatalf("unexpected length of tags: %d != %d", len(tags), 1)
}
if _, err = client.ListTag(); err != nil {
e, ok := err.(*registry_error.Error)
if ok && e.StatusCode == http.StatusUnauthorized {
return
}
t.Error(err)
return
if tags[0] != tag {
t.Errorf("unexpected tag: %s != %s", tags[0], tag)
}
}
func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string,
scopeActions ...string) (*Repository, error) {
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...)
store, err := auth.NewAuthorizerStore(endpoint, true, authorizer)
if err != nil {
return nil, err
func TestParseError(t *testing.T) {
err := &url.Error{
Err: &registry_error.Error{},
}
client, err := NewRepositoryWithModifiers(repository, endpoint, insecure, store)
if err != nil {
return nil, err
e := parseError(err)
if _, ok := e.(*registry_error.Error); !ok {
t.Errorf("error type does not match registry error")
}
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)
}