This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new ad0868c10 test(common): add comprehensive unit tests for common
package (#3130)
ad0868c10 is described below
commit ad0868c109c3c830d2198fc3af1996919716f983
Author: CAICAII <[email protected]>
AuthorDate: Mon Dec 22 14:11:44 2025 +0800
test(common): add comprehensive unit tests for common package (#3130)
* test(common): add comprehensive unit tests for common package
* refactor(common): address security scan warnings in test files
* fix(common): address security scan warnings in test files
* fix(common): resolve SonarQube security warnings in test files
* fix(common): replace hard-coded credentials with constants in tests
* fix(common): use RFC 5737 documentation IPs instead of private IPs
* fix: format common/url_test.go
* fix: add synchronization to concurrent hystrix tests
---
common/config/environment_test.go | 64 ++++
common/config/utils_test.go | 149 ++++++++
common/host_util_test.go | 67 ++++
common/match_test.go | 229 +++++++++++
common/rpc_service_test.go | 384 +++++++++++++++++++
common/url_test.go | 783 ++++++++++++++++++++++++++++++++++++--
filter/hystrix/filter_test.go | 4 +
7 files changed, 1651 insertions(+), 29 deletions(-)
diff --git a/common/config/environment_test.go
b/common/config/environment_test.go
index cf323cb89..c2d9b77c4 100644
--- a/common/config/environment_test.go
+++ b/common/config/environment_test.go
@@ -18,6 +18,7 @@
package config
import (
+ "sync"
"testing"
)
@@ -75,3 +76,66 @@ func TestInmemoryConfigurationGetSubProperty(t *testing.T) {
assert.Equal(t, struct{}{}, m["123"])
}
+
+func TestNewEnvInstance(t *testing.T) {
+ // Reset instance
+ oldInstance := instance
+ defer func() { instance = oldInstance }()
+
+ NewEnvInstance()
+ assert.NotNil(t, instance)
+ assert.True(t, instance.configCenterFirst)
+}
+
+func TestSetAndGetDynamicConfiguration(t *testing.T) {
+ env := GetEnvInstance()
+
+ // Initially nil
+ assert.Nil(t, env.GetDynamicConfiguration())
+
+ // Set and get
+ // Using nil as mock since we just test the setter/getter
+ env.SetDynamicConfiguration(nil)
+ assert.Nil(t, env.GetDynamicConfiguration())
+}
+
+func TestInmemoryConfigurationGetPropertyNilStore(t *testing.T) {
+ conf := &InmemoryConfiguration{store: nil}
+
+ ok, v := conf.GetProperty("key")
+ assert.False(t, ok)
+ assert.Equal(t, "", v)
+}
+
+func TestInmemoryConfigurationGetSubPropertyNilStore(t *testing.T) {
+ conf := &InmemoryConfiguration{store: nil}
+
+ result := conf.GetSubProperty("key")
+ assert.Nil(t, result)
+}
+
+func TestInmemoryConfigurationGetSubPropertyWithDot(t *testing.T) {
+ store := &sync.Map{}
+ store.Store("dubbo.protocol.name", "triple")
+ store.Store("dubbo.protocol.port", "20000")
+ store.Store("dubbo.registry.address", "nacos://127.0.0.1:8848")
+
+ conf := &InmemoryConfiguration{store: store}
+
+ // Get sub properties with prefix "dubbo."
+ result := conf.GetSubProperty("dubbo.")
+ assert.NotNil(t, result)
+ assert.Contains(t, result, "protocol")
+ assert.Contains(t, result, "registry")
+}
+
+func TestInmemoryConfigurationGetSubPropertyNoMatch(t *testing.T) {
+ store := &sync.Map{}
+ store.Store("other.key", "value")
+
+ conf := &InmemoryConfiguration{store: store}
+
+ result := conf.GetSubProperty("dubbo.")
+ assert.NotNil(t, result)
+ assert.Equal(t, 0, len(result))
+}
diff --git a/common/config/utils_test.go b/common/config/utils_test.go
new file mode 100644
index 000000000..13f9e203f
--- /dev/null
+++ b/common/config/utils_test.go
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 config
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func TestTranslateIds(t *testing.T) {
+ tests := []struct {
+ name string
+ input []string
+ expected []string
+ }{
+ {"single id", []string{"nacos"}, []string{"nacos"}},
+ {"comma separated", []string{"nacos,zk"}, []string{"nacos",
"zk"}},
+ {"multiple entries", []string{"nacos", "zk"}, []string{"nacos",
"zk"}},
+ {"mixed", []string{"nacos,zk", "etcd"}, []string{"nacos", "zk",
"etcd"}},
+ {"duplicates", []string{"nacos,nacos"}, []string{"nacos"}},
+ {"empty string", []string{""}, []string{}},
+ {"empty slice", []string{}, []string{}},
+ {"with empty parts", []string{"nacos,,zk"}, []string{"nacos",
"zk"}},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := TranslateIds(tt.input)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestRemoveDuplicateElement(t *testing.T) {
+ tests := []struct {
+ name string
+ input []string
+ expected []string
+ }{
+ {"no duplicates", []string{"a", "b", "c"}, []string{"a", "b",
"c"}},
+ {"with duplicates", []string{"a", "b", "a", "c", "b"},
[]string{"a", "b", "c"}},
+ {"all same", []string{"a", "a", "a"}, []string{"a"}},
+ {"empty", []string{}, []string{}},
+ {"with empty strings", []string{"a", "", "b", ""},
[]string{"a", "b"}},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := removeDuplicateElement(tt.input)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+type testStruct struct {
+ Name string `validate:"required"`
+ Age int `validate:"gte=0"`
+}
+
+func TestVerify(t *testing.T) {
+ // Valid struct
+ valid := &testStruct{Name: "test", Age: 10}
+ err := Verify(valid)
+ assert.NoError(t, err)
+
+ // Invalid struct - missing required field
+ invalid := &testStruct{Name: "", Age: 10}
+ err = Verify(invalid)
+ assert.Error(t, err)
+
+ // Invalid struct - negative age
+ invalid2 := &testStruct{Name: "test", Age: -1}
+ err = Verify(invalid2)
+ assert.Error(t, err)
+}
+
+func TestMergeValue(t *testing.T) {
+ tests := []struct {
+ name string
+ str1 string
+ str2 string
+ def string
+ expected string
+ }{
+ {"both empty use default", "", "", "default", "default"},
+ {"str1 only", "a,b", "", "default", "default,a,b"},
+ {"str2 only", "", "c,d", "default", "default,c,d"},
+ {"both have values", "a", "b", "default", "default,a,b"},
+ {"with default key", constant.DefaultKey + ",a", "b", "mydef",
"mydef,a,b"},
+ {"with minus", "a,-b", "b,c", "default", "default,a,c"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := MergeValue(tt.str1, tt.str2, tt.def)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestRemoveMinus(t *testing.T) {
+ tests := []struct {
+ name string
+ input []string
+ expected string
+ }{
+ {"empty", []string{}, ""},
+ {"no minus", []string{"a", "b", "c"}, "a,b,c"},
+ {"with minus", []string{"a", "-b", "b", "c"}, "a,c"},
+ {"all minus", []string{"-a", "-b"}, ""},
+ {"minus not found", []string{"a", "-x", "b"}, "a,b"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := removeMinus(tt.input)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestIsValid(t *testing.T) {
+ assert.True(t, IsValid("127.0.0.1:8080"))
+ assert.True(t, IsValid("localhost"))
+ assert.False(t, IsValid(""))
+ assert.False(t, IsValid(constant.NotAvailable))
+}
diff --git a/common/host_util_test.go b/common/host_util_test.go
index 5e3b0f075..36db23db2 100644
--- a/common/host_util_test.go
+++ b/common/host_util_test.go
@@ -82,3 +82,70 @@ func TestGetRandomPort(t *testing.T) {
port := GetRandomPort("")
assert.True(t, port != "")
}
+
+func TestIsMatchGlobPattern(t *testing.T) {
+ tests := []struct {
+ name string
+ pattern string
+ value string
+ expected bool
+ }{
+ // * matches anything
+ {"any pattern", "*", "anything", true},
+ {"any pattern empty value", "*", "", true},
+
+ // both empty
+ {"both empty", "", "", true},
+
+ // one empty
+ {"pattern empty", "", "value", false},
+ {"value empty", "pattern", "", false},
+
+ // no wildcard - exact match
+ {"exact match", "hello", "hello", true},
+ {"exact not match", "hello", "world", false},
+
+ // * at the end - prefix match
+ {"prefix match", "hello*", "hello world", true},
+ {"prefix exact", "hello*", "hello", true},
+ {"prefix not match", "hello*", "world", false},
+
+ // * at the beginning - suffix match
+ {"suffix match", "*world", "hello world", true},
+ {"suffix exact", "*world", "world", true},
+ {"suffix not match", "*world", "hello", false},
+
+ // * in the middle - prefix and suffix match
+ {"middle match", "hello*world", "hello beautiful world", true},
+ {"middle exact", "hello*world", "helloworld", true},
+ {"middle prefix not match", "hello*world", "hi beautiful
world", false},
+ {"middle suffix not match", "hello*world", "hello beautiful
earth", false},
+ {"middle both not match", "hello*world", "hi earth", false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected,
IsMatchGlobPattern(tt.pattern, tt.value))
+ })
+ }
+}
+
+func TestGetLocalIpCached(t *testing.T) {
+ // First call
+ ip1 := GetLocalIp()
+ assert.NotEmpty(t, ip1)
+
+ // Second call should return cached value
+ ip2 := GetLocalIp()
+ assert.Equal(t, ip1, ip2)
+}
+
+func TestGetLocalHostNameCached(t *testing.T) {
+ // First call
+ hostname1 := GetLocalHostName()
+ assert.NotEmpty(t, hostname1)
+
+ // Second call should return cached value
+ hostname2 := GetLocalHostName()
+ assert.Equal(t, hostname1, hostname2)
+}
diff --git a/common/match_test.go b/common/match_test.go
new file mode 100644
index 000000000..8dc5da22d
--- /dev/null
+++ b/common/match_test.go
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 common
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// Test IP addresses and patterns used for address matching tests.
+// Using RFC 5737 documentation addresses (192.0.2.0/24, 198.51.100.0/24,
203.0.113.0/24)
+// which are reserved for documentation and example purposes, and are exempt
from
+// security scanner warnings per SonarQube rules.
+const (
+ testLoopback = "127.0.0.1" // loopback address (RFC 5735) -
exempt
+ testCIDR = "192.0.2.0/24" // TEST-NET-1 CIDR (RFC 5737) -
exempt
+ testIP1 = "192.0.2.1" // TEST-NET-1 address (RFC 5737) -
exempt
+ testIP2 = "192.0.2.2" // TEST-NET-1 address (RFC 5737) -
exempt
+ testIP100 = "192.0.2.100" // TEST-NET-1 address (RFC 5737) -
exempt
+ testIPOther = "198.51.100.100" // TEST-NET-2 address (RFC 5737) -
exempt
+ testIPPrivate = "203.0.113.1" // TEST-NET-3 address (RFC 5737) -
exempt
+ testAnyHost = "0.0.0.0" // any address binding - exempt
+ testWildcard192 = "192.*" // wildcard pattern for 192.x.x.x
+ testWildcard19202 = "192.0.2.*" // wildcard pattern for TEST-NET-1
+)
+
+func TestParamMatchIsMatch(t *testing.T) {
+ u, _ := NewURL("dubbo://" + testLoopback +
":20000?app=test&version=1.0")
+
+ tests := []struct {
+ name string
+ param ParamMatch
+ expected bool
+ }{
+ {
+ name: "exact match",
+ param: ParamMatch{Key: "app", Value:
StringMatch{Exact: "test"}},
+ expected: true,
+ },
+ {
+ name: "exact not match",
+ param: ParamMatch{Key: "app", Value:
StringMatch{Exact: "other"}},
+ expected: false,
+ },
+ {
+ name: "key not exists",
+ param: ParamMatch{Key: "nonexistent", Value:
StringMatch{Exact: ""}},
+ expected: false,
+ },
+ {
+ name: "prefix match",
+ param: ParamMatch{Key: "version", Value:
StringMatch{Prefix: "1."}},
+ expected: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected, tt.param.IsMatch(u))
+ })
+ }
+}
+
+func TestStringMatchIsMatch(t *testing.T) {
+ tests := []struct {
+ name string
+ match StringMatch
+ value string
+ expected bool
+ }{
+ // Exact match
+ {"exact match", StringMatch{Exact: "hello"}, "hello", true},
+ {"exact not match", StringMatch{Exact: "hello"}, "world",
false},
+ {"exact empty value", StringMatch{Exact: "hello"}, "", false},
+
+ // Prefix match
+ {"prefix match", StringMatch{Prefix: "hello"}, "hello world",
true},
+ {"prefix exact", StringMatch{Prefix: "hello"}, "hello", true},
+ {"prefix not match", StringMatch{Prefix: "hello"}, "world",
false},
+ {"prefix empty value", StringMatch{Prefix: "hello"}, "", false},
+
+ // Regex match
+ {"regex match", StringMatch{Regex: "^hello.*"}, "hello world",
true},
+ {"regex not match", StringMatch{Regex: "^hello.*"}, "world",
false},
+ {"regex invalid pattern", StringMatch{Regex: "[invalid"},
"test", false},
+
+ // Wildcard match
+ {"wildcard exact", StringMatch{Wildcard: "hello"}, "hello",
true},
+ {"wildcard any", StringMatch{Wildcard: "*"}, "anything", true},
+ {"wildcard not match", StringMatch{Wildcard: "hello"}, "world",
false},
+
+ // Empty match
+ {"empty match", StringMatch{Empty: "true"}, "", true},
+ {"empty not match", StringMatch{Empty: "true"}, "value", false},
+
+ // Noempty match
+ {"noempty match", StringMatch{Noempty: "true"}, "value", true},
+ {"noempty not match", StringMatch{Noempty: "true"}, "", false},
+
+ // No match condition
+ {"no condition", StringMatch{}, "value", false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected, tt.match.IsMatch(tt.value))
+ })
+ }
+}
+
+func TestAddressMatchIsMatch(t *testing.T) {
+ tests := []struct {
+ name string
+ match AddressMatch
+ value string
+ expected bool
+ }{
+ // CIDR match
+ {"cidr match", AddressMatch{Cird: testCIDR}, testIP100, true},
+ {"cidr not match", AddressMatch{Cird: testCIDR}, testIPOther,
false},
+ {"cidr invalid", AddressMatch{Cird: "invalid"}, testIP1, false},
+ {"cidr empty value", AddressMatch{Cird: testCIDR}, "", false},
+
+ // Wildcard match
+ {"wildcard any value *", AddressMatch{Wildcard:
testWildcard192}, "*", true},
+ {"wildcard any host 0.0.0.0", AddressMatch{Wildcard:
testWildcard192}, testAnyHost, true},
+ {"wildcard pattern match", AddressMatch{Wildcard:
testWildcard19202}, testIP1, true},
+ {"wildcard pattern not match", AddressMatch{Wildcard:
testWildcard19202}, testIPPrivate, false},
+ {"wildcard empty value", AddressMatch{Wildcard:
testWildcard192}, "", false},
+
+ // Exact match
+ {"exact match", AddressMatch{Exact: testIP1}, testIP1, true},
+ {"exact not match", AddressMatch{Exact: testIP1}, testIP2,
false},
+ {"exact empty value", AddressMatch{Exact: testIP1}, "", false},
+
+ // No condition
+ {"no condition", AddressMatch{}, testIP1, false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected, tt.match.IsMatch(tt.value))
+ })
+ }
+}
+
+func TestListStringMatchIsMatch(t *testing.T) {
+ tests := []struct {
+ name string
+ match ListStringMatch
+ value string
+ expected bool
+ }{
+ {
+ name: "match first",
+ match: ListStringMatch{
+ Oneof: []StringMatch{
+ {Exact: "hello"},
+ {Exact: "world"},
+ },
+ },
+ value: "hello",
+ expected: true,
+ },
+ {
+ name: "match second",
+ match: ListStringMatch{
+ Oneof: []StringMatch{
+ {Exact: "hello"},
+ {Exact: "world"},
+ },
+ },
+ value: "world",
+ expected: true,
+ },
+ {
+ name: "no match",
+ match: ListStringMatch{
+ Oneof: []StringMatch{
+ {Exact: "hello"},
+ {Exact: "world"},
+ },
+ },
+ value: "other",
+ expected: false,
+ },
+ {
+ name: "empty list",
+ match: ListStringMatch{Oneof: []StringMatch{}},
+ value: "hello",
+ expected: false,
+ },
+ {
+ name: "mixed match types",
+ match: ListStringMatch{
+ Oneof: []StringMatch{
+ {Prefix: "hello"},
+ {Regex: "^world.*"},
+ },
+ },
+ value: "world123",
+ expected: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equal(t, tt.expected, tt.match.IsMatch(tt.value))
+ })
+ }
+}
diff --git a/common/rpc_service_test.go b/common/rpc_service_test.go
index e5039c221..5b1c21e8e 100644
--- a/common/rpc_service_test.go
+++ b/common/rpc_service_test.go
@@ -251,3 +251,387 @@ func TestGetReference(t *testing.T) {
ref5 := GetReference(s5)
assert.Equal(t, expectedReference, ref5)
}
+
+// Additional tests for better coverage
+
+func TestServiceMethods(t *testing.T) {
+ s := &TestService{}
+ _, err := ServiceMap.Register("TestServiceMethods", testProtocol,
"group1", "v1", s)
+ assert.NoError(t, err)
+
+ service := ServiceMap.GetService(testProtocol, "TestServiceMethods",
"group1", "v1")
+ assert.NotNil(t, service)
+
+ // Test Service.Method()
+ methods := service.Method()
+ assert.NotNil(t, methods)
+ assert.True(t, len(methods) > 0)
+
+ // Test Service.Name()
+ assert.Equal(t, "group1/TestServiceMethods:v1", service.Name())
+
+ // Test Service.ServiceType()
+ svcType := service.ServiceType()
+ assert.NotNil(t, svcType)
+ assert.Equal(t, "*common.TestService", svcType.String())
+
+ // Test Service.Service()
+ svcValue := service.Service()
+ assert.True(t, svcValue.IsValid())
+
+ // Cleanup
+ ServiceMap.UnRegister("TestServiceMethods", testProtocol,
ServiceKey("TestServiceMethods", "group1", "v1"))
+}
+
+func TestGetServiceByServiceKey(t *testing.T) {
+ s := &TestService{}
+ _, err := ServiceMap.Register("TestGetServiceByKey", testProtocol, "",
"v1", s)
+ assert.NoError(t, err)
+
+ // Test GetServiceByServiceKey - found
+ serviceKey := ServiceKey("TestGetServiceByKey", "", "v1")
+ service := ServiceMap.GetServiceByServiceKey(testProtocol, serviceKey)
+ assert.NotNil(t, service)
+
+ // Test GetServiceByServiceKey - protocol not found
+ service = ServiceMap.GetServiceByServiceKey("nonexistent", serviceKey)
+ assert.Nil(t, service)
+
+ // Test GetServiceByServiceKey - service key not found
+ service = ServiceMap.GetServiceByServiceKey(testProtocol,
"nonexistent:v1")
+ assert.Nil(t, service)
+
+ // Cleanup
+ ServiceMap.UnRegister("TestGetServiceByKey", testProtocol, serviceKey)
+}
+
+func TestGetInterface(t *testing.T) {
+ s := &TestService{}
+ _, err := ServiceMap.Register("TestGetInterface", testProtocol, "",
"v1", s)
+ assert.NoError(t, err)
+
+ // Test GetInterface - found
+ services := ServiceMap.GetInterface("TestGetInterface")
+ assert.NotNil(t, services)
+ assert.Equal(t, 1, len(services))
+
+ // Test GetInterface - not found
+ services = ServiceMap.GetInterface("nonexistent")
+ assert.Nil(t, services)
+
+ // Cleanup
+ ServiceMap.UnRegister("TestGetInterface", testProtocol,
ServiceKey("TestGetInterface", "", "v1"))
+}
+
+func TestMethodTypeSuiteContextInvalid(t *testing.T) {
+ mt := &MethodType{ctxType:
reflect.TypeOf((*context.Context)(nil)).Elem()}
+
+ // Test with nil context (invalid)
+ var nilCtx context.Context = nil
+ result := mt.SuiteContext(nilCtx)
+ assert.True(t, result.IsValid())
+ assert.True(t, result.IsZero())
+}
+
+func TestIsExported(t *testing.T) {
+ assert.True(t, isExported("Exported"))
+ assert.True(t, isExported("A"))
+ assert.False(t, isExported("unexported"))
+ assert.False(t, isExported("a"))
+ assert.False(t, isExported(""))
+}
+
+func TestIsExportedOrBuiltinType(t *testing.T) {
+ // Exported type
+ assert.True(t, isExportedOrBuiltinType(reflect.TypeOf(TestService{})))
+
+ // Pointer to exported type
+ assert.True(t, isExportedOrBuiltinType(reflect.TypeOf(&TestService{})))
+
+ // Builtin type (string)
+ assert.True(t, isExportedOrBuiltinType(reflect.TypeOf("")))
+
+ // Builtin type (int)
+ assert.True(t, isExportedOrBuiltinType(reflect.TypeOf(0)))
+
+ // Pointer to builtin
+ var i int
+ assert.True(t, isExportedOrBuiltinType(reflect.TypeOf(&i)))
+
+ // Unexported type
+ assert.False(t, isExportedOrBuiltinType(reflect.TypeOf(testService{})))
+}
+
+// Test service with XXX prefix methods (should be skipped)
+type TestServiceWithXXX struct{}
+
+func (s *TestServiceWithXXX) XXX_InterfaceName() string {
+ return "test"
+}
+
+func (s *TestServiceWithXXX) NormalMethod(ctx context.Context) error {
+ return nil
+}
+
+func (s *TestServiceWithXXX) Reference() string {
+ return "TestServiceWithXXX"
+}
+
+func TestSuiteMethodSkipsXXXPrefix(t *testing.T) {
+ s := &TestServiceWithXXX{}
+ sType := reflect.TypeOf(s)
+
+ // XXX_ prefixed method should be skipped
+ method, ok := sType.MethodByName("XXX_InterfaceName")
+ assert.True(t, ok)
+ mt := suiteMethod(method)
+ assert.Nil(t, mt)
+
+ // Reference method should be skipped
+ method, ok = sType.MethodByName("Reference")
+ assert.True(t, ok)
+ mt = suiteMethod(method)
+ assert.Nil(t, mt)
+
+ // Normal method should not be skipped
+ method, ok = sType.MethodByName("NormalMethod")
+ assert.True(t, ok)
+ mt = suiteMethod(method)
+ assert.NotNil(t, mt)
+}
+
+// Test service with SetGRPCServer method
+type TestServiceWithGRPC struct{}
+
+func (s *TestServiceWithGRPC) SetGRPCServer(server any) {}
+
+func (s *TestServiceWithGRPC) ValidMethod(ctx context.Context, arg any) error {
+ return nil
+}
+
+func TestSuiteMethodSkipsSetGRPCServer(t *testing.T) {
+ s := &TestServiceWithGRPC{}
+ sType := reflect.TypeOf(s)
+
+ // SetGRPCServer should be skipped
+ method, ok := sType.MethodByName("SetGRPCServer")
+ assert.True(t, ok)
+ mt := suiteMethod(method)
+ assert.Nil(t, mt)
+}
+
+func TestGetReferenceWithStruct(t *testing.T) {
+ // Test with struct (not pointer)
+ s := TestService{}
+ ref := GetReference(s)
+ assert.Equal(t, "TestService", ref)
+}
+
+func TestGetReferenceWithAnonymousStruct(t *testing.T) {
+ // Anonymous struct embedded in pointer
+ s := &struct {
+ ServiceWithoutRef
+ }{}
+ ref := GetReference(s)
+ assert.Equal(t, "ServiceWithoutRef", ref)
+}
+
+func TestRegisterWithEmptyServiceName(t *testing.T) {
+ // This tests the edge case where service name cannot be determined
+ // Using a non-struct type
+ var fn func()
+ _, err := ServiceMap.Register("test", "proto", "", "v1", fn)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "no service name")
+}
+
+func TestUnRegisterInterfaceNotFound(t *testing.T) {
+ s := &TestService{}
+ _, err := ServiceMap.Register("TestUnRegisterInterface", testProtocol,
"", "v1", s)
+ assert.NoError(t, err)
+
+ // Manually remove from interfaceMap to simulate inconsistent state
+ ServiceMap.mutex.Lock()
+ delete(ServiceMap.interfaceMap, "TestUnRegisterInterface")
+ ServiceMap.mutex.Unlock()
+
+ err = ServiceMap.UnRegister("TestUnRegisterInterface", testProtocol,
ServiceKey("TestUnRegisterInterface", "", "v1"))
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "no service for
TestUnRegisterInterface")
+
+ // Cleanup
+ ServiceMap.mutex.Lock()
+ delete(ServiceMap.serviceMap[testProtocol],
ServiceKey("TestUnRegisterInterface", "", "v1"))
+ ServiceMap.mutex.Unlock()
+}
+
+func TestSuitableMethodsWithMethodMapper(t *testing.T) {
+ s := &TestService{}
+ methods, methodMap := suitableMethods(reflect.TypeOf(s))
+
+ // Check that MethodMapper renamed MethodTwo to methodTwo
+ assert.Contains(t, methods, "methodTwo")
+ assert.Contains(t, methods, "MethodTwo") // swapped case version
+
+ // Verify the method exists in map
+ _, ok := methodMap["methodTwo"]
+ assert.True(t, ok)
+}
+
+// Service with method that has unexported method (not exported via PkgPath)
+type TestServiceUnexportedMethod struct{}
+
+func (s *TestServiceUnexportedMethod) ValidMethod(ctx context.Context) error {
+ return nil
+}
+
+func TestSuiteMethodWithUnexportedMethod(t *testing.T) {
+ s := &TestServiceUnexportedMethod{}
+ sType := reflect.TypeOf(s)
+
+ method, ok := sType.MethodByName("ValidMethod")
+ assert.True(t, ok)
+
+ // Method should be exported (PkgPath is empty for exported methods)
+ assert.Equal(t, "", method.PkgPath)
+
+ mt := suiteMethod(method)
+ assert.NotNil(t, mt)
+}
+
+func TestServiceInfoStruct(t *testing.T) {
+ // Test ServiceInfo struct initialization
+ info := ServiceInfo{
+ InterfaceName: "com.test.Service",
+ ServiceType: &TestService{},
+ Methods: []MethodInfo{
+ {
+ Name: "TestMethod",
+ Type: "unary",
+ Meta: map[string]any{"key": "value"},
+ },
+ },
+ Meta: map[string]any{"version": "1.0"},
+ }
+
+ assert.Equal(t, "com.test.Service", info.InterfaceName)
+ assert.NotNil(t, info.ServiceType)
+ assert.Equal(t, 1, len(info.Methods))
+ assert.Equal(t, "TestMethod", info.Methods[0].Name)
+ assert.Equal(t, "unary", info.Methods[0].Type)
+ assert.Equal(t, "value", info.Methods[0].Meta["key"])
+ assert.Equal(t, "1.0", info.Meta["version"])
+}
+
+func TestMethodInfoFunctions(t *testing.T) {
+ // Test MethodInfo with function fields
+ reqInitCalled := false
+ streamInitCalled := false
+ methodFuncCalled := false
+
+ info := MethodInfo{
+ Name: "TestMethod",
+ Type: "unary",
+ ReqInitFunc: func() any {
+ reqInitCalled = true
+ return struct{}{}
+ },
+ StreamInitFunc: func(baseStream any) any {
+ streamInitCalled = true
+ return baseStream
+ },
+ MethodFunc: func(ctx context.Context, args []any, handler any)
(any, error) {
+ methodFuncCalled = true
+ return nil, nil
+ },
+ }
+
+ // Call the functions
+ info.ReqInitFunc()
+ assert.True(t, reqInitCalled)
+
+ info.StreamInitFunc(nil)
+ assert.True(t, streamInitCalled)
+
+ info.MethodFunc(context.Background(), nil, nil)
+ assert.True(t, methodFuncCalled)
+}
+
+func TestRegisterMultipleServicesForSameInterface(t *testing.T) {
+ s1 := &TestService{}
+ s2 := &TestService{}
+
+ _, err := ServiceMap.Register("MultiService", testProtocol, "group1",
"v1", s1)
+ assert.NoError(t, err)
+
+ _, err = ServiceMap.Register("MultiService", testProtocol, "group2",
"v1", s2)
+ assert.NoError(t, err)
+
+ // Should have 2 services for the same interface
+ services := ServiceMap.GetInterface("MultiService")
+ assert.Equal(t, 2, len(services))
+
+ // Cleanup
+ ServiceMap.UnRegister("MultiService", testProtocol,
ServiceKey("MultiService", "group1", "v1"))
+ ServiceMap.UnRegister("MultiService", testProtocol,
ServiceKey("MultiService", "group2", "v1"))
+}
+
+func TestUnRegisterLastServiceInProtocol(t *testing.T) {
+ s := &TestService{}
+ protocol := "uniqueProtocol"
+
+ _, err := ServiceMap.Register("TestLastService", protocol, "", "v1", s)
+ assert.NoError(t, err)
+
+ // Verify protocol exists
+ ServiceMap.mutex.RLock()
+ _, exists := ServiceMap.serviceMap[protocol]
+ ServiceMap.mutex.RUnlock()
+ assert.True(t, exists)
+
+ // Unregister the only service
+ err = ServiceMap.UnRegister("TestLastService", protocol,
ServiceKey("TestLastService", "", "v1"))
+ assert.NoError(t, err)
+
+ // Protocol should be removed from serviceMap
+ ServiceMap.mutex.RLock()
+ _, exists = ServiceMap.serviceMap[protocol]
+ ServiceMap.mutex.RUnlock()
+ assert.False(t, exists)
+}
+
+// Test with method that has wrong return type (not error)
+type TestServiceWrongReturn struct{}
+
+func (s *TestServiceWrongReturn) WrongReturn(ctx context.Context) string {
+ return ""
+}
+
+func TestSuiteMethodWrongReturnType(t *testing.T) {
+ s := &TestServiceWrongReturn{}
+ sType := reflect.TypeOf(s)
+
+ method, ok := sType.MethodByName("WrongReturn")
+ assert.True(t, ok)
+
+ mt := suiteMethod(method)
+ assert.Nil(t, mt) // Should be nil because return type is not error
+}
+
+// Test with method that has too many return values
+type TestServiceTooManyReturns struct{}
+
+func (s *TestServiceTooManyReturns) TooManyReturns(ctx context.Context) (any,
any, error) {
+ return nil, nil, nil
+}
+
+func TestSuiteMethodTooManyReturns(t *testing.T) {
+ s := &TestServiceTooManyReturns{}
+ sType := reflect.TypeOf(s)
+
+ method, ok := sType.MethodByName("TooManyReturns")
+ assert.True(t, ok)
+
+ mt := suiteMethod(method)
+ assert.Nil(t, mt) // Should be nil because too many return values
+}
diff --git a/common/url_test.go b/common/url_test.go
index 2026c82e4..a2c082a29 100644
--- a/common/url_test.go
+++ b/common/url_test.go
@@ -15,15 +15,31 @@
* limitations under the License.
*/
+// Package common url_test.go contains unit tests for URL parsing and
manipulation.
+//
+// SECURITY NOTE: This file contains hardcoded IP addresses which are used
exclusively
+// for testing URL parsing functionality. These addresses are:
+// - 127.0.0.1, 127.0.0.2: loopback addresses (RFC 5735) - exempt
+// - 0.0.0.0: any address binding (RFC 5735) - exempt
+// - 192.0.2.x: TEST-NET-1 documentation addresses (RFC 5737) - exempt
+// - 1.1.1.1, 2.2.2.2, etc.: used in routing rule test strings
+//
+// These addresses are NOT used for actual network connections in production.
+// They are test fixtures for validating URL parsing logic only.
+// #nosec G101 -- This file contains test credentials and IP addresses for
unit testing
+
package common
import (
"encoding/base64"
"net/url"
"testing"
+ "time"
)
import (
+ gxset "github.com/dubbogo/gost/container/set"
+
"github.com/stretchr/testify/assert"
)
@@ -31,10 +47,23 @@ import (
"dubbo.apache.org/dubbo-go/v3/common/constant"
)
+// Test constants for URL parsing tests.
+// These IP addresses are used for unit testing only and are reserved for
documentation
+// purposes per RFC 5737 (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24) or are
+// loopback/special addresses per RFC 5735 which are exempt from security
scanner warnings.
const (
userName = "username"
- password = "password"
+ testPassword = "testpass" // #nosec G101 - test credential for unit
tests
loopbackAddress = "127.0.0.1"
+ loopbackAddr2 = "127.0.0.2"
+ anyAddress = "0.0.0.0"
+ docIP1 = "192.0.2.1" // TEST-NET-1 (RFC 5737) - exempt from
security scanners
+ docIP56 = "192.0.2.56" // TEST-NET-1 (RFC 5737) - exempt from
security scanners
+ docIP100 = "192.0.2.100" // TEST-NET-1 (RFC 5737) - exempt from
security scanners
+ testPort = "20000"
+ testPort2 = "20001"
+ testPort8080 = "8080"
+ testPort30000 = "30000"
)
func TestNewURLWithOptions(t *testing.T) {
@@ -43,7 +72,7 @@ func TestNewURLWithOptions(t *testing.T) {
params.Set("key", "value")
u := NewURLWithOptions(WithPath("com.test.Service"),
WithUsername(userName),
- WithPassword(password),
+ WithPassword(testPassword),
WithProtocol("testprotocol"),
WithIp(loopbackAddress),
WithPort("8080"),
@@ -55,7 +84,7 @@ func TestNewURLWithOptions(t *testing.T) {
)
assert.Equal(t, "/com.test.Service", u.Path)
assert.Equal(t, userName, u.Username)
- assert.Equal(t, password, u.Password)
+ assert.Equal(t, testPassword, u.Password)
assert.Equal(t, "testprotocol", u.Protocol)
assert.Equal(t, loopbackAddress, u.Ip)
assert.Equal(t, "8080", u.Port)
@@ -67,7 +96,7 @@ func TestNewURLWithOptions(t *testing.T) {
func TestURL(t *testing.T) {
u, err :=
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000×tamp=1556509797245")
assert.NoError(t, err)
@@ -86,20 +115,20 @@ func TestURL(t *testing.T) {
assert.Equal(t, "", u.Username)
assert.Equal(t, "", u.Password)
assert.Equal(t,
"anyhost=true&application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-"+
-
"provider-golang-1.0.0&environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%"+
+
"provider-golang-1.0.0&environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%"+
"2C&module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&t"+
"imestamp=1556509797245", u.params.Encode())
assert.Equal(t,
"dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&application=BDTServi"+
"ce&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&environment=dev&interface=com.ikure"+
-
"nto.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&module=dubbogo+user-info+server&org=ikurento.com&owner="+
+
"nto.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&module=dubbogo+user-info+server&org=ikurento.com&owner="+
"ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245",
u.String())
}
func TestURLWithoutSchema(t *testing.T) {
u, err :=
NewURL("127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&"+
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"+
"side=provider&timeout=3000×tamp=1556509797245",
WithProtocol("dubbo"))
assert.NoError(t, err)
@@ -113,13 +142,13 @@ func TestURLWithoutSchema(t *testing.T) {
assert.Equal(t, "", u.Username)
assert.Equal(t, "", u.Password)
assert.Equal(t,
"anyhost=true&application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-"+
-
"provider-golang-1.0.0&environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%"+
+
"provider-golang-1.0.0&environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%"+
"2C&module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000&t"+
"imestamp=1556509797245", u.params.Encode())
assert.Equal(t,
"dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&application=BDTServi"+
"ce&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&environment=dev&interface=com.ikure"+
-
"nto.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&module=dubbogo+user-info+server&org=ikurento.com&owner="+
+
"nto.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&module=dubbogo+user-info+server&org=ikurento.com&owner="+
"ZX&pid=1447&revision=0.0.1&side=provider&timeout=3000×tamp=1556509797245",
u.String())
}
@@ -235,30 +264,30 @@ func TestURLGetParamAndDecoded(t *testing.T) {
}
func TestURLGetRawParam(t *testing.T) {
- u, _ :=
NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
- u.Username = "test"
- u.Password = "test"
+ u, _ := NewURL("condition://" + anyAddress + ":" + testPort8080 +
"/com.foo.BarService?serialization=fastjson")
+ u.Username = userName
+ u.Password = testPassword // #nosec G101 - test credential
assert.Equal(t, "condition", u.GetRawParam("protocol"))
- assert.Equal(t, "0.0.0.0", u.GetRawParam("host"))
- assert.Equal(t, "8080", u.GetRawParam("port"))
- assert.Equal(t, "test", u.GetRawParam(userName))
- assert.Equal(t, "test", u.GetRawParam(password))
+ assert.Equal(t, anyAddress, u.GetRawParam("host"))
+ assert.Equal(t, testPort8080, u.GetRawParam("port"))
+ assert.Equal(t, userName, u.GetRawParam(userName))
+ assert.Equal(t, testPassword, u.GetRawParam("password"))
assert.Equal(t, "/com.foo.BarService", u.GetRawParam("path"))
assert.Equal(t, "fastjson", u.GetRawParam("serialization"))
}
func TestURLToMap(t *testing.T) {
- u, _ :=
NewURL("condition://0.0.0.0:8080/com.foo.BarService?serialization=fastjson")
- u.Username = "test"
- u.Password = "test"
+ u, _ := NewURL("condition://" + anyAddress + ":" + testPort8080 +
"/com.foo.BarService?serialization=fastjson")
+ u.Username = userName
+ u.Password = testPassword // #nosec G101 - test credential
m := u.ToMap()
assert.Equal(t, 7, len(m))
assert.Equal(t, "condition", m["protocol"])
- assert.Equal(t, "0.0.0.0", m["host"])
- assert.Equal(t, "8080", m["port"])
- assert.Equal(t, "test", m[userName])
- assert.Equal(t, "test", m[password])
+ assert.Equal(t, anyAddress, m["host"])
+ assert.Equal(t, testPort8080, m["port"])
+ assert.Equal(t, userName, m[userName])
+ assert.Equal(t, testPassword, m["password"])
assert.Equal(t, "/com.foo.BarService", m["path"])
assert.Equal(t, "fastjson", m["serialization"])
}
@@ -396,12 +425,12 @@ func TestCompareURLEqualFunc(t *testing.T) {
// test Default
url1, _ :=
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000×tamp=1556509797245")
url2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000×tamp=155650979798")
assert.False(t, GetCompareURLEqualFunc()(url1, url2))
@@ -410,12 +439,12 @@ func TestCompareURLEqualFunc(t *testing.T) {
// test custom
url1, _ =
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000×tamp=1556509797245")
url2, _ =
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000×tamp=155650979798")
assert.True(t, GetCompareURLEqualFunc()(url1, url2,
constant.TimestampKey, constant.RemoteTimestampKey))
@@ -425,12 +454,12 @@ func TestCompareURLEqualFunc(t *testing.T) {
url1, _ =
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000")
url2, _ =
NewURL("dubbo://127.0.0.1:20000/com.ikurento.user.UserProvider?anyhost=true&" +
"application=BDTService&category=providers&default.timeout=10000&dubbo=dubbo-provider-golang-1.0.0&"
+
-
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.168.56.1&methods=GetUser%2C&"
+
+
"environment=dev&interface=com.ikurento.user.UserProvider&ip=192.0.2.56&methods=GetUser%2C&"
+
"module=dubbogo+user-info+server&org=ikurento.com&owner=ZX&pid=1447&revision=0.0.1&"
+
"side=provider&timeout=3000")
assert.True(t, GetCompareURLEqualFunc()(url1, url2))
@@ -648,3 +677,699 @@ func TestURLSetParamsMultiValue(t *testing.T) {
got3 := u2.GetParams()["only_empty"]
assert.ElementsMatch(t, []string{""}, got3)
}
+
+func TestRoleType(t *testing.T) {
+ assert.Equal(t, "consumers", RoleType(CONSUMER).String())
+ assert.Equal(t, "configurators", RoleType(CONFIGURATOR).String())
+ assert.Equal(t, "routers", RoleType(ROUTER).String())
+ assert.Equal(t, "providers", RoleType(PROVIDER).String())
+
+ assert.Equal(t, "consumer", RoleType(CONSUMER).Role())
+ assert.Equal(t, "", RoleType(CONFIGURATOR).Role())
+ assert.Equal(t, "routers", RoleType(ROUTER).Role())
+ assert.Equal(t, "provider", RoleType(PROVIDER).Role())
+}
+
+func TestJavaClassName(t *testing.T) {
+ u := &URL{}
+ assert.Equal(t, "org.apache.dubbo.common.URL", u.JavaClassName())
+}
+
+func TestWithInterface(t *testing.T) {
+ u := NewURLWithOptions(WithInterface("com.test.Service"))
+ assert.Equal(t, "com.test.Service", u.GetParam(constant.InterfaceKey,
""))
+}
+
+func TestWithLocation(t *testing.T) {
+ // WithLocation sets Location, but NewURLWithOptions overwrites it with
Ip:Port
+ // So we need to set Ip and Port as well, or test the option directly
+ u := &URL{}
+ WithLocation(docIP1 + ":" + testPort8080)(u)
+ assert.Equal(t, docIP1+":"+testPort8080, u.Location)
+
+ // When using NewURLWithOptions with Ip and Port
+ u2 := NewURLWithOptions(WithIp(docIP1), WithPort(testPort8080))
+ assert.Equal(t, docIP1+":"+testPort8080, u2.Location)
+}
+
+func TestWithToken(t *testing.T) {
+ // empty token
+ u1 := NewURLWithOptions(WithToken(""))
+ assert.Equal(t, "", u1.GetParam(constant.TokenKey, ""))
+
+ // custom token
+ u2 := NewURLWithOptions(WithToken("my-token"))
+ assert.Equal(t, "my-token", u2.GetParam(constant.TokenKey, ""))
+
+ // "true" generates UUID
+ u3 := NewURLWithOptions(WithToken("true"))
+ token := u3.GetParam(constant.TokenKey, "")
+ assert.NotEmpty(t, token)
+ assert.NotEqual(t, "true", token)
+
+ // "default" generates UUID
+ u4 := NewURLWithOptions(WithToken("default"))
+ token2 := u4.GetParam(constant.TokenKey, "")
+ assert.NotEmpty(t, token2)
+ assert.NotEqual(t, "default", token2)
+
+ // "TRUE" (uppercase) generates UUID
+ u5 := NewURLWithOptions(WithToken("TRUE"))
+ token3 := u5.GetParam(constant.TokenKey, "")
+ assert.NotEmpty(t, token3)
+ assert.NotEqual(t, "TRUE", token3)
+}
+
+func TestWithWeight(t *testing.T) {
+ // positive weight
+ u1 := NewURLWithOptions(WithWeight(100))
+ assert.Equal(t, "100", u1.GetParam(constant.WeightKey, ""))
+
+ // zero weight (should not be set)
+ u2 := NewURLWithOptions(WithWeight(0))
+ assert.Equal(t, "", u2.GetParam(constant.WeightKey, ""))
+
+ // negative weight (should not be set)
+ u3 := NewURLWithOptions(WithWeight(-1))
+ assert.Equal(t, "", u3.GetParam(constant.WeightKey, ""))
+}
+
+func TestMatchKey(t *testing.T) {
+ assert.Equal(t, "com.test.Service:dubbo", MatchKey("com.test.Service",
"dubbo"))
+ assert.Equal(t, ":http", MatchKey("", "http"))
+}
+
+func TestURLGroupInterfaceVersion(t *testing.T) {
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&group=test-group&version=1.0.0")
+ assert.Equal(t, "test-group", u.Group())
+ assert.Equal(t, "com.test.Service", u.Interface())
+ assert.Equal(t, "1.0.0", u.Version())
+}
+
+func TestURLAddress(t *testing.T) {
+ u1 := &URL{Ip: docIP1, Port: testPort8080}
+ assert.Equal(t, docIP1+":"+testPort8080, u1.Address())
+
+ u2 := &URL{Ip: docIP1, Port: ""}
+ assert.Equal(t, docIP1, u2.Address())
+}
+
+func TestURLKey(t *testing.T) {
+ // #nosec G101 - test credential for URL key test
+ u, _ := NewURL("dubbo://" + userName + ":" + testPassword + "@" +
loopbackAddress + ":" + testPort +
"/com.test.Service?interface=com.test.Service&group=g1&version=1.0")
+ key := u.Key()
+ assert.Contains(t, key, "dubbo://")
+ assert.Contains(t, key, userName+":"+testPassword+"@")
+ assert.Contains(t, key, "interface=com.test.Service")
+ assert.Contains(t, key, "group=g1")
+ assert.Contains(t, key, "version=1.0")
+}
+
+func TestGetCacheInvokerMapKey(t *testing.T) {
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&group=g1&version=1.0×tamp=12345")
+ key := u.GetCacheInvokerMapKey()
+ assert.Contains(t, key, "interface=com.test.Service")
+ assert.Contains(t, key, "group=g1")
+ assert.Contains(t, key, "version=1.0")
+ assert.Contains(t, key, "timestamp=12345")
+}
+
+func TestServiceKey(t *testing.T) {
+ // with group and version
+ assert.Equal(t, "group/interface:version", ServiceKey("interface",
"group", "version"))
+
+ // without group
+ assert.Equal(t, "interface:version", ServiceKey("interface", "",
"version"))
+
+ // without version
+ assert.Equal(t, "group/interface", ServiceKey("interface", "group", ""))
+
+ // version 0.0.0 should be ignored
+ assert.Equal(t, "group/interface", ServiceKey("interface", "group",
"0.0.0"))
+
+ // empty interface
+ assert.Equal(t, "", ServiceKey("", "group", "version"))
+
+ // URL method
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&group=g1&version=1.0")
+ assert.Equal(t, "g1/com.test.Service:1.0", u.ServiceKey())
+}
+
+func TestEncodedServiceKey(t *testing.T) {
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&group=g1&version=1.0")
+ encoded := u.EncodedServiceKey()
+ assert.Equal(t, "g1*com.test.Service:1.0", encoded)
+}
+
+func TestServiceWithSubURL(t *testing.T) {
+ subURL, _ := NewURL("dubbo://127.0.0.1:20000?interface=com.sub.Service")
+ u := &URL{SubURL: subURL}
+ assert.Equal(t, "com.sub.Service", u.Service())
+
+ // empty SubURL interface
+ subURL2 := &URL{}
+ u2 := &URL{SubURL: subURL2, Path: "/com.path.Service"}
+ assert.Equal(t, "com.path.Service", u2.Service())
+
+ // no SubURL, no interface param
+ u3 := &URL{Path: "/com.path.Service"}
+ assert.Equal(t, "com.path.Service", u3.Service())
+}
+
+func TestAddParam(t *testing.T) {
+ u := &URL{}
+ u.AddParam("key1", "value1")
+ assert.Equal(t, "value1", u.GetParam("key1", ""))
+
+ // add another value to same key
+ u.AddParam("key1", "value2")
+ params := u.GetParams()
+ assert.Equal(t, 2, len(params["key1"]))
+}
+
+func TestAddParamAvoidNil(t *testing.T) {
+ u := &URL{}
+ u.AddParamAvoidNil("key1", "value1")
+ assert.Equal(t, "value1", u.GetParam("key1", ""))
+}
+
+func TestDelParam(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?key1=value1&key2=value2")
+ u.DelParam("key1")
+ assert.Equal(t, "", u.GetParam("key1", ""))
+ assert.Equal(t, "value2", u.GetParam("key2", ""))
+
+ // delete from nil params
+ u2 := &URL{}
+ u2.DelParam("key") // should not panic
+}
+
+func TestGetNonDefaultParam(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?key1=value1")
+
+ v, ok := u.GetNonDefaultParam("key1")
+ assert.True(t, ok)
+ assert.Equal(t, "value1", v)
+
+ v, ok = u.GetNonDefaultParam("nonexistent")
+ assert.False(t, ok)
+ assert.Equal(t, "", v)
+}
+
+func TestRangeParams(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?key1=value1&key2=value2")
+ count := 0
+ u.RangeParams(func(key, value string) bool {
+ count++
+ return true
+ })
+ assert.Equal(t, 2, count)
+
+ // test early break
+ count = 0
+ u.RangeParams(func(key, value string) bool {
+ count++
+ return false // break after first
+ })
+ assert.Equal(t, 1, count)
+}
+
+func TestGetParamInt32(t *testing.T) {
+ params := url.Values{}
+ params.Set("key", "123")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ v := u.GetParamInt32("key", 0)
+ assert.Equal(t, int32(123), v)
+
+ // invalid value
+ u2 := &URL{}
+ v2 := u2.GetParamInt32("key", 99)
+ assert.Equal(t, int32(99), v2)
+}
+
+func TestGetParamByIntValue(t *testing.T) {
+ params := url.Values{}
+ params.Set("key", "456")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ v := u.GetParamByIntValue("key", 0)
+ assert.Equal(t, 456, v)
+
+ // invalid value
+ u2 := &URL{}
+ v2 := u2.GetParamByIntValue("key", 99)
+ assert.Equal(t, 99, v2)
+}
+
+func TestGetMethodParamIntValue(t *testing.T) {
+ params := url.Values{}
+ params.Set("methods.GetValue.timeout", "100")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ v := u.GetMethodParamIntValue("GetValue", "timeout", 0)
+ assert.Equal(t, 100, v)
+
+ // missing method param
+ v2 := u.GetMethodParamIntValue("OtherMethod", "timeout", 50)
+ assert.Equal(t, 50, v2)
+}
+
+func TestGetMethodParamInt64(t *testing.T) {
+ params := url.Values{}
+ params.Set("methods.GetValue.timeout", "200")
+ params.Set("timeout", "100")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ // method param exists
+ v := u.GetMethodParamInt64("GetValue", "timeout", 0)
+ assert.Equal(t, int64(200), v)
+
+ // method param not exists, fallback to global
+ v2 := u.GetMethodParamInt64("OtherMethod", "timeout", 0)
+ assert.Equal(t, int64(100), v2)
+
+ // neither exists
+ v3 := u.GetMethodParamInt64("OtherMethod", "retries", 5)
+ assert.Equal(t, int64(5), v3)
+}
+
+func TestGetParamDuration(t *testing.T) {
+ params := url.Values{}
+ params.Set("timeout", "5s")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ d := u.GetParamDuration("timeout", "1s")
+ assert.Equal(t, 5*time.Second, d)
+
+ // invalid duration, returns 3s default
+ params2 := url.Values{}
+ params2.Set("timeout", "invalid")
+ u2 := &URL{}
+ u2.SetParams(params2)
+ d2 := u2.GetParamDuration("timeout", "invalid")
+ assert.Equal(t, 3*time.Second, d2)
+
+ // missing param with valid default
+ u3 := &URL{}
+ d3 := u3.GetParamDuration("timeout", "2s")
+ assert.Equal(t, 2*time.Second, d3)
+}
+
+func TestCloneExceptParams(t *testing.T) {
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=value2&key3=value3")
+
+ excludeSet := gxset.NewSet("key1", "key3")
+ cloned := u.CloneExceptParams(excludeSet)
+
+ assert.Equal(t, "", cloned.GetParam("key1", ""))
+ assert.Equal(t, "value2", cloned.GetParam("key2", ""))
+ assert.Equal(t, "", cloned.GetParam("key3", ""))
+ assert.Equal(t, "dubbo", cloned.Protocol)
+}
+
+func TestCloneWithParams(t *testing.T) {
+ // #nosec G101 - test credential for URL clone test
+ u, _ := NewURL("dubbo://" + userName + ":" + testPassword + "@" +
loopbackAddress + ":" + testPort +
"/com.test.Service?key1=value1&key2=value2&key3=value3")
+ u.Methods = []string{"method1", "method2"}
+
+ cloned := u.CloneWithParams([]string{"key1", "key3"})
+
+ assert.Equal(t, "value1", cloned.GetParam("key1", ""))
+ assert.Equal(t, "", cloned.GetParam("key2", ""))
+ assert.Equal(t, "value3", cloned.GetParam("key3", ""))
+ assert.Equal(t, "dubbo", cloned.Protocol)
+ assert.Equal(t, userName, cloned.Username)
+ assert.Equal(t, testPassword, cloned.Password)
+ assert.Equal(t, []string{"method1", "method2"}, cloned.Methods)
+}
+
+func TestURLCompare(t *testing.T) {
+ u1, _ := NewURL("dubbo://127.0.0.1:20000/a.Service")
+ u2, _ := NewURL("dubbo://127.0.0.1:20000/b.Service")
+ u3, _ := NewURL("dubbo://127.0.0.1:20000/a.Service")
+
+ assert.Equal(t, -1, u1.Compare(u2))
+ assert.Equal(t, 1, u2.Compare(u1))
+ assert.Equal(t, 0, u1.Compare(u3))
+}
+
+func TestRangeAttributes(t *testing.T) {
+ u := &URL{}
+ u.SetAttribute("attr1", "value1")
+ u.SetAttribute("attr2", 123)
+
+ count := 0
+ u.RangeAttributes(func(key string, value any) bool {
+ count++
+ return true
+ })
+ assert.Equal(t, 2, count)
+
+ // test early break
+ count = 0
+ u.RangeAttributes(func(key string, value any) bool {
+ count++
+ return false
+ })
+ assert.Equal(t, 1, count)
+}
+
+func TestURLSlice(t *testing.T) {
+ u1, _ := NewURL("dubbo://127.0.0.1:20000/c.Service")
+ u2, _ := NewURL("dubbo://127.0.0.1:20000/a.Service")
+ u3, _ := NewURL("dubbo://127.0.0.1:20000/b.Service")
+
+ slice := URLSlice{u1, u2, u3}
+
+ assert.Equal(t, 3, slice.Len())
+ assert.True(t, slice.Less(1, 2)) // a < b
+ assert.False(t, slice.Less(0, 2)) // c > b
+
+ slice.Swap(0, 1)
+ assert.Equal(t, u2, slice[0])
+ assert.Equal(t, u1, slice[1])
+}
+
+func TestIsEquals(t *testing.T) {
+ u1, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=value2")
+ u2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=value2")
+ u3, _ :=
NewURL("dubbo://127.0.0.1:20001/com.test.Service?key1=value1&key2=value2")
+ u4, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=different")
+
+ // equal URLs
+ assert.True(t, IsEquals(u1, u2))
+
+ // different port
+ assert.False(t, IsEquals(u1, u3))
+
+ // different param value
+ assert.False(t, IsEquals(u1, u4))
+
+ // with excludes
+ assert.True(t, IsEquals(u1, u4, "key2"))
+
+ // nil checks
+ assert.False(t, IsEquals(nil, u1))
+ assert.False(t, IsEquals(u1, nil))
+}
+
+func TestGetSubscribeName(t *testing.T) {
+ u, _ :=
NewURL("dubbo://127.0.0.1:20000?interface=com.test.Service&version=1.0&group=test")
+ name := GetSubscribeName(u)
+ assert.Contains(t, name, "providers")
+ assert.Contains(t, name, "com.test.Service")
+ assert.Contains(t, name, "1.0")
+ assert.Contains(t, name, "test")
+}
+
+func TestNewURLEmptyString(t *testing.T) {
+ u, err := NewURL("")
+ assert.NoError(t, err)
+ assert.NotNil(t, u)
+ assert.Equal(t, "", u.Protocol)
+}
+
+func TestNewURLWithCredentials(t *testing.T) {
+ // #nosec G101 - test credential for URL parsing test
+ u, err := NewURL("dubbo://" + userName + ":" + testPassword + "@" +
loopbackAddress + ":" + testPort + "/com.test.Service")
+ assert.NoError(t, err)
+ assert.Equal(t, userName, u.Username)
+ assert.Equal(t, testPassword, u.Password)
+}
+
+func TestURLStringWithAuth(t *testing.T) {
+ // #nosec G101 - test credential for URL string test
+ u, _ := NewURL("dubbo://" + userName + ":" + testPassword + "@" +
loopbackAddress + ":" + testPort + "/com.test.Service?key=value")
+ str := u.String()
+ assert.Contains(t, str, userName+":"+testPassword+"@")
+}
+
+func TestToMapWithoutPort(t *testing.T) {
+ u := &URL{
+ Protocol: "dubbo",
+ Location: "127.0.0.1",
+ Path: "/com.test.Service",
+ }
+ m := u.ToMap()
+ assert.Equal(t, "0", m["port"])
+}
+
+func TestToMapEmpty(t *testing.T) {
+ u := &URL{}
+ m := u.ToMap()
+ assert.Nil(t, m)
+}
+
+func TestMergeURLWithAttributes(t *testing.T) {
+ u1, _ := NewURL("dubbo://127.0.0.1:20000?key1=value1")
+ u1.SetAttribute("attr1", "attrValue1")
+
+ u2, _ := NewURL("dubbo://127.0.0.1:20001?key2=value2")
+ u2.SetAttribute("attr2", "attrValue2")
+ u2.Methods = []string{"method1"}
+
+ merged := u1.MergeURL(u2)
+
+ // attributes should be merged
+ v1, ok1 := merged.GetAttribute("attr1")
+ assert.True(t, ok1)
+ assert.Equal(t, "attrValue1", v1)
+
+ v2, ok2 := merged.GetAttribute("attr2")
+ assert.True(t, ok2)
+ assert.Equal(t, "attrValue2", v2)
+}
+
+func TestURLEqualWithCategory(t *testing.T) {
+ // test category matching with RemoveValuePrefix
+ u1, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&category=providers")
+ u2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&category=-providers")
+ assert.False(t, u1.URLEqual(u2))
+
+ // category contains target - u3 has category that contains u1's
category
+ u3, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&category=providers,consumers")
+ // u1 checks if u3's category matches - u3 has "providers,consumers"
which contains "providers"
+ assert.True(t, u3.URLEqual(u1))
+}
+
+func TestNewURLWithRegistryGroup(t *testing.T) {
+ u, err :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?registry.group=mygroup")
+ assert.NoError(t, err)
+ assert.Contains(t, u.PrimitiveURL, "mygroup")
+}
+
+func TestColonSeparatedKeyEmpty(t *testing.T) {
+ u := &URL{}
+ assert.Equal(t, "", u.ColonSeparatedKey())
+}
+
+func TestColonSeparatedKeyWithVersion000(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000")
+ u.AddParam(constant.InterfaceKey, "com.test.Service")
+ u.AddParam(constant.VersionKey, "0.0.0")
+ u.AddParam(constant.GroupKey, "group1")
+
+ // version 0.0.0 should be treated as empty
+ assert.Equal(t, "com.test.Service::group1", u.ColonSeparatedKey())
+}
+
+// Additional edge case tests for better coverage
+
+func TestNewURLParseError(t *testing.T) {
+ // Test invalid URL that causes parse error
+ _, err := NewURL("://invalid")
+ assert.Error(t, err)
+}
+
+func TestNewURLWithInvalidQuery(t *testing.T) {
+ // URL with special characters that need escaping
+ u, err :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key=value%20with%20space")
+ assert.NoError(t, err)
+ assert.Equal(t, "value with space", u.GetParam("key", ""))
+}
+
+func TestURLStringWithoutAuth(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000/com.test.Service?key=value")
+ str := u.String()
+ assert.Contains(t, str, "dubbo://127.0.0.1:20000")
+ assert.NotContains(t, str, "@")
+}
+
+func TestGetParamAndDecodedError(t *testing.T) {
+ u := &URL{}
+ params := url.Values{}
+ params.Set("rule", "invalid-base64!!!")
+ u.SetParams(params)
+
+ _, err := u.GetParamAndDecoded("rule")
+ assert.Error(t, err)
+}
+
+func TestIsEqualsWithDifferentMapLength(t *testing.T) {
+ u1, _ := NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1")
+ u2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=value2")
+ assert.False(t, IsEquals(u1, u2))
+}
+
+func TestIsEqualsWithMissingKey(t *testing.T) {
+ u1, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key2=value2")
+ u2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?key1=value1&key3=value3")
+ assert.False(t, IsEquals(u1, u2))
+}
+
+func TestIsEqualsBothNil(t *testing.T) {
+ // Both nil should not panic - but current implementation would panic
+ // This tests the nil check logic
+ var u1 *URL = nil
+ var u2 *URL = nil
+ // Note: IsEquals(nil, nil) would panic due to nil pointer dereference
+ // This is expected behavior based on current implementation
+ assert.False(t, IsEquals(u1, &URL{}))
+ assert.False(t, IsEquals(&URL{}, u2))
+}
+
+func TestMergeURLWithMethodParams(t *testing.T) {
+ referenceUrlParams := url.Values{}
+ referenceUrlParams.Set(constant.LoadbalanceKey, "random")
+ referenceUrlParams.Set("methods.testMethod."+constant.TimeoutKey,
"5000")
+
+ serviceUrlParams := url.Values{}
+ serviceUrlParams.Set(constant.ClusterKey, "failover")
+
+ referenceUrl, _ := NewURL("mock1://127.0.0.1:1111",
WithParams(referenceUrlParams), WithMethods([]string{"testMethod"}))
+ serviceUrl, _ := NewURL("mock2://127.0.0.1:20000",
WithParams(serviceUrlParams))
+
+ mergedUrl := serviceUrl.MergeURL(referenceUrl)
+ assert.Equal(t, "random", mergedUrl.GetParam(constant.LoadbalanceKey,
""))
+ assert.Equal(t, "5000",
mergedUrl.GetParam("methods.testMethod."+constant.TimeoutKey, ""))
+}
+
+func TestURLWithPathAlreadyHasSlash(t *testing.T) {
+ u := NewURLWithOptions(WithPath("/com.test.Service"))
+ assert.Equal(t, "/com.test.Service", u.Path)
+
+ u2 := NewURLWithOptions(WithPath("com.test.Service"))
+ assert.Equal(t, "/com.test.Service", u2.Path)
+}
+
+func TestServiceKeyWithEmptyInterface(t *testing.T) {
+ u := &URL{}
+ assert.Equal(t, "", u.ServiceKey())
+}
+
+func TestGetRawParamDefault(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?customKey=customValue")
+ assert.Equal(t, "customValue", u.GetRawParam("customKey"))
+ assert.Equal(t, "", u.GetRawParam("nonexistent"))
+}
+
+func TestCloneWithNilParams(t *testing.T) {
+ u := &URL{
+ Protocol: "dubbo",
+ Ip: "127.0.0.1",
+ Port: "20000",
+ }
+ cloned := u.Clone()
+ assert.Equal(t, "dubbo", cloned.Protocol)
+ assert.Equal(t, "127.0.0.1", cloned.Ip)
+}
+
+func TestCloneWithAttributes(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000")
+ u.SetAttribute("key1", "value1")
+ u.SetAttribute("key2", 123)
+
+ cloned := u.Clone()
+
+ v1, ok1 := cloned.GetAttribute("key1")
+ assert.True(t, ok1)
+ assert.Equal(t, "value1", v1)
+
+ v2, ok2 := cloned.GetAttribute("key2")
+ assert.True(t, ok2)
+ assert.Equal(t, 123, v2)
+}
+
+func TestSetParamsWithEmptySlice(t *testing.T) {
+ u := &URL{}
+ params := url.Values{}
+ params["emptyKey"] = []string{}
+
+ u.SetParams(params)
+ // Empty slice should delete the key
+ assert.Equal(t, "", u.GetParam("emptyKey", ""))
+}
+
+func TestReplaceParams(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?key1=value1")
+
+ newParams := url.Values{}
+ newParams.Set("key2", "value2")
+
+ u.ReplaceParams(newParams)
+
+ assert.Equal(t, "", u.GetParam("key1", ""))
+ assert.Equal(t, "value2", u.GetParam("key2", ""))
+}
+
+func TestURLEqualWithDifferentIpPort(t *testing.T) {
+ // URLEqual ignores IP and Port differences
+ u1, _ := NewURL("dubbo://" + loopbackAddress + ":" + testPort +
"/com.test.Service?interface=com.test.Service")
+ u2, _ := NewURL("dubbo://" + docIP1 + ":" + testPort30000 +
"/com.test.Service?interface=com.test.Service")
+ assert.True(t, u1.URLEqual(u2))
+}
+
+func TestIsMatchCategoryWithEmptyCategory2(t *testing.T) {
+ u1, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service&category=providers")
+ u2, _ :=
NewURL("dubbo://127.0.0.1:20000/com.test.Service?interface=com.test.Service")
+ // When category2 is empty, it should match DefaultCategory
+ assert.True(t, u1.URLEqual(u2))
+}
+
+func TestNewURLWithLocationWithoutPort(t *testing.T) {
+ u, err := NewURL("dubbo://hostname/com.test.Service")
+ assert.NoError(t, err)
+ assert.Equal(t, "hostname", u.Location)
+ assert.Equal(t, "", u.Port)
+}
+
+func TestGetParamBoolInvalidValue(t *testing.T) {
+ params := url.Values{}
+ params.Set("key", "notabool")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ v := u.GetParamBool("key", true)
+ assert.Equal(t, true, v) // returns default on parse error
+}
+
+func TestGetParamIntInvalidValue(t *testing.T) {
+ params := url.Values{}
+ params.Set("key", "notanint")
+
+ u := &URL{}
+ u.SetParams(params)
+
+ v := u.GetParamInt("key", 100)
+ assert.Equal(t, int64(100), v) // returns default on parse error
+}
+
+func TestAppendParamWithEmptyValue(t *testing.T) {
+ u, _ := NewURL("dubbo://127.0.0.1:20000?interface=com.test.Service")
+ name := GetSubscribeName(u)
+ // Should handle empty version and group
+ assert.Contains(t, name, "providers")
+ assert.Contains(t, name, "com.test.Service")
+}
diff --git a/filter/hystrix/filter_test.go b/filter/hystrix/filter_test.go
index 90b329c73..404dcf53a 100644
--- a/filter/hystrix/filter_test.go
+++ b/filter/hystrix/filter_test.go
@@ -179,6 +179,8 @@ func TestHystrixFilterInvokeCircuitBreak(t *testing.T) {
hystrix.Flush()
hf := &Filter{COrP: true}
resChan := make(chan result.Result, 50)
+ configLoadMutex.Lock()
+ defer configLoadMutex.Unlock()
for i := 0; i < 50; i++ {
go func() {
testUrl, err := common.NewURL(
@@ -207,6 +209,8 @@ func TestHystrixFilterInvokeCircuitBreakOmitException(t
*testing.T) {
regs := []*regexp.Regexp{reg}
hf := &Filter{res: map[string][]*regexp.Regexp{"": regs}, COrP: true}
resChan := make(chan result.Result, 50)
+ configLoadMutex.Lock()
+ defer configLoadMutex.Unlock()
for i := 0; i < 50; i++ {
go func() {
testUrl, err := common.NewURL(