mirror of https://github.com/goharbor/harbor.git
512 lines
13 KiB
Go
512 lines
13 KiB
Go
// Copyright Project Harbor Authors
|
|
//
|
|
// 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 utils
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParseEndpoint(t *testing.T) {
|
|
cases := []struct {
|
|
input string
|
|
err bool
|
|
expected string
|
|
}{
|
|
{" example.com/ ", false, "http://example.com"},
|
|
{"ftp://example.com", true, ""},
|
|
{"http://example.com", false, "http://example.com"},
|
|
{"https://example.com", false, "https://example.com"},
|
|
{"http://example!@#!?//#", true, ""},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
u, err := ParseEndpoint(c.input)
|
|
if c.err {
|
|
require.NotNil(t, err)
|
|
continue
|
|
}
|
|
require.Nil(t, err)
|
|
assert.Equal(t, c.expected, u.String())
|
|
}
|
|
}
|
|
|
|
func TestParseRepository(t *testing.T) {
|
|
repository := "library/ubuntu"
|
|
project, rest := ParseRepository(repository)
|
|
if project != "library" {
|
|
t.Errorf("unexpected project: %s != %s", project, "library")
|
|
}
|
|
if rest != "ubuntu" {
|
|
t.Errorf("unexpected rest: %s != %s", rest, "ubuntu")
|
|
}
|
|
|
|
repository = "library/test/ubuntu"
|
|
project, rest = ParseRepository(repository)
|
|
if project != "library" {
|
|
t.Errorf("unexpected project: %s != %s", project, "library/test")
|
|
}
|
|
if rest != "test/ubuntu" {
|
|
t.Errorf("unexpected rest: %s != %s", rest, "ubuntu")
|
|
}
|
|
|
|
repository = "ubuntu"
|
|
project, rest = ParseRepository(repository)
|
|
if project != "" {
|
|
t.Errorf("unexpected project: [%s] != [%s]", project, "")
|
|
}
|
|
|
|
if rest != "ubuntu" {
|
|
t.Errorf("unexpected rest: %s != %s", rest, "ubuntu")
|
|
}
|
|
|
|
repository = ""
|
|
project, rest = ParseRepository(repository)
|
|
if project != "" {
|
|
t.Errorf("unexpected project: [%s] != [%s]", project, "")
|
|
}
|
|
|
|
if rest != "" {
|
|
t.Errorf("unexpected rest: [%s] != [%s]", rest, "")
|
|
}
|
|
}
|
|
|
|
func TestEncrypt(t *testing.T) {
|
|
tests := map[string]struct {
|
|
content string
|
|
salt string
|
|
alg string
|
|
want string
|
|
}{
|
|
"sha1 test": {content: "content", salt: "salt", alg: SHA1, want: "dc79e76c88415c97eb089d9cc80b4ab0"},
|
|
"sha256 test": {content: "content", salt: "salt", alg: SHA256, want: "83d3d6f3e7cacb040423adf7ced63d21"},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
got := Encrypt(tc.content, tc.salt, tc.alg)
|
|
if !reflect.DeepEqual(tc.want, got) {
|
|
t.Errorf("%s: expected: %v, got: %v", name, tc.want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReversibleEncrypt(t *testing.T) {
|
|
password := "password"
|
|
key := "1234567890123456"
|
|
encrypted, err := ReversibleEncrypt(password, key)
|
|
if err != nil {
|
|
t.Errorf("Failed to encrypt: %v", err)
|
|
}
|
|
t.Logf("Encrypted password: %s", encrypted)
|
|
if encrypted == password {
|
|
t.Errorf("Encrypted password is identical to the original")
|
|
}
|
|
if !strings.HasPrefix(encrypted, EncryptHeaderV1) {
|
|
t.Errorf("Encrypted password does not have v1 header")
|
|
}
|
|
decrypted, err := ReversibleDecrypt(encrypted, key)
|
|
if err != nil {
|
|
t.Errorf("Failed to decrypt: %v", err)
|
|
}
|
|
if decrypted != password {
|
|
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
|
}
|
|
// Test b64 for backward compatibility
|
|
b64password := base64.StdEncoding.EncodeToString([]byte(password))
|
|
decrypted, err = ReversibleDecrypt(b64password, key)
|
|
if err != nil {
|
|
t.Errorf("Failed to decrypt: %v", err)
|
|
}
|
|
if decrypted != password {
|
|
t.Errorf("decrypted password: %s, is not identical to original", decrypted)
|
|
}
|
|
}
|
|
|
|
func TestGenerateRandomString(t *testing.T) {
|
|
str := GenerateRandomString()
|
|
if len(str) != 32 {
|
|
t.Errorf("unexpected length: %d != %d", len(str), 32)
|
|
}
|
|
str2 := GenerateRandomString()
|
|
if str2 == str {
|
|
t.Errorf("Two identical random strings in a row: %s", str)
|
|
}
|
|
}
|
|
|
|
func TestGenerateRandomStringWithLen(t *testing.T) {
|
|
str := GenerateRandomStringWithLen(16)
|
|
if len(str) != 16 {
|
|
t.Errorf("Failed to generate ramdom string with fixed length.")
|
|
}
|
|
}
|
|
|
|
func TestTestTCPConn(t *testing.T) {
|
|
server := httptest.NewServer(nil)
|
|
defer server.Close()
|
|
addr := strings.TrimPrefix(server.URL, "http://")
|
|
if err := TestTCPConn(addr, 60, 2); err != nil {
|
|
t.Fatalf("failed to test tcp connection of %s: %v", addr, err)
|
|
}
|
|
}
|
|
|
|
func TestParseTimeStamp(t *testing.T) {
|
|
// invalid input
|
|
_, err := ParseTimeStamp("")
|
|
assert.NotNil(t, err)
|
|
|
|
// invalid input
|
|
_, err = ParseTimeStamp("invalid")
|
|
assert.NotNil(t, err)
|
|
|
|
// valid
|
|
now := time.Now().Unix()
|
|
result, err := ParseTimeStamp(strconv.FormatInt(now, 10))
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, now, result.Unix())
|
|
}
|
|
|
|
func TestParseHarborIDOrName(t *testing.T) {
|
|
// nil input
|
|
id, name, err := ParseProjectIDOrName(nil)
|
|
assert.NotNil(t, err)
|
|
|
|
// valid int ID
|
|
id, name, err = ParseProjectIDOrName(1)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(1), id)
|
|
assert.Equal(t, "", name)
|
|
|
|
// valid int64 ID
|
|
id, name, err = ParseProjectIDOrName(int64(1))
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(1), id)
|
|
assert.Equal(t, "", name)
|
|
|
|
// valid name
|
|
id, name, err = ParseProjectIDOrName("project")
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, int64(0), id)
|
|
assert.Equal(t, "project", name)
|
|
}
|
|
|
|
type testingStruct struct {
|
|
Name string
|
|
Count int
|
|
}
|
|
|
|
func TestConvertMapToStruct(t *testing.T) {
|
|
dataMap := make(map[string]interface{})
|
|
dataMap["Name"] = "testing"
|
|
dataMap["Count"] = 100
|
|
|
|
obj := &testingStruct{}
|
|
if err := ConvertMapToStruct(obj, dataMap); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
if obj.Name != "testing" || obj.Count != 100 {
|
|
t.Fail()
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSafeCastString(t *testing.T) {
|
|
type args struct {
|
|
value interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{"nil value", args{nil}, ""},
|
|
{"normal string", args{"sample"}, "sample"},
|
|
{"wrong type", args{12}, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SafeCastString(tt.args.value); got != tt.want {
|
|
t.Errorf("SafeCastString() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafeCastBool(t *testing.T) {
|
|
type args struct {
|
|
value interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{"nil value", args{nil}, false},
|
|
{"normal bool", args{true}, true},
|
|
{"wrong type", args{"true"}, false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SafeCastBool(tt.args.value); got != tt.want {
|
|
t.Errorf("SafeCastBool() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafeCastInt(t *testing.T) {
|
|
type args struct {
|
|
value interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want int
|
|
}{
|
|
{"nil value", args{nil}, 0},
|
|
{"normal int", args{1234}, 1234},
|
|
{"wrong type", args{"sample"}, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SafeCastInt(tt.args.value); got != tt.want {
|
|
t.Errorf("SafeCastInt() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSafeCastFloat64(t *testing.T) {
|
|
type args struct {
|
|
value interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want float64
|
|
}{
|
|
{"nil value", args{nil}, 0},
|
|
{"normal float64", args{12.34}, 12.34},
|
|
{"wrong type", args{false}, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := SafeCastFloat64(tt.args.value); got != tt.want {
|
|
t.Errorf("SafeCastFloat64() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTrimLower(t *testing.T) {
|
|
type args struct {
|
|
str string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{"normal", args{" CN=example,DC=test,DC=com "}, "cn=example,dc=test,dc=com"},
|
|
{"empty", args{" "}, ""},
|
|
{"empty2", args{""}, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := TrimLower(tt.args.str); got != tt.want {
|
|
t.Errorf("TrimLower() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetStrValueOfAnyType(t *testing.T) {
|
|
type args struct {
|
|
value interface{}
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want string
|
|
}{
|
|
{"float", args{float32(1048576.1)}, "1048576.1"},
|
|
{"float", args{float64(1048576.12)}, "1048576.12"},
|
|
{"float", args{1048576.000}, "1048576"},
|
|
{"int", args{1048576}, "1048576"},
|
|
{"int", args{9223372036854775807}, "9223372036854775807"},
|
|
{"string", args{"hello world"}, "hello world"},
|
|
{"bool", args{true}, "true"},
|
|
{"bool", args{false}, "false"},
|
|
{"map", args{map[string]interface{}{"key1": "value1"}}, "{\"key1\":\"value1\"}"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := GetStrValueOfAnyType(tt.args.value); got != tt.want {
|
|
t.Errorf("GetStrValueOfAnyType() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNextSchedule(t *testing.T) {
|
|
curTime := time.Date(2009, time.November, 10, 20, 0, 0, 0, time.UTC)
|
|
expectTime1 := time.Date(2009, time.November, 11, 0, 0, 0, 0, time.UTC)
|
|
expectTime2 := time.Date(2009, time.November, 10, 21, 0, 0, 0, time.UTC)
|
|
expectTime4 := time.Date(2009, time.November, 10, 20, 8, 0, 0, time.UTC)
|
|
|
|
type args struct {
|
|
cron string
|
|
curTime time.Time
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want time.Time
|
|
}{
|
|
{"daily_in_six", args{"0 0 0 * * *", curTime}, expectTime1},
|
|
{"hourly_in_six", args{"0 0 * * * *", curTime}, expectTime2},
|
|
{"custom", args{"0 8 20 * * *", curTime}, expectTime4},
|
|
{"zero time", args{"", curTime}, time.Time{}},
|
|
{"invalid cron", args{"2", curTime}, time.Time{}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equalf(t, tt.want, NextSchedule(tt.args.cron, tt.args.curTime), "NextSchedule(%v, %v)", tt.args.cron, tt.args.curTime)
|
|
})
|
|
}
|
|
}
|
|
|
|
type UserGroupSearchItem struct {
|
|
GroupName string
|
|
}
|
|
|
|
func Test_sortMostMatch(t *testing.T) {
|
|
type args struct {
|
|
input []*UserGroupSearchItem
|
|
matchWord string
|
|
expected []*UserGroupSearchItem
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
}{
|
|
{"normal", args{[]*UserGroupSearchItem{
|
|
{GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
}, "user", []*UserGroupSearchItem{
|
|
{GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
}}},
|
|
{"duplicate_item", args{[]*UserGroupSearchItem{
|
|
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
}, "user", []*UserGroupSearchItem{
|
|
{GroupName: "user"}, {GroupName: "user"}, {GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
}}},
|
|
{"miss_exact_match", args{[]*UserGroupSearchItem{
|
|
{GroupName: "harbor_user"}, {GroupName: "admin_user"}, {GroupName: "users"},
|
|
}, "user", []*UserGroupSearchItem{
|
|
{GroupName: "users"}, {GroupName: "admin_user"}, {GroupName: "harbor_user"},
|
|
}}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
sort.Slice(tt.args.input, func(i, j int) bool {
|
|
return MostMatchSorter(tt.args.input[i].GroupName, tt.args.input[j].GroupName, tt.args.matchWord)
|
|
})
|
|
assert.True(t, reflect.DeepEqual(tt.args.input, tt.args.expected))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateCronString(t *testing.T) {
|
|
testCases := []struct {
|
|
description string
|
|
input string
|
|
hasErr bool
|
|
}{
|
|
// empty cron string
|
|
{
|
|
description: "test case 1",
|
|
input: "",
|
|
hasErr: true,
|
|
},
|
|
|
|
// invalid cron format
|
|
{
|
|
description: "test case 2",
|
|
input: "0 2 3",
|
|
hasErr: true,
|
|
},
|
|
|
|
// the 1st field (indicating Seconds of time) of the cron setting must be 0
|
|
{
|
|
description: "test case 3",
|
|
input: "1 0 0 1 1 0",
|
|
hasErr: true,
|
|
},
|
|
|
|
// valid cron string
|
|
{
|
|
description: "test case 4",
|
|
input: "0 1 2 1 1 *",
|
|
hasErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
err := ValidateCronString(tc.input)
|
|
if tc.hasErr {
|
|
if err == nil {
|
|
t.Errorf("%s, expect having error, while actual error returned is nil", tc.description)
|
|
}
|
|
} else {
|
|
// tc.hasErr == false
|
|
if err != nil {
|
|
t.Errorf("%s, expect having no error, while actual error returned is not nil, err=%v", tc.description, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsLocalPath(t *testing.T) {
|
|
type args struct {
|
|
path string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want bool
|
|
}{
|
|
{"normal test", args{"/harbor/project"}, true},
|
|
{"failed", args{"www.myexample.com"}, false},
|
|
{"other_site1", args{"//www.myexample.com"}, false},
|
|
{"other_site2", args{"https://www.myexample.com"}, false},
|
|
{"other_site", args{"http://www.myexample.com"}, false},
|
|
{"empty_path", args{""}, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equalf(t, tt.want, IsLocalPath(tt.args.path), "IsLocalPath(%v)", tt.args.path)
|
|
})
|
|
}
|
|
}
|