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 725897ed9d01ce1ad351f49e74066d0cf51e5dff Author: Sirui Huang <[email protected]> AuthorDate: Sun Dec 21 10:55:20 2025 +0800 add the unit test (#3121) --- client/action_test.go | 99 ++++++++++++++++++++++++++ client/client_test.go | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) diff --git a/client/action_test.go b/client/action_test.go new file mode 100644 index 000000000..c6ef042a6 --- /dev/null +++ b/client/action_test.go @@ -0,0 +1,99 @@ +/* + * 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 client + +import ( + "os" + "strconv" + "testing" +) + +import ( + "github.com/stretchr/testify/require" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/global" +) + +func TestGetEnv(t *testing.T) { + key := "TEST_GET_ENV_KEY" + _ = os.Unsetenv(key) + require.Equal(t, "fallback", getEnv(key, "fallback")) + + require.NoError(t, os.Setenv(key, "value")) + defer os.Unsetenv(key) + require.Equal(t, "value", getEnv(key, "fallback")) +} + +func TestUpdateOrCreateMeshURL(t *testing.T) { + t.Run("mesh disabled", func(t *testing.T) { + refOpts := &ReferenceOptions{ + Reference: &global.ReferenceConfig{URL: ""}, + Consumer: &global.ConsumerConfig{MeshEnabled: false}, + } + updateOrCreateMeshURL(refOpts) + require.Equal(t, "", refOpts.Reference.URL) + }) + + t.Run("mesh enabled build url", func(t *testing.T) { + defer os.Unsetenv(constant.PodNamespaceEnvKey) + defer os.Unsetenv(constant.ClusterDomainKey) + require.NoError(t, os.Setenv(constant.PodNamespaceEnvKey, "ns")) + require.NoError(t, os.Setenv(constant.ClusterDomainKey, "cluster.local")) + + refOpts := &ReferenceOptions{ + Reference: &global.ReferenceConfig{ + Protocol: constant.TriProtocol, + ProvidedBy: "svc", + MeshProviderPort: 0, + }, + Consumer: &global.ConsumerConfig{MeshEnabled: true}, + } + updateOrCreateMeshURL(refOpts) + expected := "tri://svc.ns" + constant.SVC + "cluster.local:" + strconv.Itoa(constant.DefaultMeshPort) + require.Equal(t, expected, refOpts.Reference.URL) + }) + + t.Run("panic non tri protocol", func(t *testing.T) { + refOpts := &ReferenceOptions{ + Reference: &global.ReferenceConfig{ + Protocol: "dubbo", + ProvidedBy: "svc", + }, + Consumer: &global.ConsumerConfig{MeshEnabled: true}, + } + require.Panics(t, func() { + updateOrCreateMeshURL(refOpts) + }) + }) + + t.Run("panic empty providedBy", func(t *testing.T) { + refOpts := &ReferenceOptions{ + Reference: &global.ReferenceConfig{ + Protocol: constant.TriProtocol, + ProvidedBy: "", + }, + Consumer: &global.ConsumerConfig{MeshEnabled: true}, + } + require.Panics(t, func() { + updateOrCreateMeshURL(refOpts) + }) + }) +} diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 000000000..699f314bf --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,194 @@ +/* + * 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 client + +import ( + "context" + "errors" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/require" +) + +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/constant" + "dubbo.apache.org/dubbo-go/v3/protocol/base" + "dubbo.apache.org/dubbo-go/v3/protocol/result" +) + +type fakeInvoker struct { + lastCtx context.Context + lastInvocation base.Invocation + res result.Result +} + +func (f *fakeInvoker) GetURL() *common.URL { + return nil +} + +func (f *fakeInvoker) IsAvailable() bool { + return true +} + +func (f *fakeInvoker) Destroy() {} + +func (f *fakeInvoker) Invoke(ctx context.Context, inv base.Invocation) result.Result { + f.lastCtx = ctx + f.lastInvocation = inv + return f.res +} + +func TestClientDefinitionConnection(t *testing.T) { + var def ClientDefinition + conn, err := def.GetConnection() + require.Error(t, err) + require.Nil(t, conn) + + expectedConn := &Connection{} + def.SetConnection(expectedConn) + gotConn, err := def.GetConnection() + require.NoError(t, err) + require.Equal(t, expectedConn, gotConn) +} + +func TestGenerateInvocation(t *testing.T) { + resp := new(int) + opts := &CallOptions{RequestTimeout: "1s", Retries: "2"} + + inv, err := generateInvocation("Echo", []any{"foo", 1}, resp, constant.CallUnary, opts) + require.NoError(t, err) + + timeout, _ := inv.GetAttachment(constant.TimeoutKey) + retries, _ := inv.GetAttachment(constant.RetriesKey) + require.Equal(t, "1s", timeout) + require.Equal(t, "2", retries) + + attr, ok := inv.GetAttribute(constant.CallTypeKey) + require.True(t, ok) + require.Equal(t, constant.CallUnary, attr) + + require.Equal(t, []any{"foo", 1}, inv.Arguments()) + require.Equal(t, resp, inv.Reply()) + require.Equal(t, []any{"foo", 1, resp}, inv.ParameterRawValues()) +} + +func TestConnectionCallPassesOptions(t *testing.T) { + invRes := &result.RPCResult{} + invoker := &fakeInvoker{res: invRes} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + resp := new(string) + res, err := conn.call(context.Background(), []any{"req"}, resp, "Ping", constant.CallUnary, WithCallRequestTimeout(1500*time.Millisecond), WithCallRetries(3)) + require.NoError(t, err) + require.Equal(t, invRes, res) + + inv := invoker.lastInvocation + timeout, _ := inv.GetAttachment(constant.TimeoutKey) + retries, _ := inv.GetAttachment(constant.RetriesKey) + require.Equal(t, "1.5s", timeout) + require.Equal(t, "3", retries) + + requireCallType(t, inv, constant.CallUnary) + require.Equal(t, []any{"req", resp}, inv.ParameterRawValues()) +} + +func TestCallUnary(t *testing.T) { + t.Run("success", func(t *testing.T) { + invoker := &fakeInvoker{res: &result.RPCResult{}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + resp := new(string) + err := conn.CallUnary(context.Background(), []any{"a"}, resp, "Unary") + require.NoError(t, err) + requireCallType(t, invoker.lastInvocation, constant.CallUnary) + require.Equal(t, []any{"a", resp}, invoker.lastInvocation.ParameterRawValues()) + }) + + t.Run("error", func(t *testing.T) { + resErr := errors.New("fail") + invoker := &fakeInvoker{res: &result.RPCResult{Err: resErr}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + err := conn.CallUnary(context.Background(), []any{"a"}, new(string), "Unary") + require.ErrorIs(t, err, resErr) + }) +} + +func TestCallClientStream(t *testing.T) { + resVal := "stream" + invoker := &fakeInvoker{res: &result.RPCResult{Rest: resVal}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + out, err := conn.CallClientStream(context.Background(), "ClientStream") + require.NoError(t, err) + require.Equal(t, resVal, out) + + requireCallType(t, invoker.lastInvocation, constant.CallClientStream) + require.Empty(t, invoker.lastInvocation.ParameterRawValues()) +} + +func TestCallServerStream(t *testing.T) { + t.Run("success", func(t *testing.T) { + req := "payload" + resVal := "server-stream" + invoker := &fakeInvoker{res: &result.RPCResult{Rest: resVal}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + out, err := conn.CallServerStream(context.Background(), req, "ServerStream") + require.NoError(t, err) + require.Equal(t, resVal, out) + + requireCallType(t, invoker.lastInvocation, constant.CallServerStream) + require.Equal(t, []any{req}, invoker.lastInvocation.ParameterRawValues()) + }) + + t.Run("error", func(t *testing.T) { + req := "payload" + resErr := errors.New("stream err") + invoker := &fakeInvoker{res: &result.RPCResult{Err: resErr}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + out, err := conn.CallServerStream(context.Background(), req, "ServerStream") + require.ErrorIs(t, err, resErr) + require.Nil(t, out) + }) +} + +func TestCallBidiStream(t *testing.T) { + resVal := "bidi" + invoker := &fakeInvoker{res: &result.RPCResult{Rest: resVal}} + conn := &Connection{refOpts: &ReferenceOptions{invoker: invoker}} + + out, err := conn.CallBidiStream(context.Background(), "Bidi") + require.NoError(t, err) + require.Equal(t, resVal, out) + + requireCallType(t, invoker.lastInvocation, constant.CallBidiStream) + require.Empty(t, invoker.lastInvocation.ParameterRawValues()) +} + +func requireCallType(t *testing.T, inv base.Invocation, callType string) { + t.Helper() + attr, ok := inv.GetAttribute(constant.CallTypeKey) + require.True(t, ok) + require.Equal(t, callType, attr) +}
