This is an automated email from the ASF dual-hosted git repository. alexstocks pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
commit 1b54257ef397d9321fddf5398b7c2f966b67e1ec Author: Akashisang <[email protected]> AuthorDate: Mon Dec 22 07:42:42 2025 +0800 test: add unit test for server/action.go server/compat.go (#3132) * test: add unit test for server/ --- server/action_test.go | 675 ++++++++++++++++++++++++++++++++++++++++++++++++++ server/compat_test.go | 324 ++++++++++++++++++++++++ 2 files changed, 999 insertions(+) diff --git a/server/action_test.go b/server/action_test.go new file mode 100644 index 000000000..17c29f9e6 --- /dev/null +++ b/server/action_test.go @@ -0,0 +1,675 @@ +/* + * 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 server + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" + + "go.uber.org/atomic" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/config" + "dubbo.apache.org/dubbo-go/v3/global" + "dubbo.apache.org/dubbo-go/v3/protocol/base" +) + +// Test Prefix method +func TestPrefix(t *testing.T) { + svcOpts := &ServiceOptions{ + Id: "com.example.TestService", + } + + prefix := svcOpts.Prefix() + assert.Equal(t, "dubbo.service.com.example.TestService", prefix) +} + +// Test InitExported +func TestInitExported(t *testing.T) { + svcOpts := &ServiceOptions{ + exported: atomic.NewBool(true), + } + + svcOpts.InitExported() + assert.False(t, svcOpts.exported.Load()) +} + +// Test IsExport returns false initially +func TestIsExportFalse(t *testing.T) { + svcOpts := &ServiceOptions{ + exported: atomic.NewBool(false), + } + + assert.False(t, svcOpts.IsExport()) +} + +// Test IsExport returns true when exported +func TestIsExportTrue(t *testing.T) { + svcOpts := &ServiceOptions{ + exported: atomic.NewBool(true), + } + + assert.True(t, svcOpts.IsExport()) +} + +// Test check with valid TpsLimiter +func TestCheckValidTpsLimiter(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimiter: "", + }, + } + + err := svcOpts.check() + assert.NoError(t, err) +} + +// Test check with valid TpsLimitRate +func TestCheckValidTpsLimitRate(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitRate: "1000", + }, + } + + err := svcOpts.check() + assert.NoError(t, err) +} + +// Test check with invalid TpsLimitRate +func TestCheckInvalidTpsLimitRate(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitRate: "invalid", + }, + } + + err := svcOpts.check() + assert.Error(t, err) +} + +// Test check with negative TpsLimitRate +func TestCheckNegativeTpsLimitRate(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitRate: "-1", + }, + } + + err := svcOpts.check() + assert.Error(t, err) +} + +// Test check with valid TpsLimitInterval +func TestCheckValidTpsLimitInterval(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitInterval: "1000", + }, + } + + err := svcOpts.check() + assert.NoError(t, err) +} + +// Test check with invalid TpsLimitInterval +func TestCheckInvalidTpsLimitInterval(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitInterval: "invalid", + }, + } + + err := svcOpts.check() + assert.Error(t, err) +} + +// Test check with negative TpsLimitInterval +func TestCheckNegativeTpsLimitInterval(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitInterval: "-1", + }, + } + + err := svcOpts.check() + assert.Error(t, err) +} + +// Test Implement +func TestImplement(t *testing.T) { + svcOpts := &ServiceOptions{} + mockService := &mockRPCService{} + + svcOpts.Implement(mockService) + + assert.Equal(t, mockService, svcOpts.rpcService) +} + +// Test Unexport when not exported +func TestUnexportNotExported(t *testing.T) { + svcOpts := &ServiceOptions{ + unexported: atomic.NewBool(false), + exported: atomic.NewBool(false), + exporters: []base.Exporter{}, + } + + svcOpts.Unexport() + assert.False(t, svcOpts.exported.Load()) + assert.False(t, svcOpts.unexported.Load()) +} + +// Test Unexport when already unexported +func TestUnexportAlreadyUnexported(t *testing.T) { + svcOpts := &ServiceOptions{ + unexported: atomic.NewBool(true), + exported: atomic.NewBool(false), + exporters: []base.Exporter{}, + } + + svcOpts.Unexport() + assert.True(t, svcOpts.unexported.Load()) +} + +// Test removeDuplicateElement +func TestRemoveDuplicateElement(t *testing.T) { + items := []string{"a", "b", "a", "c", "b", ""} + result := removeDuplicateElement(items) + + assert.Len(t, result, 3) + assert.Contains(t, result, "a") + assert.Contains(t, result, "b") + assert.Contains(t, result, "c") +} + +// Test removeDuplicateElement with empty slice +func TestRemoveDuplicateElementEmpty(t *testing.T) { + items := []string{} + result := removeDuplicateElement(items) + + assert.Len(t, result, 0) +} + +// Test removeDuplicateElement with only empty strings +func TestRemoveDuplicateElementOnlyEmpty(t *testing.T) { + items := []string{"", "", ""} + result := removeDuplicateElement(items) + + assert.Len(t, result, 0) +} + +// Test removeDuplicateElement with mixed content +func TestRemoveDuplicateElementMixed(t *testing.T) { + items := []string{"reg1", "reg2", "reg1", "", "reg3", ""} + result := removeDuplicateElement(items) + + assert.Len(t, result, 3) + assert.Contains(t, result, "reg1") + assert.Contains(t, result, "reg2") + assert.Contains(t, result, "reg3") +} + +// Test getRegistryIds +func TestGetRegistryIds(t *testing.T) { + registries := map[string]*global.RegistryConfig{ + "reg1": {Protocol: "zookeeper"}, + "reg2": {Protocol: "nacos"}, + "reg3": {Protocol: "etcd"}, + } + + ids := getRegistryIds(registries) + + assert.Len(t, ids, 3) + assert.Contains(t, ids, "reg1") + assert.Contains(t, ids, "reg2") + assert.Contains(t, ids, "reg3") +} + +// Test getRegistryIds with empty map +func TestGetRegistryIdsEmpty(t *testing.T) { + registries := map[string]*global.RegistryConfig{} + + ids := getRegistryIds(registries) + + assert.Len(t, ids, 0) +} + +// Test GetExportedUrls when not exported +func TestGetExportedUrlsNotExported(t *testing.T) { + svcOpts := &ServiceOptions{ + exported: atomic.NewBool(false), + exporters: []base.Exporter{}, + } + + urls := svcOpts.GetExportedUrls() + assert.Nil(t, urls) +} + +// Test GetExportedUrls when exported with no exporters +func TestGetExportedUrlsExportedEmpty(t *testing.T) { + svcOpts := &ServiceOptions{ + exported: atomic.NewBool(true), + exporters: []base.Exporter{}, + } + + urls := svcOpts.GetExportedUrls() + assert.Len(t, urls, 0) +} + +// Test getUrlMap basic functionality +func TestGetUrlMapBasic(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Filter: "", + Params: map[string]string{}, + NotRegister: false, + Tag: "v1", + Group: "test-group", + Version: "1.0.0", + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + Organization: "test-org", + Module: "test-module", + Owner: "test-owner", + Environment: "test-env", + Version: "1.0.0", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{Enable: nil}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{Enable: nil}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.NotNil(t, urlMap) + assert.Equal(t, "com.example.Service", urlMap.Get(constant.InterfaceKey)) + assert.Equal(t, constant.ClusterKeyFailover, urlMap.Get(constant.ClusterKey)) + assert.Equal(t, constant.LoadBalanceKeyRoundRobin, urlMap.Get(constant.LoadbalanceKey)) + assert.Equal(t, "60", urlMap.Get(constant.WarmupKey)) + assert.Equal(t, "3", urlMap.Get(constant.RetriesKey)) + assert.Equal(t, constant.JSONSerialization, urlMap.Get(constant.SerializationKey)) +} + +// Test getUrlMap with custom params +func TestGetUrlMapWithParams(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + Params: map[string]string{ + "customKey": "customValue", + }, + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + Organization: "test-org", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.NotNil(t, urlMap) + assert.Equal(t, "customValue", urlMap.Get("customKey")) +} + +// Test getUrlMap with group and version +func TestGetUrlMapWithGroupAndVersion(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + Group: "test-group", + Version: "1.0.0", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "test-group", urlMap.Get(constant.GroupKey)) + assert.Equal(t, "1.0.0", urlMap.Get(constant.VersionKey)) +} + +// Test getUrlMap with methods +func TestGetUrlMapWithMethods(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + Methods: []*global.MethodConfig{ + { + Name: "testMethod", + LoadBalance: "random", + Retries: "5", + Weight: 200, + }, + }, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "random", urlMap.Get("methods.testMethod."+constant.LoadbalanceKey)) + assert.Equal(t, "5", urlMap.Get("methods.testMethod."+constant.RetriesKey)) + assert.Equal(t, "200", urlMap.Get("methods.testMethod."+constant.WeightKey)) +} + +// Test loadProtocol with matching IDs +func TestLoadProtocol(t *testing.T) { + protocols := map[string]*global.ProtocolConfig{ + "dubbo": { + Name: "dubbo", + Port: "20880", + }, + "triple": { + Name: "triple", + Port: "50051", + }, + } + + protocolIDs := []string{"dubbo", "triple"} + result := loadProtocol(protocolIDs, protocols) + + assert.Len(t, result, 2) +} + +// Test loadProtocol with partial matching +func TestLoadProtocolPartialMatch(t *testing.T) { + protocols := map[string]*global.ProtocolConfig{ + "dubbo": { + Name: "dubbo", + Port: "20880", + }, + "triple": { + Name: "triple", + Port: "50051", + }, + } + + protocolIDs := []string{"dubbo"} + result := loadProtocol(protocolIDs, protocols) + + assert.Len(t, result, 1) + assert.Equal(t, "dubbo", result[0].Name) +} + +// Test loadProtocol with no matching IDs +func TestLoadProtocolNoMatch(t *testing.T) { + protocols := map[string]*global.ProtocolConfig{ + "dubbo": { + Name: "dubbo", + Port: "20880", + }, + } + + protocolIDs := []string{"non-existent"} + result := loadProtocol(protocolIDs, protocols) + + assert.Len(t, result, 0) +} + +// Test setRegistrySubURL +func TestSetRegistrySubURL(t *testing.T) { + ivkURL, _ := common.NewURL("dubbo://localhost:20880/com.example.Service") + ivkURL.AddParam(constant.RegistryKey, "zookeeper") + ivkURL.AddParam(constant.RegistryTypeKey, "service_discovery") + + regURL, _ := common.NewURL("registry://zookeeper:2181") + regURL.AddParam(constant.RegistryKey, "zookeeper") + regURL.AddParam(constant.RegistryTypeKey, "service_discovery") + + setRegistrySubURL(ivkURL, regURL) + + assert.Equal(t, "zookeeper", ivkURL.GetParam(constant.RegistryKey, "")) + assert.Equal(t, "service_discovery", ivkURL.GetParam(constant.RegistryTypeKey, "")) + assert.NotNil(t, regURL.SubURL) +} + +// Test Unexport when exported +func TestUnexportWhenExported(t *testing.T) { + svcOpts := &ServiceOptions{ + unexported: atomic.NewBool(false), + exported: atomic.NewBool(true), + exporters: []base.Exporter{}, + } + + svcOpts.Unexport() + assert.False(t, svcOpts.exported.Load()) + assert.True(t, svcOpts.unexported.Load()) +} + +// Test check with multiple validation errors +func TestCheckMultipleErrors(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimitRate: "invalid", + TpsLimitInterval: "also-invalid", + }, + } + + err := svcOpts.check() + assert.Error(t, err) +} + +// Test getUrlMap with TPS limit config +func TestGetUrlMapWithTpsLimit(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + TpsLimiter: "default", + TpsLimitRate: "1000", + TpsLimitStrategy: "adaptive", + TpsLimitRejectedHandler: "abort", + TpsLimitInterval: "100", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "default", urlMap.Get(constant.TPSLimiterKey)) + assert.Equal(t, "1000", urlMap.Get(constant.TPSLimitRateKey)) + assert.Equal(t, "adaptive", urlMap.Get(constant.TPSLimitStrategyKey)) + assert.Equal(t, "abort", urlMap.Get(constant.TPSRejectedExecutionHandlerKey)) +} + +// Test getUrlMap with execute limit config +func TestGetUrlMapWithExecuteLimit(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + ExecuteLimit: "200", + ExecuteLimitRejectedHandler: "abort", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "200", urlMap.Get(constant.ExecuteLimitKey)) + assert.Equal(t, "abort", urlMap.Get(constant.ExecuteRejectedExecutionHandlerKey)) +} + +// Test getUrlMap with auth and param sign +func TestGetUrlMapWithAuthAndParamSign(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + Auth: "default", + ParamSign: "md5", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "default", urlMap.Get(constant.ServiceAuthKey)) + assert.Equal(t, "md5", urlMap.Get(constant.ParameterSignatureEnableKey)) +} + +// Test getUrlMap with access log +func TestGetUrlMapWithAccessLog(t *testing.T) { + svcOpts := &ServiceOptions{ + Service: &global.ServiceConfig{ + Interface: "com.example.Service", + AccessLog: "/var/log/access.log", + Cluster: constant.ClusterKeyFailover, + Loadbalance: constant.LoadBalanceKeyRoundRobin, + Warmup: "60", + Retries: "3", + Serialization: constant.JSONSerialization, + Params: map[string]string{}, + }, + Provider: &global.ProviderConfig{}, + applicationCompat: &config.ApplicationConfig{ + Name: "test-app", + }, + srvOpts: &ServerOptions{ + Metrics: &global.MetricsConfig{}, + Otel: &global.OtelConfig{ + TracingConfig: &global.OtelTraceConfig{}, + }, + }, + } + + urlMap := svcOpts.getUrlMap() + assert.Equal(t, "/var/log/access.log", urlMap.Get(constant.AccessLogFilterKey)) +} + +// Test postProcessConfig +func TestPostProcessConfig(t *testing.T) { + svcOpts := &ServiceOptions{} + url, _ := common.NewURL("dubbo://localhost:20880/test") + + svcOpts.postProcessConfig(url) + assert.NotNil(t, url) +} + +// Mock RPCService for testing +type mockRPCService struct{} + +func (m *mockRPCService) Invoke(methodName string, params []any, results []any) error { + return nil +} + +func (m *mockRPCService) Reference() string { + return "com.example.MockService" +} diff --git a/server/compat_test.go b/server/compat_test.go new file mode 100644 index 000000000..f58fe6f68 --- /dev/null +++ b/server/compat_test.go @@ -0,0 +1,324 @@ +/* + * 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 server + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/config" + "dubbo.apache.org/dubbo-go/v3/global" +) + +// Test compatApplicationConfig with all fields +func TestCompatApplicationConfigAll(t *testing.T) { + appCfg := &global.ApplicationConfig{ + Organization: "test-org", + Name: "test-app", + Module: "test-module", + Group: "test-group", + Version: "1.0.0", + Owner: "test-owner", + Environment: "test-env", + MetadataType: "remote", + Tag: "v1", + MetadataServicePort: "30880", + MetadataServiceProtocol: "triple", + } + + result := compatApplicationConfig(appCfg) + + assert.NotNil(t, result) + assert.Equal(t, "test-org", result.Organization) + assert.Equal(t, "test-app", result.Name) + assert.Equal(t, "test-module", result.Module) + assert.Equal(t, "test-group", result.Group) + assert.Equal(t, "1.0.0", result.Version) + assert.Equal(t, "test-owner", result.Owner) + assert.Equal(t, "test-env", result.Environment) + assert.Equal(t, "remote", result.MetadataType) + assert.Equal(t, "v1", result.Tag) + assert.Equal(t, "30880", result.MetadataServicePort) + assert.Equal(t, "triple", result.MetadataServiceProtocol) +} + +// Test compatApplicationConfig with partial fields +func TestCompatApplicationConfigPartial(t *testing.T) { + appCfg := &global.ApplicationConfig{ + Name: "test-app", + Version: "1.0.0", + } + + result := compatApplicationConfig(appCfg) + + assert.NotNil(t, result) + assert.Equal(t, "test-app", result.Name) + assert.Equal(t, "1.0.0", result.Version) + assert.Empty(t, result.Organization) + assert.Empty(t, result.Module) +} + +// Test compatApplicationConfig with empty config +func TestCompatApplicationConfigEmpty(t *testing.T) { + appCfg := &global.ApplicationConfig{} + + result := compatApplicationConfig(appCfg) + + assert.NotNil(t, result) + assert.Empty(t, result.Name) + assert.Empty(t, result.Organization) + assert.Empty(t, result.Module) + assert.Empty(t, result.Group) + assert.Empty(t, result.Version) + assert.Empty(t, result.Owner) + assert.Empty(t, result.Environment) + assert.Empty(t, result.MetadataType) + assert.Empty(t, result.Tag) + assert.Empty(t, result.MetadataServicePort) + assert.Empty(t, result.MetadataServiceProtocol) +} + +// Test compatRegistryConfig with all fields +func TestCompatRegistryConfigAll(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "zookeeper", + Timeout: "10s", + Group: "registry-group", + Namespace: "test-ns", + TTL: "60s", + Address: "localhost:2181", + Username: "admin", + Password: "password", + Simplified: true, + Preferred: true, + Zone: "zone1", + Weight: 100, + Params: map[string]string{"key": "value"}, + RegistryType: "service_discovery", + UseAsMetaReport: "true", + UseAsConfigCenter: "true", + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Equal(t, "zookeeper", result.Protocol) + assert.Equal(t, "10s", result.Timeout) + assert.Equal(t, "registry-group", result.Group) + assert.Equal(t, "test-ns", result.Namespace) + assert.Equal(t, "60s", result.TTL) + assert.Equal(t, "localhost:2181", result.Address) + assert.Equal(t, "admin", result.Username) + assert.Equal(t, "password", result.Password) + assert.True(t, result.Simplified) + assert.True(t, result.Preferred) + assert.Equal(t, "zone1", result.Zone) + assert.Equal(t, int64(100), result.Weight) + assert.Equal(t, "service_discovery", result.RegistryType) + assert.Equal(t, "true", result.UseAsMetaReport) + assert.Equal(t, "true", result.UseAsConfigCenter) + assert.NotNil(t, result.Params) + assert.Equal(t, "value", result.Params["key"]) +} + +// Test compatRegistryConfig with partial fields +func TestCompatRegistryConfigPartial(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "nacos", + Address: "localhost:8848", + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Equal(t, "nacos", result.Protocol) + assert.Equal(t, "localhost:8848", result.Address) + assert.Empty(t, result.Timeout) + assert.Empty(t, result.Group) +} + +// Test compatRegistryConfig with empty config +func TestCompatRegistryConfigEmpty(t *testing.T) { + regCfg := &global.RegistryConfig{} + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Empty(t, result.Protocol) + assert.Empty(t, result.Address) + assert.Empty(t, result.Timeout) + assert.Empty(t, result.Group) + assert.Empty(t, result.Namespace) + assert.Empty(t, result.TTL) + assert.Empty(t, result.Username) + assert.Empty(t, result.Password) + assert.False(t, result.Simplified) + assert.False(t, result.Preferred) +} + +// Test compatRegistryConfig with boolean fields +func TestCompatRegistryConfigBooleans(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "etcd", + Simplified: true, + Preferred: true, + UseAsMetaReport: "true", + UseAsConfigCenter: "false", + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.True(t, result.Simplified) + assert.True(t, result.Preferred) + assert.Equal(t, "true", result.UseAsMetaReport) + assert.Equal(t, "false", result.UseAsConfigCenter) +} + +// Test compatRegistryConfig with numeric fields +func TestCompatRegistryConfigNumeric(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "zookeeper", + Weight: 500, + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Equal(t, "zookeeper", result.Protocol) + assert.Equal(t, int64(500), result.Weight) +} + +// Test compatRegistryConfig with params +func TestCompatRegistryConfigWithParams(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "zookeeper", + Params: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Equal(t, 3, len(result.Params)) + assert.Equal(t, "value1", result.Params["key1"]) + assert.Equal(t, "value2", result.Params["key2"]) + assert.Equal(t, "value3", result.Params["key3"]) +} + +// Test compatApplicationConfig preserves all field types +func TestCompatApplicationConfigFieldPreservation(t *testing.T) { + appCfg := &global.ApplicationConfig{ + Name: "myapp", + Organization: "myorg", + Module: "mymodule", + Group: "mygroup", + Version: "2.0.0", + Owner: "owner", + Environment: "production", + MetadataType: "local", + Tag: "beta", + MetadataServicePort: "25555", + MetadataServiceProtocol: "grpc", + } + + result := compatApplicationConfig(appCfg) + + // Verify type conversion doesn't lose data + assert.IsType(t, (*config.ApplicationConfig)(nil), result) + assert.Equal(t, appCfg.Name, result.Name) + assert.Equal(t, appCfg.Organization, result.Organization) + assert.Equal(t, appCfg.Module, result.Module) +} + +// Test compatRegistryConfig preserves all field types +func TestCompatRegistryConfigFieldPreservation(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "consul", + Timeout: "30s", + Group: "consul-group", + Namespace: "consul-ns", + TTL: "120s", + Address: "localhost:8500", + Username: "consul-user", + Password: "consul-pass", + Simplified: true, + Preferred: false, + Zone: "zone-a", + Weight: 200, + Params: map[string]string{"consul-key": "consul-val"}, + RegistryType: "interface", + UseAsMetaReport: "true", + UseAsConfigCenter: "false", + } + + result := compatRegistryConfig(regCfg) + + // Verify type conversion doesn't lose data + assert.IsType(t, (*config.RegistryConfig)(nil), result) + assert.Equal(t, regCfg.Protocol, result.Protocol) + assert.Equal(t, regCfg.Timeout, result.Timeout) + assert.Equal(t, regCfg.Group, result.Group) +} + +// Test multiple conversions don't affect original +func TestCompatConfigsPreservesOriginal(t *testing.T) { + appCfg := &global.ApplicationConfig{ + Name: "original-app", + Version: "1.0.0", + } + + result1 := compatApplicationConfig(appCfg) + result2 := compatApplicationConfig(appCfg) + + assert.Equal(t, "original-app", appCfg.Name) + assert.Equal(t, "1.0.0", appCfg.Version) + + assert.Equal(t, result1.Name, result2.Name) + assert.Equal(t, result1.Version, result2.Version) + assert.Equal(t, "original-app", result1.Name) +} + +// Test compatRegistryConfig with special characters in params +func TestCompatRegistryConfigSpecialChars(t *testing.T) { + regCfg := &global.RegistryConfig{ + Protocol: "zookeeper", + Params: map[string]string{ + "special.key": "value with spaces", + "another-key": "value/with/slashes", + "key_with_colon": "value:with:colons", + "key.with.dots": "value.with.dots", + }, + } + + result := compatRegistryConfig(regCfg) + + assert.NotNil(t, result) + assert.Equal(t, "value with spaces", result.Params["special.key"]) + assert.Equal(t, "value/with/slashes", result.Params["another-key"]) + assert.Equal(t, "value:with:colons", result.Params["key_with_colon"]) + assert.Equal(t, "value.with.dots", result.Params["key.with.dots"]) +}
