package util import ( "encoding/base64" "strings" "testing" ) // ---------- token.go ---------- func TestSHA256Hex(t *testing.T) { // Known SHA-256 of empty string got := SHA256Hex("") want := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" if got != want { t.Errorf("SHA256Hex(\"\") = %q, want %q", got, want) } // Known SHA-256 of "hello" got = SHA256Hex("hello") want = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" if got != want { t.Errorf("SHA256Hex(\"hello\") = %q, want %q", got, want) } // Different inputs produce different hashes if SHA256Hex("a") == SHA256Hex("b") { t.Error("different inputs should produce different hashes") } } func TestIsTokenValid(t *testing.T) { tests := []struct { name string input string secret string want bool }{ {"correct", "mytoken", "mytoken", true}, {"wrong", "wrong", "mytoken", false}, {"empty input", "", "mytoken", false}, {"empty secret", "mytoken", "", false}, {"both empty", "", "", false}, {"case sensitive", "MyToken", "mytoken", false}, {"long token", strings.Repeat("a", 1000), strings.Repeat("a", 1000), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsTokenValid(tt.input, tt.secret); got != tt.want { t.Errorf("IsTokenValid(%q, %q) = %v, want %v", tt.input, tt.secret, got, tt.want) } }) } } func TestIsGrantTokenValid(t *testing.T) { // Use the stored hash of a token secret := "mygranttoken" storedHash := SHA256Hex(secret) tests := []struct { name string input string storedHash string want bool }{ {"correct", secret, storedHash, true}, {"wrong", "wrong", storedHash, false}, {"empty input", "", storedHash, false}, {"empty storedHash", secret, "", false}, {"both empty", "", "", false}, {"garbage hash", secret, "garbage", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsGrantTokenValid(tt.input, tt.storedHash); got != tt.want { t.Errorf("IsGrantTokenValid(%q, %q) = %v, want %v", tt.input, tt.storedHash, got, tt.want) } }) } } func TestRandomToken(t *testing.T) { tok, err := RandomToken() if err != nil { t.Fatalf("RandomToken() error: %v", err) } if tok == "" { t.Error("RandomToken() returned empty string") } // Should be base64 RawURL (no padding), 32 chars for 24 bytes if len(tok) != 32 { t.Errorf("RandomToken() length = %d, want 32", len(tok)) } if strings.Contains(tok, "=") { t.Error("RandomToken() should not contain padding") } // Two calls should produce different tokens (probabilistically) tok2, _ := RandomToken() if tok == tok2 { t.Error("two RandomToken() calls produced same value") } // Should decode back to 24 bytes b, err := base64.RawURLEncoding.DecodeString(tok) if err != nil { t.Fatalf("RandomToken() not valid base64 raw url: %v", err) } if len(b) != 24 { t.Errorf("decoded token length = %d, want 24", len(b)) } } func TestEncodeDecodeBase64(t *testing.T) { input := "Hello, 世界!" // Std encoded := EncodeBase64Std(input) decoded, err := DecodeBase64Std(encoded) if err != nil { t.Fatalf("DecodeBase64Std error: %v", err) } if decoded != input { t.Errorf("Std round-trip failed: got %q, want %q", decoded, input) } // URL (with padding) encoded = EncodeBase64URL(input) decoded, err = DecodeBase64URL(encoded) if err != nil { t.Fatalf("DecodeBase64URL error: %v", err) } if decoded != input { t.Errorf("URL round-trip failed: got %q, want %q", decoded, input) } // RawURL (no padding) encoded = EncodeBase64RawURL(input) decoded, err = DecodeBase64RawURL(encoded) if err != nil { t.Fatalf("DecodeBase64RawURL error: %v", err) } if decoded != input { t.Errorf("RawURL round-trip failed: got %q, want %q", decoded, input) } if strings.Contains(encoded, "=") { t.Error("EncodeBase64RawURL should not produce padding") } } func TestDecodeBase64StdInvalid(t *testing.T) { if _, err := DecodeBase64Std("!!!invalid"); err == nil { t.Error("expected error for invalid base64 std") } } func TestDecodeBase64URLInvalid(t *testing.T) { if _, err := DecodeBase64URL("!!!invalid"); err == nil { t.Error("expected error for invalid base64 url") } } func TestDecodeBase64RawURLInvalid(t *testing.T) { if _, err := DecodeBase64RawURL("!!!invalid"); err == nil { t.Error("expected error for invalid base64 raw url") } } func TestDecodeBase64Auto(t *testing.T) { input := "Hello World" // std encoding std := base64.StdEncoding.EncodeToString([]byte(input)) got, err := DecodeBase64Auto(std) if err != nil { t.Fatalf("DecodeBase64Auto(std) error: %v", err) } if got != input { t.Errorf("DecodeBase64Auto(std) = %q, want %q", got, input) } // raw url encoding rawURL := base64.RawURLEncoding.EncodeToString([]byte(input)) got, err = DecodeBase64Auto(rawURL) if err != nil { t.Fatalf("DecodeBase64Auto(rawURL) error: %v", err) } if got != input { t.Errorf("DecodeBase64Auto(rawURL) = %q, want %q", got, input) } // url encoding with padding urlEnc := base64.URLEncoding.EncodeToString([]byte(input)) got, err = DecodeBase64Auto(urlEnc) if err != nil { t.Fatalf("DecodeBase64Auto(url) error: %v", err) } if got != input { t.Errorf("DecodeBase64Auto(url) = %q, want %q", got, input) } // whitespace trimming got, err = DecodeBase64Auto(" " + std + " ") if err != nil { t.Fatalf("DecodeBase64Auto(trimmed) error: %v", err) } if got != input { t.Errorf("DecodeBase64Auto(trimmed) = %q, want %q", got, input) } // invalid if _, err := DecodeBase64Auto("!!!not base64 at all"); err == nil { t.Error("expected error for invalid base64 in Auto") } } func TestBase64Utf8(t *testing.T) { input := "Hello, 世界!" got := Base64Utf8(input) want := base64.StdEncoding.EncodeToString([]byte(input)) if got != want { t.Errorf("Base64Utf8(%q) = %q, want %q", input, got, want) } // round trip decoded, err := base64.StdEncoding.DecodeString(got) if err != nil { t.Fatalf("decode error: %v", err) } if string(decoded) != input { t.Errorf("round-trip failed: got %q, want %q", string(decoded), input) } } // ---------- path.go ---------- func TestGetByPath(t *testing.T) { nested := map[string]any{ "ws-opts": map[string]any{ "headers": map[string]any{ "Host": "example.com", }, "path": "/ws", }, "server": "1.2.3.4", "port": 443, } tests := []struct { name string path string want any }{ {"top-level string", "server", "1.2.3.4"}, {"top-level int", "port", 443}, {"nested 2 levels", "ws-opts.headers.Host", "example.com"}, {"nested 1 level", "ws-opts.path", "/ws"}, {"missing key", "nonexistent", nil}, {"missing nested", "ws-opts.nonexistent", nil}, {"missing deep", "a.b.c", nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := GetByPath(nested, tt.path) if got != tt.want { t.Errorf("GetByPath(%q) = %v, want %v", tt.path, got, tt.want) } }) } // non-map intermediate value m := map[string]any{"server": "1.2.3.4"} if got := GetByPath(m, "server.sub"); got != nil { t.Errorf("GetByPath on non-map intermediate = %v, want nil", got) } } func TestSetByPath(t *testing.T) { t.Run("top-level new", func(t *testing.T) { m := map[string]any{} result := SetByPath(m, "key", "value") if result["key"] != "value" { t.Errorf("SetByPath failed: got %v", result["key"]) } }) t.Run("nested new creates intermediate maps", func(t *testing.T) { m := map[string]any{} result := SetByPath(m, "a.b.c", "deep") // navigate a := result["a"].(map[string]any) b := a["b"].(map[string]any) if b["c"] != "deep" { t.Errorf("nested set failed: got %v", b["c"]) } }) t.Run("overwrite existing", func(t *testing.T) { m := map[string]any{"key": "old"} result := SetByPath(m, "key", "new") if result["key"] != "new" { t.Errorf("overwrite failed: got %v", result["key"]) } }) t.Run("overwrite nested existing", func(t *testing.T) { m := map[string]any{ "ws-opts": map[string]any{"path": "/old"}, } result := SetByPath(m, "ws-opts.path", "/new") ws := result["ws-opts"].(map[string]any) if ws["path"] != "/new" { t.Errorf("nested overwrite failed: got %v", ws["path"]) } }) t.Run("preserve existing sibling keys", func(t *testing.T) { m := map[string]any{ "ws-opts": map[string]any{"path": "/ws", "headers": map[string]any{"Host": "h"}}, } result := SetByPath(m, "ws-opts.newkey", "newval") ws := result["ws-opts"].(map[string]any) if ws["path"] != "/ws" { t.Errorf("sibling 'path' lost: got %v", ws["path"]) } if ws["newkey"] != "newval" { t.Errorf("new key not set: got %v", ws["newkey"]) } }) t.Run("replace non-map intermediate with map", func(t *testing.T) { m := map[string]any{"a": "stringvalue"} result := SetByPath(m, "a.b", "val") a := result["a"].(map[string]any) if a["b"] != "val" { t.Errorf("replace non-map intermediate failed: got %v", a["b"]) } }) } func TestGetString(t *testing.T) { m := map[string]any{ "name": "test", "port": 443, } if got := GetString(m, "name"); got != "test" { t.Errorf("GetString(name) = %q, want %q", got, "test") } if got := GetString(m, "port"); got != "" { t.Errorf("GetString(port) = %q, want empty (non-string)", got) } if got := GetString(m, "missing"); got != "" { t.Errorf("GetString(missing) = %q, want empty", got) } } func TestGetInt(t *testing.T) { tests := []struct { name string m map[string]any path string want int }{ {"int", map[string]any{"port": 443}, "port", 443}, {"int64", map[string]any{"port": int64(443)}, "port", 443}, {"float64", map[string]any{"port": float64(443)}, "port", 443}, {"string numeric", map[string]any{"port": "443"}, "port", 443}, {"string non-numeric", map[string]any{"port": "abc"}, "port", 0}, {"missing", map[string]any{}, "port", 0}, {"non-numeric type", map[string]any{"port": true}, "port", 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetInt(tt.m, tt.path); got != tt.want { t.Errorf("GetInt(%s) = %d, want %d", tt.path, got, tt.want) } }) } } func TestGetBool(t *testing.T) { m := map[string]any{ "enabled": true, "port": 443, } if got := GetBool(m, "enabled"); !got { t.Error("GetBool(enabled) = false, want true") } if got := GetBool(m, "port"); got { t.Error("GetBool(port) = true, want false (non-bool)") } if got := GetBool(m, "missing"); got { t.Error("GetBool(missing) = true, want false") } } func TestDeepCopy(t *testing.T) { original := map[string]any{ "name": "test", "port": 443, "nested": map[string]any{"key": "val"}, } cp := DeepCopy(original) // Values match (note: JSON round-trip converts int → float64) if cp["name"] != "test" { t.Errorf("DeepCopy name mismatch: %v", cp["name"]) } if port, ok := cp["port"].(float64); !ok || port != 443 { t.Errorf("DeepCopy port mismatch: %v", cp["port"]) } // Nested map is a different instance (deep) nestedOrig := original["nested"].(map[string]any) nestedCopy := cp["nested"].(map[string]any) nestedCopy["key"] = "changed" if nestedOrig["key"] == "changed" { t.Error("DeepCopy did not deep-copy nested map") } // Empty input if got := DeepCopy(map[string]any{}); len(got) != 0 { t.Errorf("DeepCopy(empty) = %v, want empty map", got) } } func TestStripUndefined(t *testing.T) { m := map[string]any{ "name": "test", "empty": "", "nilval": nil, "port": 443, "zero": 0, "false": false, "nonempty": "value", } result := StripUndefined(m) if _, ok := result["empty"]; ok { t.Error("StripUndefined should remove empty strings") } if _, ok := result["nilval"]; ok { t.Error("StripUndefined should remove nil values") } if _, ok := result["name"]; !ok { t.Error("StripUndefined should keep non-empty strings") } if _, ok := result["port"]; !ok { t.Error("StripUndefined should keep ints") } if _, ok := result["zero"]; !ok { t.Error("StripUndefined should keep zero (not nil/empty)") } if _, ok := result["false"]; !ok { t.Error("StripUndefined should keep false (not nil/empty)") } } func TestMergeDeep(t *testing.T) { base := map[string]any{ "name": "base", "port": 8080, "nested": map[string]any{ "a": "base-a", "b": "base-b", }, } next := map[string]any{ "port": 9090, "nested": map[string]any{ "b": "next-b", "c": "next-c", }, "newkey": "next-val", } result := MergeDeep(base, next) if result["name"] != "base" { t.Errorf("MergeDeep name = %v, want base", result["name"]) } if result["port"] != 9090 { t.Errorf("MergeDeep port = %v, want 9090 (overridden)", result["port"]) } if result["newkey"] != "next-val" { t.Errorf("MergeDeep newkey = %v, want next-val", result["newkey"]) } nested := result["nested"].(map[string]any) if nested["a"] != "base-a" { t.Errorf("MergeDeep nested.a = %v, want base-a (preserved)", nested["a"]) } if nested["b"] != "next-b" { t.Errorf("MergeDeep nested.b = %v, want next-b (overridden)", nested["b"]) } if nested["c"] != "next-c" { t.Errorf("MergeDeep nested.c = %v, want next-c (added)", nested["c"]) } // nil values in next should be skipped result2 := MergeDeep(map[string]any{"a": "1"}, map[string]any{"a": nil, "b": "2"}) if result2["a"] != "1" { t.Errorf("MergeDeep nil in next should not override: a = %v", result2["a"]) } if result2["b"] != "2" { t.Errorf("MergeDeep b = %v, want 2", result2["b"]) } // base not mutated if base["port"] != 8080 { t.Error("MergeDeep should not mutate base map") } } func TestToId(t *testing.T) { tests := []struct { name string input string want string }{ {"simple", "My Node", "my-node"}, {"uppercase", "HelloWorld", "helloworld"}, {"special chars", "Node!@#Name", "node-name"}, {"underscores preserved", "my_node", "my_node"}, {"leading/trailing dashes", " --name-- ", "name"}, {"consecutive special", "a b", "a-b"}, {"numbers", "node123", "node123"}, {"mixed", "HK-香港 01", "hk-01"}, {"empty", "", "item"}, {"only special", "!!!", "item"}, {"dashes collapsed", "a--b", "a-b"}, {"long truncation", strings.Repeat("a", 100), strings.Repeat("a", 64)}, {"chinese chars", "香港节点", "item"}, {"underscore and dash", "my_node-name", "my_node-name"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := ToId(tt.input) if got != tt.want { t.Errorf("ToId(%q) = %q, want %q", tt.input, got, tt.want) } }) } } // ---------- flag.go ---------- func TestDetectFlag(t *testing.T) { tests := []struct { name string input string want string }{ {"Hong Kong CN", "香港 01", "🇭🇰"}, {"Hong Kong EN", "Hong Kong 01", "🇭🇰"}, {"HK abbreviation", "HK 01", "🇭🇰"}, {"Taiwan CN", "台湾 01", "🇹🇼"}, {"Taiwan TW", "台灣 01", "🇹🇼"}, {"Taiwan EN", "Taiwan 01", "🇹🇼"}, {"TW abbreviation", "TW 01", "🇹🇼"}, {"Singapore CN", "新加坡 01", "🇸🇬"}, {"Singapore EN", "Singapore 01", "🇸🇬"}, {"SG abbreviation", "SG 01", "🇸🇬"}, {"Japan CN", "日本 01", "🇯🇵"}, {"Tokyo", "东京 01", "🇯🇵"}, {"Osaka JP", "大阪 01", "🇯🇵"}, {"Japan EN", "Japan 01", "🇯🇵"}, {"JP abbreviation", "JP 01", "🇯🇵"}, {"USA CN", "美国 01", "🇺🇸"}, {"USA EN", "United States 01", "🇺🇸"}, {"Los Angeles", "洛杉矶 01", "🇺🇸"}, {"US abbreviation", "US 01", "🇺🇸"}, {"UK CN", "英国 01", "🇬🇧"}, {"London", "伦敦 01", "🇬🇧"}, {"UK abbreviation", "UK 01", "🇬🇧"}, {"Germany CN", "德国 01", "🇩🇪"}, {"Frankfurt", "法兰克福 01", "🇩🇪"}, {"DE abbreviation", "DE 01", "🇩🇪"}, {"Korea CN", "韩国 01", "🇰🇷"}, {"Seoul", "首尔 01", "🇰🇷"}, {"KR abbreviation", "KR 01", "🇰🇷"}, {"no match", "Unknown Location", "🏳️"}, {"empty", "", "🏳️"}, {"lowercase abbreviation", "hk node", "🇭🇰"}, {"uppercase abbreviation", "HK node", "🇭🇰"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := DetectFlag(tt.input) if got != tt.want { t.Errorf("DetectFlag(%q) = %q, want %q", tt.input, got, tt.want) } }) } } func TestRemoveFlag(t *testing.T) { tests := []struct { name string input string want string }{ {"with HK flag", "🇭🇰 香港 01", "香港 01"}, {"with TW flag", "🇹🇼 台湾 01", "台湾 01"}, {"with white flag", "🏳️ Test Node", "Test Node"}, {"no flag", "Test Node", "Test Node"}, {"empty", "", ""}, {"only flag", "🇭🇰", ""}, {"multiple spaces with flag", "🇭🇰 Node", "Node"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := RemoveFlag(tt.input) if got != tt.want { t.Errorf("RemoveFlag(%q) = %q, want %q", tt.input, got, tt.want) } }) } } func TestNormalizeTaiwanFlag(t *testing.T) { tests := []struct { name string flag string mode string want string }{ {"tw flag ws mode", "🇹🇼", "ws", "🇼🇸"}, {"tw flag tw mode", "🇹🇼", "tw", "🇹🇼"}, {"tw flag default mode", "🇹🇼", "", "🇨🇳"}, {"tw flag other mode", "🇹🇼", "other", "🇨🇳"}, {"non-tw flag ws mode", "🇭🇰", "ws", "🇭🇰"}, {"non-tw flag tw mode", "🇭🇰", "tw", "🇭🇰"}, {"non-tw flag default", "🇺🇸", "", "🇺🇸"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := NormalizeTaiwanFlag(tt.flag, tt.mode) if got != tt.want { t.Errorf("NormalizeTaiwanFlag(%q, %q) = %q, want %q", tt.flag, tt.mode, got, tt.want) } }) } } func TestIsASCII(t *testing.T) { tests := []struct { name string s string want bool }{ {"ascii", "Hello World", true}, {"ascii with numbers", "123 abc", true}, {"ascii symbols", "!@#$%^&*()", true}, {"empty", "", true}, {"non-ascii (chinese)", "你好", false}, {"non-ascii (emoji)", "🇭🇰", false}, {"mixed", "Hello 世界", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsASCII(tt.s); got != tt.want { t.Errorf("IsASCII(%q) = %v, want %v", tt.s, got, tt.want) } }) } } // ---------- ip.go ---------- func TestIsIPv4(t *testing.T) { tests := []struct { name string input string want bool }{ {"valid", "192.168.1.1", true}, {"valid zeros", "0.0.0.0", true}, {"valid max", "255.255.255.255", true}, {"valid single", "1.1.1.1", true}, {"too many parts", "1.2.3.4.5", false}, {"too few parts", "1.2.3", false}, {"part too large", "256.1.1.1", false}, {"non-numeric", "a.b.c.d", false}, {"empty part", "1..1.1", false}, {"empty", "", false}, {"part too long", "1234.1.1.1", false}, {"with letters", "192.168.1.1a", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsIPv4(tt.input); got != tt.want { t.Errorf("IsIPv4(%q) = %v, want %v", tt.input, got, tt.want) } }) } } func TestIsIPv6(t *testing.T) { tests := []struct { name string input string want bool }{ {"valid full", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", true}, {"valid compressed", "::1", true}, {"valid loopback", "::1", true}, {"valid with zero", "fe80::1", true}, {"missing colon", "192.168.1.1", false}, {"invalid chars", "2001:0db8:85a3::GGGG", false}, {"empty", "", false}, {"simple", "2001::1", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsIPv6(tt.input); got != tt.want { t.Errorf("IsIPv6(%q) = %v, want %v", tt.input, got, tt.want) } }) } } func TestIsIPAddress(t *testing.T) { tests := []struct { name string input string want bool }{ {"ipv4", "192.168.1.1", true}, {"ipv6", "::1", true}, {"hostname", "example.com", false}, {"empty", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsIPAddress(tt.input); got != tt.want { t.Errorf("IsIPAddress(%q) = %v, want %v", tt.input, got, tt.want) } }) } } func TestShouldResolveServer(t *testing.T) { tests := []struct { name string server string want bool }{ {"hostname", "example.com", true}, {"subdomain", "sub.example.com", true}, {"ipv4", "192.168.1.1", false}, {"ipv6", "::1", false}, {"empty", "", false}, {"whitespace only", " ", false}, {"hostname with spaces", " example.com ", true}, {"no dot", "localhost", false}, {"invalid chars", "exa mple.com", false}, {"with underscore (invalid)", "my_host.com", false}, {"with port-like", "example.com:443", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := ShouldResolveServer(tt.server); got != tt.want { t.Errorf("ShouldResolveServer(%q) = %v, want %v", tt.server, got, tt.want) } }) } } // ---------- limits.go ---------- func TestLimitsConstants(t *testing.T) { // Verify the limit constants are set to expected values. if MaxAPIBodyBytes != 4*1024*1024 { t.Errorf("MaxAPIBodyBytes = %d, want %d", MaxAPIBodyBytes, 4*1024*1024) } if MaxRemoteSourceUrls != 8 { t.Errorf("MaxRemoteSourceUrls = %d, want 8", MaxRemoteSourceUrls) } if MaxRemoteSourceRespBytes != 2*1024*1024 { t.Errorf("MaxRemoteSourceRespBytes = %d, want %d", MaxRemoteSourceRespBytes, 2*1024*1024) } if MaxRemoteSourceTotalBytes != 12*1024*1024 { t.Errorf("MaxRemoteSourceTotalBytes = %d, want %d", MaxRemoteSourceTotalBytes, 12*1024*1024) } if MaxFlowRespBytes != 64*1024 { t.Errorf("MaxFlowRespBytes = %d, want %d", MaxFlowRespBytes, 64*1024) } if MaxDoHRespBytes != 64*1024 { t.Errorf("MaxDoHRespBytes = %d, want %d", MaxDoHRespBytes, 64*1024) } if MaxRecycleEntries != 50 { t.Errorf("MaxRecycleEntries = %d, want 50", MaxRecycleEntries) } if MaxCustomRules != 32 { t.Errorf("MaxCustomRules = %d, want 32", MaxCustomRules) } } // ---------- random.go ---------- func TestReadRandom(t *testing.T) { buf := make([]byte, 16) n, err := readRandom(buf) if err != nil { t.Fatalf("readRandom error: %v", err) } if n != 16 { t.Errorf("readRandom returned n=%d, want 16", n) } // very unlikely to be all zeros allZero := true for _, b := range buf { if b != 0 { allZero = false break } } if allZero { t.Error("readRandom returned all zeros (suspicious)") } // two reads should differ buf2 := make([]byte, 16) readRandom(buf2) same := true for i := range buf { if buf[i] != buf2[i] { same = false break } } if same { t.Error("two readRandom calls returned same bytes (suspicious)") } }