// 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) }) } }