Files
Sylve/pkg/utils/strings_test.go
T

949 lines
19 KiB
Go

// SPDX-License-Identifier: BSD-2-Clause
//
// Copyright (c) 2025 The FreeBSD Foundation.
//
// This software was developed by Hayzam Sherif <hayzam@alchemilla.io>
// of Alchemilla Ventures Pvt. Ltd. <hello@alchemilla.io>,
// under sponsorship from the FreeBSD Foundation.
package utils
import (
"fmt"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/golang-jwt/jwt/v4"
)
func TestFNVHash(t *testing.T) {
tests := []struct {
input string
expected uint64
}{
{
input: "hello",
expected: FNVHash("hello"),
},
{
input: "world",
expected: FNVHash("world"),
},
{
input: "",
expected: FNVHash(""),
},
{
input: "12345",
expected: FNVHash("12345"),
},
{
input: "special@chars!",
expected: FNVHash("special@chars!"),
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := FNVHash(tt.input)
if result != tt.expected {
t.Errorf("FNVHash(%q) = %d; want %d", tt.input, result, tt.expected)
}
})
}
}
func TestHashAndCheckPassword(t *testing.T) {
password := "supersecret123"
hash, err := HashPassword(password)
if err != nil {
t.Fatalf("HashPassword failed: %v", err)
}
if hash == "" {
t.Fatal("expected non-empty hash")
}
if !CheckPasswordHash(password, hash) {
t.Error("CheckPasswordHash failed for correct password")
}
if CheckPasswordHash("wrongpassword", hash) {
t.Error("CheckPasswordHash returned true for incorrect password")
}
}
func TestSHA256(t *testing.T) {
tests := []struct {
input string
count int
expected string
}{
{
input: "hello",
count: 1,
expected: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
},
{
input: "hello",
count: 2,
expected: "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50",
},
{
input: "",
count: 1,
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
input: "test",
count: 0,
expected: "test",
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%s,count=%d", tt.input, tt.count), func(t *testing.T) {
result := SHA256(tt.input, tt.count)
if result != tt.expected {
t.Errorf("SHA256(%q, %d) = %q; want %q", tt.input, tt.count, result, tt.expected)
}
})
}
}
func TestRemoveSpaces(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "hello world",
expected: "helloworld",
},
{
input: " leading spaces",
expected: "leadingspaces",
},
{
input: "trailing spaces ",
expected: "trailingspaces",
},
{
input: " spaces in between ",
expected: "spacesinbetween",
},
{
input: "nospace",
expected: "nospace",
},
{
input: "",
expected: "",
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := RemoveSpaces(tt.input)
if result != tt.expected {
t.Errorf("RemoveSpaces(%q) = %q; want %q", tt.input, result, tt.expected)
}
})
}
}
func TestStringToUintId(t *testing.T) {
tests := []struct {
input string
expected uint
}{
{
input: "hello",
expected: uint(FNVHash("hello")),
},
{
input: "world",
expected: uint(FNVHash("world")),
},
{
input: "",
expected: uint(FNVHash("")),
},
{
input: "12345",
expected: uint(FNVHash("12345")),
},
{
input: "special@chars!",
expected: uint(FNVHash("special@chars!")),
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := StringToUintId(tt.input)
if result != tt.expected {
t.Errorf("StringToUintId(%q) = %d; want %d", tt.input, result, tt.expected)
}
})
}
}
func TestGenerateRandomUUID(t *testing.T) {
uuids := make(map[string]bool)
for i := 0; i < 100; i++ {
uuid := GenerateRandomUUID()
if _, exists := uuids[uuid]; exists {
t.Errorf("Duplicate UUID generated: %s", uuid)
}
uuids[uuid] = true
if len(uuid) == 0 {
t.Error("Generated UUID is empty")
}
}
}
func TestGenerateRandomString(t *testing.T) {
tests := []struct {
length int
}{
{length: 0},
{length: 1},
{length: 5},
{length: 10},
{length: 50},
{length: 100},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("length=%d", tt.length), func(t *testing.T) {
result := GenerateRandomString(tt.length)
if len(result) != tt.length {
t.Errorf("GenerateRandomString(%d) = %q; length = %d; want length = %d", tt.length, result, len(result), tt.length)
}
for _, char := range result {
if !strings.ContainsRune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", char) {
t.Errorf("GenerateRandomString(%d) generated invalid character: %q", tt.length, char)
}
}
})
}
t.Run("randomness", func(t *testing.T) {
length := 10
generatedStrings := make(map[string]bool)
for i := 0; i < 100; i++ {
str := GenerateRandomString(length)
if generatedStrings[str] {
t.Errorf("Duplicate string generated: %q", str)
}
generatedStrings[str] = true
}
})
}
func TestStringInSlice(t *testing.T) {
tests := []struct {
element string
list []string
expected bool
}{
{
element: "apple",
list: []string{"apple", "banana", "cherry"},
expected: true,
},
{
element: "grape",
list: []string{"apple", "banana", "cherry"},
expected: false,
},
{
element: "",
list: []string{"apple", "banana", "cherry"},
expected: false,
},
{
element: "banana",
list: []string{},
expected: false,
},
{
element: "orange",
list: []string{"orange"},
expected: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("element=%q,list=%v", tt.element, tt.list), func(t *testing.T) {
result := StringInSlice(tt.element, tt.list)
if result != tt.expected {
t.Errorf("StringInSlice(%q, %v) = %v; want %v", tt.element, tt.list, result, tt.expected)
}
})
}
}
func TestStringToUint64(t *testing.T) {
tests := []struct {
input string
expected uint64
}{
{
input: "12345",
expected: 12345,
},
{
input: "0",
expected: 0,
},
{
input: "18446744073709551615",
expected: 18446744073709551615,
},
{
input: "",
expected: 0,
},
{
input: "notanumber",
expected: 0,
},
{
input: "-12345",
expected: 0,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := StringToUint64(tt.input)
if result != tt.expected {
t.Errorf("StringToUint64(%q) = %d; want %d", tt.input, result, tt.expected)
}
})
}
}
func TestStringToFloat64(t *testing.T) {
tests := []struct {
input string
expected float64
}{
{
input: "123.45",
expected: 123.45,
},
{
input: "0",
expected: 0,
},
{
input: "-987.65",
expected: -987.65,
},
{
input: "1e3",
expected: 1000,
},
{
input: "",
expected: 0,
},
{
input: "notanumber",
expected: 0,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := StringToFloat64(tt.input)
if result != tt.expected {
t.Errorf("StringToFloat64(%q) = %f; want %f", tt.input, result, tt.expected)
}
})
}
}
func TestRemoveEmptyLines(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "line1\n\nline2\n\nline3\n",
expected: "line1\nline2\nline3\n",
},
{
input: "\n\n\nline1\n\nline2\n\n",
expected: "line1\nline2\n",
},
{
input: "line1\nline2\nline3",
expected: "line1\nline2\nline3",
},
{
input: "\n\n\n",
expected: "",
},
{
input: "",
expected: "",
},
{
input: " \n\nline1\n \n\nline2\n",
expected: " \nline1\n \nline2\n",
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := RemoveEmptyLines(tt.input)
if result != tt.expected {
t.Errorf("RemoveEmptyLines(%q) = %q; want %q", tt.input, result, tt.expected)
}
})
}
}
func TestParseJWT(t *testing.T) {
claims := jwt.MapClaims{
"username": "testuser",
"role": "admin",
"exp": time.Now().Add(time.Hour).Unix(),
"jti": "some-jti",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("test-secret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
t.Run("valid token", func(t *testing.T) {
result, err := ParseJWT(tokenString)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
parsedClaims, ok := result.(map[string]interface{})
if !ok {
t.Fatalf("expected map[string]interface{}, got %T", result)
}
if parsedClaims["username"] != "testuser" {
t.Errorf("expected username 'testuser', got %v", parsedClaims["username"])
}
if parsedClaims["role"] != "admin" {
t.Errorf("expected role 'admin', got %v", parsedClaims["role"])
}
if _, exists := parsedClaims["exp"]; exists {
t.Errorf("expected 'exp' to be removed, but it exists")
}
if _, exists := parsedClaims["jti"]; exists {
t.Errorf("expected 'jti' to be removed, but it exists")
}
})
t.Run("invalid token format", func(t *testing.T) {
_, err := ParseJWT("thisisnot.valid.jwt")
if err == nil {
t.Error("expected error for invalid JWT format, got nil")
}
})
t.Run("not a JWT", func(t *testing.T) {
_, err := ParseJWT("notatoken")
if err == nil {
t.Error("expected error for malformed token, got nil")
}
})
}
func TestBytesToSize(t *testing.T) {
tests := []struct {
toType string
bytes float64
expected float64
}{
{
toType: "KB",
bytes: 1024,
expected: 1,
},
{
toType: "MB",
bytes: 1048576,
expected: 1,
},
{
toType: "GB",
bytes: 1073741824,
expected: 1,
},
{
toType: "TB",
bytes: 1099511627776,
expected: 1,
},
{
toType: "KB",
bytes: 2048,
expected: 2,
},
{
toType: "MB",
bytes: 2097152,
expected: 2,
},
{
toType: "GB",
bytes: 2147483648,
expected: 2,
},
{
toType: "TB",
bytes: 2199023255552,
expected: 2,
},
{
toType: "unknown",
bytes: 12345,
expected: 12345,
},
{
toType: "",
bytes: 12345,
expected: 12345,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("toType=%q,bytes=%f", tt.toType, tt.bytes), func(t *testing.T) {
result := BytesToSize(tt.toType, tt.bytes)
if result != tt.expected {
t.Errorf("BytesToSize(%q, %f) = %f; want %f", tt.toType, tt.bytes, result, tt.expected)
}
})
}
}
func TestUnescapeFilepath(t *testing.T) {
tests := []struct {
input string
expected string
hasError bool
}{
{
input: "normal/path",
expected: "normal/path",
hasError: false,
},
{
input: "escaped\\040path",
expected: "escaped path",
hasError: false,
},
{
input: "multiple\\040escaped\\041chars",
expected: "multiple escaped!chars",
hasError: false,
},
{
input: "\\011tab\\012newline",
expected: "\ttab\nnewline",
hasError: false,
},
{
input: "invalid\\08code",
expected: "",
hasError: true,
},
{
input: "short\\04",
expected: "",
hasError: true,
},
{
input: "",
expected: "",
hasError: false,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result, err := UnescapeFilepath(tt.input)
if (err != nil) != tt.hasError {
t.Errorf("UnescapeFilepath(%q) error = %v; want error = %v", tt.input, err != nil, tt.hasError)
}
if result != tt.expected {
t.Errorf("UnescapeFilepath(%q) = %q; want %q", tt.input, result, tt.expected)
}
})
}
}
func TestHumanFormatToSize(t *testing.T) {
tests := []struct {
name string
input string
expected uint64
}{
{"Bytes", "123", 123},
{"Bytes with B", "123B", 123},
{"Bytes with b", "123b", 123},
{"Kilobytes", "1KB", 1 << 10},
{"Kilobytes lowercase", "1kb", 1 << 10},
{"Megabytes", "1MB", 1 << 20},
{"Gigabytes", "1GB", 1 << 30},
{"Terabytes", "1TB", 1 << 40},
{"Petabytes", "1PB", 1 << 50},
{"Decimal KB", "1.5KB", (1 << 10) + (1 << 9)},
{"Decimal MB", "0.5MB", 1 << 19},
{"Small decimal", "0.0001GB", 107374},
{"Space before unit", "1 KB", 1 << 10},
{"Multiple spaces", "1 MB", 1 << 20},
{"Tab separator", "1\tGB", 1 << 30},
{"Zero", "0", 0},
{"Zero with unit", "0GB", 0},
{"Large number", "1000000000000000000", 1000000000000000000},
{"Max uint64", "18446744073709551615", ^uint64(0)},
{"Empty string", "", 0},
{"Only spaces", " ", 0},
{"Invalid unit", "1XB", 0},
{"Negative number", "-1KB", 0},
{"Invalid format", "KB", 0},
{"Number too large", "18446744073709551616", ^uint64(0)},
{"Number way too large", "1e100PB", ^uint64(0)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := HumanFormatToSize(tt.input); got != tt.expected {
t.Errorf("HumanFormatToSize(%q) = %v, want %v", tt.input, got, tt.expected)
}
})
}
}
func TestIsIndented(t *testing.T) {
tests := []struct {
input string
expected bool
}{
{
input: " indented line",
expected: true,
},
{
input: "\tindented with tab",
expected: true,
},
{
input: "not indented",
expected: false,
},
{
input: "",
expected: false,
},
{
input: " \t mixed spaces and tab",
expected: true,
},
{
input: "\nnewline character",
expected: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
result := IsIndented(tt.input)
if result != tt.expected {
t.Errorf("IsIndented(%q) = %v; want %v", tt.input, result, tt.expected)
}
})
}
}
func TestContains(t *testing.T) {
tests := []struct {
slice []string
val string
expected bool
}{
{
slice: []string{"apple", "banana", "cherry"},
val: "banana",
expected: true,
},
{
slice: []string{"apple", "banana", "cherry"},
val: "grape",
expected: false,
},
{
slice: []string{},
val: "apple",
expected: false,
},
{
slice: []string{"apple", "banana", "cherry"},
val: "",
expected: false,
},
{
slice: []string{"", "banana", "cherry"},
val: "",
expected: true,
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("slice=%v,val=%q", tt.slice, tt.val), func(t *testing.T) {
result := Contains(tt.slice, tt.val)
if result != tt.expected {
t.Errorf("Contains(%v, %q) = %v; want %v", tt.slice, tt.val, result, tt.expected)
}
})
}
}
func TestEncodeBase62Basic(t *testing.T) {
tests := []struct {
num uint64
length int
want string
}{
{0, 1, "0"},
{1, 1, "1"},
{61, 1, "z"},
{62, 2, "10"},
{3843, 2, "zz"},
{238327, 3, "zzz"},
{238328, 4, "1000"},
}
for _, tt := range tests {
got := EncodeBase62(tt.num, tt.length)
if got != tt.want {
t.Errorf("EncodeBase62(%d, %d) = %s; want %s", tt.num, tt.length, got, tt.want)
}
}
}
func TestEncodeBase62Padding(t *testing.T) {
got := EncodeBase62(0, 5)
if got != "00000" {
t.Errorf("Expected zero-padded result '00000', got %s", got)
}
}
func TestEncodeBase62TruncationRisk(t *testing.T) {
got := EncodeBase62(3843, 1)
if got == "z" {
t.Logf("Warning: result truncated to %q due to insufficient length", got)
} else if len(got) != 1 {
t.Errorf("Expected length 1 result, got length %d", len(got))
}
}
func TestShortHashDeterministic(t *testing.T) {
input := "freebsd"
hash1 := ShortHash(input)
hash2 := ShortHash(input)
if hash1 != hash2 {
t.Errorf("ShortHash should be deterministic, got %s and %s", hash1, hash2)
}
}
func TestShortHashLength(t *testing.T) {
input := "any input string"
hash := ShortHash(input)
if len(hash) != 8 {
t.Errorf("Expected hash length of 8, got %d", len(hash))
}
}
func TestShortHashUniqueness(t *testing.T) {
hash1 := ShortHash("input1")
hash2 := ShortHash("input2")
if hash1 == hash2 {
t.Errorf("Expected different hashes for different inputs, got same: %s", hash1)
}
}
func TestJoinStringsEmptySlice(t *testing.T) {
result := JoinStrings([]string{}, ",")
if result != "" {
t.Errorf("Expected empty string for empty slice, got %q", result)
}
}
func TestJoinStringsSingleElement(t *testing.T) {
result := JoinStrings([]string{"hello"}, ",")
if result != "hello" {
t.Errorf("Expected 'hello', got %q", result)
}
}
func TestJoinStringsMultipleElements(t *testing.T) {
result := JoinStrings([]string{"a", "b", "c"}, "-")
expected := "a-b-c"
if result != expected {
t.Errorf("Expected %q, got %q", expected, result)
}
}
func TestJoinStringsCustomSeparator(t *testing.T) {
result := JoinStrings([]string{"1", "2", "3"}, " | ")
expected := "1 | 2 | 3"
if result != expected {
t.Errorf("Expected %q, got %q", expected, result)
}
}
func TestMapKeysEmpty(t *testing.T) {
m := map[string]struct{}{}
keys := MapKeys(m)
if len(keys) != 0 {
t.Errorf("Expected 0 keys, got %d", len(keys))
}
}
func TestMapKeysSingle(t *testing.T) {
m := map[string]struct{}{"one": {}}
keys := MapKeys(m)
if len(keys) != 1 || keys[0] != "one" {
t.Errorf("Expected [\"one\"], got %v", keys)
}
}
func TestMapKeysMultiple(t *testing.T) {
m := map[string]struct{}{
"a": {},
"b": {},
"c": {},
}
keys := MapKeys(m)
sort.Strings(keys)
expected := []string{"a", "b", "c"}
if !reflect.DeepEqual(keys, expected) {
t.Errorf("Expected %v, got %v", expected, keys)
}
}
func TestIsValidVMName(t *testing.T) {
valid := []string{"vm1", "my-vm", "VM_123", "a", "A-B_C"}
invalid := []string{"vm!", "my.vm", "name with space", "💻", ""}
for _, name := range valid {
if !IsValidVMName(name) {
t.Errorf("Expected valid VM name: %q", name)
}
}
for _, name := range invalid {
if IsValidVMName(name) {
t.Errorf("Expected invalid VM name: %q", name)
}
}
}
func TestIsValidMACAddress(t *testing.T) {
valid := []string{
"00:1A:2B:3C:4D:5E",
"aa:bb:cc:dd:ee:ff",
"AA-BB-CC-DD-EE-FF",
}
invalid := []string{
"001A:2B:3C:4D:5E",
"00:1A:2B:3C:4D:5E:6F",
"GG:HH:II:JJ:KK:LL",
"00-1A-2B-3C-4D",
"123456",
"",
}
for _, mac := range valid {
if !IsValidMACAddress(mac) {
t.Errorf("Expected valid MAC address: %q", mac)
}
}
for _, mac := range invalid {
if IsValidMACAddress(mac) {
t.Errorf("Expected invalid MAC address: %q", mac)
}
}
}
func TestGenerateRandomMAC(t *testing.T) {
for i := 0; i < 5; i++ {
mac := GenerateRandomMAC()
if mac == "" {
t.Fatal("GenerateRandomMAC returned empty string")
}
if !IsValidMACAddress(mac) {
t.Errorf("Generated MAC is not valid: %s", mac)
}
}
}
func TestIsHexValid(t *testing.T) {
valid := []string{
"abc123",
"ABCDEF",
"0123456789abcdef",
"deadBEEF",
"CAFEBABE",
"0",
"f",
}
for _, s := range valid {
if !IsHex(s) {
t.Errorf("Expected IsHex(%q) to be true", s)
}
}
}
func TestIsHexInvalid(t *testing.T) {
invalid := []string{
"xyz123",
"hello",
"123g",
"ABCXYZ",
"abc-123",
"12 34",
"💀dead",
"",
}
for _, s := range invalid {
if IsHex(s) {
t.Errorf("Expected IsHex(%q) to be false", s)
}
}
}
func TestIsHexEmpty(t *testing.T) {
if IsHex("") {
t.Error("Expected IsHex(\"\") to be true (empty string is valid hex)")
}
}