mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
UT for package utils
This commit is contained in:
parent
e0649f6d69
commit
7538bbf066
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
echo "mode: set" >>profile.cov
|
||||
for dir in $(go list ./... | grep -v -E 'vendor|tests')
|
||||
do
|
||||
|
@ -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
61
utils/log/level_test.go
Normal 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")
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(t *testing.T) {
|
||||
}
|
||||
|
156
utils/log/logger_test.go
Normal file
156
utils/log/logger_test.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
56
utils/registry/manifest_test.go
Normal file
56
utils/registry/manifest_test.go
Normal 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)
|
||||
}
|
||||
}
|
@ -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 ...
|
||||
|
150
utils/registry/registry_test.go
Normal file
150
utils/registry/registry_test.go
Normal 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{})
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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: ®istry_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{})
|
||||
}
|
||||
|
60
utils/registry/transport_test.go
Normal file
60
utils/registry/transport_test.go
Normal 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
88
utils/test/test.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user