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 4eb036a62 test: add unit test for protocol/dubbo (#3136)
4eb036a62 is described below
commit 4eb036a6210937e8efcf82c1015da1397bb0d9d5
Author: Akashisang <[email protected]>
AuthorDate: Tue Dec 23 12:45:47 2025 +0800
test: add unit test for protocol/dubbo (#3136)
* test: add unit test for protocol/dubbo
---
protocol/dubbo/dubbo_codec_test.go | 265 +++++++++++
protocol/dubbo/dubbo_exporter_test.go | 219 +++++++++
protocol/dubbo/impl/codec_test.go | 224 +++++++++
protocol/dubbo/impl/hessian_test.go | 752 +++++++++++++++++++++++++++++-
protocol/dubbo/impl/package_test.go | 440 +++++++++++++++++
protocol/dubbo/impl/request_test.go | 206 ++++++++
protocol/dubbo/impl/response_test.go | 219 +++++++++
protocol/dubbo/impl/serialization_test.go | 157 +++++++
protocol/dubbo/impl/serialize_test.go | 486 +++++++++++++++++++
protocol/dubbo/opentracing_test.go | 275 +++++++++++
10 files changed, 3242 insertions(+), 1 deletion(-)
diff --git a/protocol/dubbo/dubbo_codec_test.go
b/protocol/dubbo/dubbo_codec_test.go
new file mode 100644
index 000000000..f57c0efa0
--- /dev/null
+++ b/protocol/dubbo/dubbo_codec_test.go
@@ -0,0 +1,265 @@
+/*
+ * 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 dubbo
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// TestIsRequest tests isRequest with various bit patterns
+func TestIsRequest(t *testing.T) {
+ codec := &DubboCodec{}
+
+ tests := []struct {
+ desc string
+ data []byte
+ expected bool
+ }{
+ {
+ desc: "request with 0x80 at position 2",
+ data: []byte{0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: true,
+ },
+ {
+ desc: "request with 0xFF at position 2",
+ data: []byte{0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: true,
+ },
+ {
+ desc: "response with 0x00 at position 2",
+ data: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: false,
+ },
+ {
+ desc: "response with 0x7F at position 2",
+ data: []byte{0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ result := codec.isRequest(test.data)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDecodeWithInsufficientData tests decode with insufficient header data
+func TestDecodeWithInsufficientData(t *testing.T) {
+ codec := &DubboCodec{}
+
+ // Data shorter than header length
+ data := []byte{0x01, 0x02}
+
+ decodeResult, length, err := codec.Decode(data)
+ assert.Nil(t, decodeResult)
+ assert.Equal(t, 0, length)
+ assert.NoError(t, err)
+}
+
+// TestDecodeWithEmptyData tests decode with empty data
+func TestDecodeWithEmptyData(t *testing.T) {
+ codec := &DubboCodec{}
+
+ data := []byte{}
+
+ decodeResult, length, err := codec.Decode(data)
+ assert.Nil(t, decodeResult)
+ assert.Equal(t, 0, length)
+ assert.NoError(t, err)
+}
+
+// TestCodecType tests that DubboCodec is properly instantiated
+func TestCodecType(t *testing.T) {
+ codec := &DubboCodec{}
+
+ // Verify codec implements the expected interface by checking it has
the methods
+ assert.NotNil(t, codec.EncodeRequest)
+ assert.NotNil(t, codec.EncodeResponse)
+ assert.NotNil(t, codec.Decode)
+ assert.NotNil(t, codec.encodeHeartbeatRequest)
+}
+
+// TestIsRequestEdgeCases tests isRequest with edge case bit patterns
+func TestIsRequestEdgeCases(t *testing.T) {
+ codec := &DubboCodec{}
+
+ tests := []struct {
+ desc string
+ data []byte
+ expected bool
+ }{
+ {
+ desc: "bit 0 only",
+ data: []byte{0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: false,
+ },
+ {
+ desc: "bit 1 only",
+ data: []byte{0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: false,
+ },
+ {
+ desc: "bit 2 only",
+ data: []byte{0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00},
+ expected: false,
+ },
+ {
+ desc: "multiple bits set including bit 7",
+ data: []byte{0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF},
+ expected: true,
+ },
+ {
+ desc: "all bits set except bit 7 (no request bit)",
+ data: []byte{0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF},
+ expected: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ result := codec.isRequest(test.data)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestCodecMultipleInstances tests that multiple codec instances work
independently
+func TestCodecMultipleInstances(t *testing.T) {
+ codec1 := &DubboCodec{}
+ codec2 := &DubboCodec{}
+
+ // Both should produce the same results independently
+ testData := []byte{0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00}
+
+ result1 := codec1.isRequest(testData)
+ result2 := codec2.isRequest(testData)
+
+ assert.Equal(t, result1, result2)
+ assert.True(t, result1)
+}
+
+// TestDecodeDataLength tests various data lengths
+func TestDecodeDataLength(t *testing.T) {
+ codec := &DubboCodec{}
+
+ tests := []struct {
+ desc string
+ dataLen int
+ shouldReturnNil bool
+ }{
+ {
+ desc: "1 byte",
+ dataLen: 1,
+ shouldReturnNil: true,
+ },
+ {
+ desc: "5 bytes",
+ dataLen: 5,
+ shouldReturnNil: true,
+ },
+ {
+ desc: "15 bytes",
+ dataLen: 15,
+ shouldReturnNil: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ data := make([]byte, test.dataLen)
+ decodeResult, _, err := codec.Decode(data)
+
+ if test.shouldReturnNil {
+ assert.Nil(t, decodeResult)
+ }
+ assert.NoError(t, err)
+ })
+ }
+}
+
+// TestIsRequestConsistency tests that isRequest gives consistent results
+func TestIsRequestConsistency(t *testing.T) {
+ codec := &DubboCodec{}
+ data := []byte{0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00}
+
+ // Call multiple times, should get same result
+ result1 := codec.isRequest(data)
+ result2 := codec.isRequest(data)
+ result3 := codec.isRequest(data)
+
+ assert.Equal(t, result1, result2)
+ assert.Equal(t, result2, result3)
+ assert.True(t, result1)
+}
+
+// TestRequestBitMask tests the request bit mask logic
+func TestRequestBitMask(t *testing.T) {
+ codec := &DubboCodec{}
+
+ // Test boundary values for byte at position 2 with bit 7 (0x80) set
+ tests := []struct {
+ desc string
+ byteValue byte
+ expected bool
+ }{
+ {
+ desc: "0x00 - no bits set",
+ byteValue: 0x00,
+ expected: false,
+ },
+ {
+ desc: "0x01 - only bit 0 set",
+ byteValue: 0x01,
+ expected: false,
+ },
+ {
+ desc: "0x7F - bits 0-6 set",
+ byteValue: 0x7F,
+ expected: false,
+ },
+ {
+ desc: "0x80 - only bit 7 set",
+ byteValue: 0x80,
+ expected: true,
+ },
+ {
+ desc: "0x81 - bits 0 and 7 set",
+ byteValue: 0x81,
+ expected: true,
+ },
+ {
+ desc: "0xFF - all bits set",
+ byteValue: 0xFF,
+ expected: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ data := []byte{0x00, 0x00, test.byteValue, 0x00, 0x00,
0x00, 0x00, 0x00}
+ result := codec.isRequest(data)
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
diff --git a/protocol/dubbo/dubbo_exporter_test.go
b/protocol/dubbo/dubbo_exporter_test.go
new file mode 100644
index 000000000..d54bb2a3a
--- /dev/null
+++ b/protocol/dubbo/dubbo_exporter_test.go
@@ -0,0 +1,219 @@
+/*
+ * 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 dubbo
+
+import (
+ "context"
+ "sync"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/protocol/base"
+ "dubbo.apache.org/dubbo-go/v3/protocol/result"
+)
+
+// MockInvoker is a simple mock implementation of base.Invoker for testing
+type MockInvoker struct {
+ url *common.URL
+ available bool
+ destroyed bool
+}
+
+func (m *MockInvoker) GetURL() *common.URL {
+ return m.url
+}
+
+func (m *MockInvoker) Invoke(ctx context.Context, invocation base.Invocation)
result.Result {
+ return &result.RPCResult{}
+}
+
+func (m *MockInvoker) IsAvailable() bool {
+ return m.available && !m.destroyed
+}
+
+func (m *MockInvoker) Destroy() {
+ m.destroyed = true
+}
+
+// TestNewDubboExporter tests the NewDubboExporter function
+func TestNewDubboExporter(t *testing.T) {
+ tests := []struct {
+ desc string
+ key string
+ invoker base.Invoker
+ expectedNotNil bool
+ expectedAvailable bool
+ }{
+ {
+ desc: "with valid invoker",
+ key: "test_key",
+ invoker: &MockInvoker{
+ url: func() *common.URL {
+ url, _ :=
common.NewURL("dubbo://localhost:20000/test.TestService?interface=test.TestService&group=test&version=1.0")
+ return url
+ }(),
+ available: true,
+ destroyed: false,
+ },
+ expectedNotNil: true,
+ expectedAvailable: true,
+ },
+ {
+ desc: "with nil invoker",
+ key: "test_key",
+ invoker: nil,
+ expectedNotNil: true,
+ expectedAvailable: false,
+ },
+ {
+ desc: "with different key",
+ key: "another_key",
+ invoker: &MockInvoker{
+ url: func() *common.URL {
+ url, _ :=
common.NewURL("dubbo://localhost:20000/service?interface=test.Service")
+ return url
+ }(),
+ available: true,
+ destroyed: false,
+ },
+ expectedNotNil: true,
+ expectedAvailable: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ exporterMap := &sync.Map{}
+ exporter := NewDubboExporter(test.key, test.invoker,
exporterMap)
+
+ assert.NotNil(t, exporter)
+ if test.invoker != nil {
+ assert.Equal(t, test.invoker,
exporter.GetInvoker())
+ assert.Equal(t, test.expectedAvailable,
exporter.GetInvoker().IsAvailable())
+ } else {
+ assert.Nil(t, exporter.GetInvoker())
+ }
+ })
+ }
+}
+
+// TestDubboExporterGetInvoker tests the GetInvoker method
+func TestDubboExporterGetInvoker(t *testing.T) {
+ tests := []struct {
+ desc string
+ key string
+ urlString string
+ checkURL bool
+ }{
+ {
+ desc: "get invoker from exporter",
+ key: "test_key",
+ urlString:
"dubbo://localhost:20000/test.TestService?interface=test.TestService",
+ checkURL: true,
+ },
+ {
+ desc: "get invoker with group and version",
+ key: "service_key",
+ urlString:
"dubbo://localhost:20000/service?interface=com.example.Service&group=test&version=1.0",
+ checkURL: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ exporterMap := &sync.Map{}
+ url, _ := common.NewURL(test.urlString)
+ mockInvoker := &MockInvoker{
+ url: url,
+ available: true,
+ destroyed: false,
+ }
+
+ exporter := NewDubboExporter(test.key, mockInvoker,
exporterMap)
+ retrievedInvoker := exporter.GetInvoker()
+
+ assert.Equal(t, mockInvoker, retrievedInvoker)
+ if test.checkURL {
+ assert.Equal(t, url, retrievedInvoker.GetURL())
+ }
+ })
+ }
+}
+
+// TestDubboExporterMultipleExporters tests creating multiple exporters with
different keys coexisting in the same map
+func TestDubboExporterMultipleExporters(t *testing.T) {
+ tests := []struct {
+ desc string
+ key string
+ interfaceName string
+ }{
+ {
+ desc: "service1",
+ key: "com.example.Service1_key",
+ interfaceName: "com.example.Service1",
+ },
+ {
+ desc: "service2",
+ key: "com.example.Service2_key",
+ interfaceName: "com.example.Service2",
+ },
+ {
+ desc: "test service",
+ key: "test.Service_key",
+ interfaceName: "test.Service",
+ },
+ }
+
+ // Create a shared exporterMap for all test cases
+ exporterMap := &sync.Map{}
+ exporters := make(map[string]*DubboExporter)
+ mockInvokers := make(map[string]*MockInvoker)
+
+ // Create multiple exporters with different keys in the shared map
+ for _, test := range tests {
+ url, _ := common.NewURL("dubbo://localhost:20000/" +
test.interfaceName + "?interface=" + test.interfaceName)
+ mockInvoker := &MockInvoker{
+ url: url,
+ available: true,
+ destroyed: false,
+ }
+ mockInvokers[test.key] = mockInvoker
+
+ exporter := NewDubboExporter(test.key, mockInvoker, exporterMap)
+ exporters[test.key] = exporter
+ }
+
+ // Verify all exporters coexist in memory - validate the count
+ assert.Equal(t, len(tests), len(exporters), "all exporters should
coexist in the exporters map")
+
+ // Verify each exporter has the correct invoker and can be accessed
independently
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ exporter, ok := exporters[test.key]
+ assert.True(t, ok, "exporter with key %s should exist
in exporters map", test.key)
+ assert.NotNil(t, exporter, "exporter should not be nil")
+ assert.Equal(t, mockInvokers[test.key],
exporter.GetInvoker(), "exporter should return the correct invoker")
+ })
+ }
+}
diff --git a/protocol/dubbo/impl/codec_test.go
b/protocol/dubbo/impl/codec_test.go
index de45d1eb2..1248df844 100644
--- a/protocol/dubbo/impl/codec_test.go
+++ b/protocol/dubbo/impl/codec_test.go
@@ -87,3 +87,227 @@ func TestDubboPackage_MarshalAndUnmarshal(t *testing.T) {
}
assert.Equal(t, tmpData, reassembleBody["attachments"])
}
+
+// TestEncodeHeaderHeartbeat tests EncodeHeader for heartbeat packages
+func TestEncodeHeaderHeartbeat(t *testing.T) {
+ codec := NewDubboCodec(nil)
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageHeartbeat
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 12345
+
+ header := codec.EncodeHeader(*pkg)
+ assert.NotNil(t, header)
+ assert.Greater(t, len(header), 0)
+ assert.Equal(t, MAGIC_HIGH, header[0])
+ assert.Equal(t, MAGIC_LOW, header[1])
+}
+
+// TestEncodeHeaderResponse tests EncodeHeader for response packages
+func TestEncodeHeaderResponse(t *testing.T) {
+ codec := NewDubboCodec(nil)
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageResponse
+ pkg.Header.ResponseStatus = Response_OK
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 99999
+
+ header := codec.EncodeHeader(*pkg)
+ assert.NotNil(t, header)
+ assert.Equal(t, MAGIC_HIGH, header[0])
+ assert.Equal(t, MAGIC_LOW, header[1])
+ assert.Equal(t, Response_OK, header[3])
+}
+
+// TestEncodeHeaderResponseWithError tests EncodeHeader for error response
packages
+func TestEncodeHeaderResponseWithError(t *testing.T) {
+ codec := NewDubboCodec(nil)
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageResponse
+ pkg.Header.ResponseStatus = Response_SERVICE_ERROR
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 11111
+
+ header := codec.EncodeHeader(*pkg)
+ assert.NotNil(t, header)
+ assert.Equal(t, Response_SERVICE_ERROR, header[3])
+}
+
+// TestEncodeHeaderRequestTwoWay tests EncodeHeader for two-way request
packages
+func TestEncodeHeaderRequestTwoWay(t *testing.T) {
+ codec := NewDubboCodec(nil)
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageRequest_TwoWay
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 22222
+
+ header := codec.EncodeHeader(*pkg)
+ assert.NotNil(t, header)
+ assert.Greater(t, len(header), 0)
+}
+
+// TestProtocolCodecSetSerializer tests SetSerializer method of ProtocolCodec
+func TestProtocolCodecSetSerializer(t *testing.T) {
+ codec := NewDubboCodec(nil)
+ serializer := &HessianSerializer{}
+
+ codec.SetSerializer(serializer)
+ assert.NotNil(t, codec.serializer)
+}
+
+// TestPackageResponseWorkflow tests response package workflow
+func TestPackageResponseWorkflow(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageResponse
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 99999
+ pkg.Header.ResponseStatus = Response_OK
+ pkg.Body = "response"
+ pkg.SetSerializer(mockSerializer)
+
+ assert.False(t, pkg.IsHeartBeat())
+ assert.False(t, pkg.IsRequest())
+ assert.True(t, pkg.IsResponse())
+ assert.False(t, pkg.IsResponseWithException())
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+}
+
+// TestPackageRequestWorkflow tests request package workflow
+func TestPackageRequestWorkflow(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageRequest
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 66666
+ pkg.Service.Interface = "TestService"
+ pkg.Service.Path = "/dubbo"
+ pkg.Service.Method = "test"
+ pkg.Body = []any{"arg1"}
+ pkg.SetSerializer(mockSerializer)
+
+ assert.False(t, pkg.IsHeartBeat())
+ assert.True(t, pkg.IsRequest())
+ assert.False(t, pkg.IsResponse())
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+}
+
+// TestPackageBodyManipulation tests body manipulation methods
+func TestPackageBodyManipulation(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+
+ testBodies := []any{
+ "string",
+ 123,
+ []string{"a", "b"},
+ map[string]any{"key": "value"},
+ }
+
+ for _, body := range testBodies {
+ pkg.SetBody(body)
+ assert.Equal(t, body, pkg.GetBody())
+ }
+}
+
+// TestPackageIDManipulation tests ID manipulation
+func TestPackageIDManipulation(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+
+ ids := []int64{1, 100, 1000, 999999, 9223372036854775807}
+
+ for _, id := range ids {
+ pkg.SetID(id)
+ assert.Equal(t, id, pkg.Header.ID)
+ }
+}
+
+// TestPackageServiceManipulation tests service manipulation
+func TestPackageServiceManipulation(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+
+ services := []Service{
+ {Path: "/a", Interface: "InterfaceA"},
+ {Path: "/b", Interface: "InterfaceB", Version: "1.0"},
+ {Path: "/c", Interface: "InterfaceC", Group: "g1", Version:
"2.0"},
+ }
+
+ for _, svc := range services {
+ pkg.SetService(svc)
+ retrieved := pkg.GetService()
+ assert.Equal(t, svc.Path, retrieved.Path)
+ assert.Equal(t, svc.Interface, retrieved.Interface)
+ assert.Equal(t, svc.Version, retrieved.Version)
+ assert.Equal(t, svc.Group, retrieved.Group)
+ }
+}
+
+// TestPackageHeaderManipulation tests header manipulation
+func TestPackageHeaderManipulation(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+
+ headers := []DubboHeader{
+ {ID: 1, BodyLen: 100, Type: PackageRequest},
+ {ID: 2, BodyLen: 200, Type: PackageResponse, ResponseStatus:
Response_OK},
+ {ID: 3, BodyLen: 300, Type: PackageHeartbeat, SerialID:
constant.SHessian2},
+ }
+
+ for _, header := range headers {
+ pkg.SetHeader(header)
+ retrieved := pkg.GetHeader()
+ assert.Equal(t, header.ID, retrieved.ID)
+ assert.Equal(t, header.BodyLen, retrieved.BodyLen)
+ assert.Equal(t, header.Type, retrieved.Type)
+ }
+}
+
+// TestPackageResponseStatusWorkflow tests response status workflow
+func TestPackageResponseStatusWorkflow(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = PackageResponse
+
+ statuses := []struct {
+ status byte
+ name string
+ }{
+ {Response_OK, "OK"},
+ {Response_CLIENT_TIMEOUT, "CLIENT_TIMEOUT"},
+ {Response_SERVER_TIMEOUT, "SERVER_TIMEOUT"},
+ {Response_BAD_REQUEST, "BAD_REQUEST"},
+ }
+
+ for _, s := range statuses {
+ pkg.SetResponseStatus(s.status)
+ assert.Equal(t, s.status, pkg.Header.ResponseStatus)
+ }
+}
+
+// TestDubboPackageIntegrationWithLoadSerializer tests integration with
LoadSerializer
+func TestDubboPackageIntegrationWithLoadSerializer(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.Type = PackageRequest
+ pkg.Header.ID = 55555
+ pkg.Service.Interface = "TestService"
+ pkg.Body = []any{"test"}
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ assert.Greater(t, data.Len(), 0)
+}
diff --git a/protocol/dubbo/impl/hessian_test.go
b/protocol/dubbo/impl/hessian_test.go
index 73c98ea9f..5cc4303f3 100644
--- a/protocol/dubbo/impl/hessian_test.go
+++ b/protocol/dubbo/impl/hessian_test.go
@@ -18,15 +18,22 @@
package impl
import (
+ "errors"
"testing"
+ "time"
)
import (
- "github.com/apache/dubbo-go-hessian2"
+ hessian "github.com/apache/dubbo-go-hessian2"
+ "github.com/apache/dubbo-go-hessian2/java_exception"
"github.com/stretchr/testify/assert"
)
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+)
+
const (
dubboParam = "org.apache.dubbo.param"
dubboPojo = "org.apache.dubbo.pojo"
@@ -59,6 +66,265 @@ func TestGetArgType(t *testing.T) {
t.Run("param", func(t *testing.T) {
assert.Equal(t, dubboParam, getArgType(&Param{}))
})
+
+ t.Run("nil", func(t *testing.T) {
+ assert.Equal(t, "V", getArgType(nil))
+ })
+
+ t.Run("bool", func(t *testing.T) {
+ assert.Equal(t, "Z", getArgType(true))
+ })
+
+ t.Run("bool array", func(t *testing.T) {
+ assert.Equal(t, "[Z", getArgType([]bool{true, false}))
+ })
+
+ t.Run("byte", func(t *testing.T) {
+ assert.Equal(t, "B", getArgType(byte(1)))
+ })
+
+ t.Run("byte array", func(t *testing.T) {
+ assert.Equal(t, "[B", getArgType([]byte{1, 2, 3}))
+ })
+
+ t.Run("int8", func(t *testing.T) {
+ assert.Equal(t, "B", getArgType(int8(1)))
+ })
+
+ t.Run("int8 array", func(t *testing.T) {
+ assert.Equal(t, "[B", getArgType([]int8{1, 2, 3}))
+ })
+
+ t.Run("int16", func(t *testing.T) {
+ assert.Equal(t, "S", getArgType(int16(1)))
+ })
+
+ t.Run("int16 array", func(t *testing.T) {
+ assert.Equal(t, "[S", getArgType([]int16{1, 2, 3}))
+ })
+
+ t.Run("uint16", func(t *testing.T) {
+ assert.Equal(t, "C", getArgType(uint16(1)))
+ })
+
+ t.Run("uint16 array", func(t *testing.T) {
+ assert.Equal(t, "[C", getArgType([]uint16{1, 2, 3}))
+ })
+
+ t.Run("int", func(t *testing.T) {
+ assert.Equal(t, "J", getArgType(int(1)))
+ })
+
+ t.Run("int array", func(t *testing.T) {
+ assert.Equal(t, "[J", getArgType([]int{1, 2, 3}))
+ })
+
+ t.Run("int32", func(t *testing.T) {
+ assert.Equal(t, "I", getArgType(int32(1)))
+ })
+
+ t.Run("int32 array", func(t *testing.T) {
+ assert.Equal(t, "[I", getArgType([]int32{1, 2, 3}))
+ })
+
+ t.Run("int64", func(t *testing.T) {
+ assert.Equal(t, "J", getArgType(int64(1)))
+ })
+
+ t.Run("int64 array", func(t *testing.T) {
+ assert.Equal(t, "[J", getArgType([]int64{1, 2, 3}))
+ })
+
+ t.Run("time.Time", func(t *testing.T) {
+ assert.Equal(t, "java.util.Date", getArgType(time.Now()))
+ })
+
+ t.Run("time.Time array", func(t *testing.T) {
+ assert.Equal(t, "[Ljava.util.Date",
getArgType([]time.Time{time.Now()}))
+ })
+
+ t.Run("float32", func(t *testing.T) {
+ assert.Equal(t, "F", getArgType(float32(1.0)))
+ })
+
+ t.Run("float32 array", func(t *testing.T) {
+ assert.Equal(t, "[F", getArgType([]float32{1.0, 2.0}))
+ })
+
+ t.Run("float64", func(t *testing.T) {
+ assert.Equal(t, "D", getArgType(float64(1.0)))
+ })
+
+ t.Run("float64 array", func(t *testing.T) {
+ assert.Equal(t, "[D", getArgType([]float64{1.0, 2.0}))
+ })
+
+ t.Run("string", func(t *testing.T) {
+ assert.Equal(t, "java.lang.String", getArgType("test"))
+ })
+
+ t.Run("string array", func(t *testing.T) {
+ assert.Equal(t, "[Ljava.lang.String;",
getArgType([]string{"test1", "test2"}))
+ })
+
+ t.Run("map[any]any", func(t *testing.T) {
+ assert.Equal(t, "java.util.Map", getArgType(map[any]any{"key":
"value"}))
+ })
+
+ t.Run("map[string]int", func(t *testing.T) {
+ assert.Equal(t, "java.util.Map",
getArgType(map[string]int{"key": 1}))
+ })
+
+ t.Run("pointer types", func(t *testing.T) {
+ v := int8(1)
+ assert.Equal(t, "java.lang.Byte", getArgType(&v))
+
+ v2 := int16(1)
+ assert.Equal(t, "java.lang.Short", getArgType(&v2))
+
+ v3 := uint16(1)
+ assert.Equal(t, "java.lang.Character", getArgType(&v3))
+
+ v4 := int(1)
+ assert.Equal(t, "java.lang.Long", getArgType(&v4))
+
+ v5 := int32(1)
+ assert.Equal(t, "java.lang.Integer", getArgType(&v5))
+
+ v6 := int64(1)
+ assert.Equal(t, "java.lang.Long", getArgType(&v6))
+
+ v7 := float32(1.0)
+ assert.Equal(t, "java.lang.Float", getArgType(&v7))
+
+ v8 := float64(1.0)
+ assert.Equal(t, "java.lang.Double", getArgType(&v8))
+ })
+
+ t.Run("slice of structs", func(t *testing.T) {
+ assert.Equal(t, "[Ljava.lang.Object;", getArgType([]Pojo{{},
{}}))
+ })
+
+ t.Run("slice of string", func(t *testing.T) {
+ assert.Equal(t, "[Ljava.lang.String;", getArgType([]string{"a",
"b"}))
+ })
+
+ t.Run("generic object", func(t *testing.T) {
+ type GenericStruct struct {
+ Field string
+ }
+ assert.Equal(t, "java.lang.Object",
getArgType(&GenericStruct{Field: "test"}))
+ })
+}
+
+func TestGetArgsTypeList(t *testing.T) {
+ t.Run("multiple types", func(t *testing.T) {
+ args := []any{int32(1), "test", true, float64(1.0)}
+ types, err := GetArgsTypeList(args)
+ assert.NoError(t, err)
+ assert.Equal(t, "ILjava/lang/String;ZD", types)
+ })
+
+ t.Run("with pojo", func(t *testing.T) {
+ args := []any{&Pojo{}, "test"}
+ types, err := GetArgsTypeList(args)
+ assert.NoError(t, err)
+ assert.Equal(t, "Lorg/apache/dubbo/pojo;Ljava/lang/String;",
types)
+ })
+
+ t.Run("with array", func(t *testing.T) {
+ args := []any{[]string{"a", "b"}}
+ types, err := GetArgsTypeList(args)
+ assert.NoError(t, err)
+ assert.Equal(t, "[Ljava/lang/String;", types)
+ })
+
+ t.Run("empty args", func(t *testing.T) {
+ args := []any{}
+ types, err := GetArgsTypeList(args)
+ assert.NoError(t, err)
+ assert.Equal(t, "", types)
+ })
+}
+
+func TestToMapStringInterface(t *testing.T) {
+ t.Run("normal map", func(t *testing.T) {
+ origin := map[any]any{
+ "key1": "value1",
+ "key2": 123,
+ }
+ result := ToMapStringInterface(origin)
+ assert.Equal(t, "value1", result["key1"])
+ assert.Equal(t, 123, result["key2"])
+ })
+
+ t.Run("with nil value", func(t *testing.T) {
+ origin := map[any]any{
+ "key1": nil,
+ "key2": "value2",
+ }
+ result := ToMapStringInterface(origin)
+ assert.Equal(t, "", result["key1"])
+ assert.Equal(t, "value2", result["key2"])
+ })
+
+ t.Run("with non-string key", func(t *testing.T) {
+ origin := map[any]any{
+ 123: "value1",
+ "key2": "value2",
+ }
+ result := ToMapStringInterface(origin)
+ assert.Equal(t, 1, len(result))
+ assert.Equal(t, "value2", result["key2"])
+ })
+}
+
+func TestVersion2Int(t *testing.T) {
+ tests := []struct {
+ name string
+ version string
+ expected int
+ }{
+ {"version 2.0.10", "2.0.10", 2001000},
+ {"version 2.6.2", "2.6.2", 2060200},
+ {"version 2.7.0", "2.7.0", 2070000},
+ {"version 2.0.1", "2.0.1", 2000100},
+ {"version 3.0.0", "3.0.0", 3000000},
+ {"invalid version", "invalid", -1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := version2Int(tt.version)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestIsSupportResponseAttachment(t *testing.T) {
+ t.Run("empty version", func(t *testing.T) {
+ assert.False(t, isSupportResponseAttachment(""))
+ })
+
+ t.Run("version 2.0.10", func(t *testing.T) {
+ assert.False(t, isSupportResponseAttachment("2.0.10"))
+ })
+
+ t.Run("version 2.6.2", func(t *testing.T) {
+ assert.False(t, isSupportResponseAttachment("2.6.2"))
+ })
+
+ t.Run("version 2.7.0", func(t *testing.T) {
+ assert.True(t, isSupportResponseAttachment("2.7.0"))
+ })
+
+ t.Run("version 3.0.0", func(t *testing.T) {
+ assert.True(t, isSupportResponseAttachment("3.0.0"))
+ })
+
+ t.Run("invalid version", func(t *testing.T) {
+ assert.False(t, isSupportResponseAttachment("invalid"))
+ })
}
func TestMarshalRequestWithTypedNilPointer(t *testing.T) {
@@ -103,3 +369,487 @@ func TestMarshalRequestWithNonNilPointer(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, data)
}
+
+func TestMarshalRequest(t *testing.T) {
+ t.Run("basic request", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Service: Service{
+ Path: "com.test.Service",
+ Interface: "com.test.Service",
+ Version: "1.0.0",
+ Method: "test",
+ Group: "testGroup",
+ Timeout: time.Second * 5,
+ },
+ Body: &RequestPayload{
+ Params: []any{int32(123), "test"},
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalRequest(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("request with invalid params", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Service: Service{
+ Path: "test.Path",
+ Version: "1.0.0",
+ Method: "Echo",
+ },
+ Body: &RequestPayload{
+ Params: "not a slice",
+ Attachments: map[string]any{},
+ },
+ }
+
+ _, err := marshalRequest(encoder, pkg)
+ assert.Error(t, err)
+ })
+}
+
+func TestMarshalResponse(t *testing.T) {
+ t.Run("heartbeat response", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageHeartbeat |
PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with value", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ RspObj: "test response",
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with null value", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ RspObj: nil,
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with exception", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ Exception: errors.New("test error"),
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with throwable exception", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ Exception: java_exception.NewThrowable("test
throwable"),
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ RspObj: "test",
+ Attachments: map[string]any{
+ DUBBO_VERSION_KEY: "2.7.0",
+ "custom": "value",
+ },
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("response with error status", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_SERVER_ERROR,
+ },
+ Body: &ResponsePayload{
+ Exception: errors.New("server error"),
+ },
+ }
+
+ data, err := marshalResponse(encoder, pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+}
+
+func TestUnmarshalRequestBody(t *testing.T) {
+ t.Run("basic request unmarshal", func(t *testing.T) {
+ // Prepare encoded request
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode("2.0.2") // dubbo version
+ _ = encoder.Encode("com.test.Service") // target
+ _ = encoder.Encode("1.0.0") // service
version
+ _ = encoder.Encode("test") // method
+ _ = encoder.Encode("Ljava/lang/String;") // args types
+ _ = encoder.Encode("hello") // args
+ _ = encoder.Encode(map[any]any{"key": "value"}) // attachments
+
+ pkg := &DubboPackage{}
+ err := unmarshalRequestBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Body)
+
+ body, ok := pkg.Body.(map[string]any)
+ assert.True(t, ok)
+ assert.Equal(t, "2.0.2", body["dubboVersion"])
+ assert.Equal(t, "Ljava/lang/String;", body["argsTypes"])
+ assert.NotNil(t, body["attachments"])
+ })
+
+ t.Run("request with multiple args", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode("2.0.2")
+ _ = encoder.Encode("com.test.Service")
+ _ = encoder.Encode("1.0.0")
+ _ = encoder.Encode("test")
+ _ = encoder.Encode("ILjava/lang/String;")
+ _ = encoder.Encode(int32(123))
+ _ = encoder.Encode("test")
+ _ = encoder.Encode(map[any]any{"key": "value"})
+
+ pkg := &DubboPackage{}
+ err := unmarshalRequestBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ body, ok := pkg.GetBody().(map[string]any)
+ assert.True(t, ok)
+ args := body["args"].([]any)
+ assert.Equal(t, 2, len(args))
+ })
+
+ t.Run("request with nil attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode("2.0.2")
+ _ = encoder.Encode("com.test.Service")
+ _ = encoder.Encode("1.0.0")
+ _ = encoder.Encode("test")
+ _ = encoder.Encode("")
+ _ = encoder.Encode(nil)
+
+ pkg := &DubboPackage{}
+ err := unmarshalRequestBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ })
+}
+
+func TestUnmarshalResponseBody(t *testing.T) {
+ t.Run("response with value", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_VALUE)
+ _ = encoder.Encode("test response")
+
+ // Need to set RspObj for ReflectResponse to work
+ rspObj := ""
+ pkg := &DubboPackage{
+ Body: &ResponsePayload{
+ RspObj: &rspObj,
+ },
+ }
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ assert.Equal(t, "test response", rspObj)
+ })
+
+ t.Run("response with value and attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_VALUE_WITH_ATTACHMENTS)
+ _ = encoder.Encode("test response")
+ _ = encoder.Encode(map[any]any{"key": "value"})
+
+ rspObj := ""
+ pkg := &DubboPackage{
+ Body: &ResponsePayload{
+ RspObj: &rspObj,
+ },
+ }
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ response := EnsureResponsePayload(pkg.Body)
+ assert.NotNil(t, response.Attachments)
+ assert.Equal(t, "value", response.Attachments["key"])
+ })
+
+ t.Run("response with exception", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_WITH_EXCEPTION)
+ _ = encoder.Encode(java_exception.NewThrowable("test error"))
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ response := EnsureResponsePayload(pkg.Body)
+ assert.NotNil(t, response.Exception)
+ })
+
+ t.Run("response with exception and attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS)
+ _ = encoder.Encode(java_exception.NewThrowable("test error"))
+ _ = encoder.Encode(map[any]any{"key": "value"})
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ response := EnsureResponsePayload(pkg.Body)
+ assert.NotNil(t, response.Exception)
+ assert.NotNil(t, response.Attachments)
+ })
+
+ t.Run("response with null value", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_NULL_VALUE)
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ })
+
+ t.Run("response with null value and attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_NULL_VALUE_WITH_ATTACHMENTS)
+ _ = encoder.Encode(map[any]any{"key": "value"})
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ response := EnsureResponsePayload(pkg.Body)
+ assert.NotNil(t, response.Attachments)
+ })
+
+ t.Run("response with non-error exception", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_WITH_EXCEPTION)
+ _ = encoder.Encode("string exception")
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+
+ response := EnsureResponsePayload(pkg.Body)
+ assert.NotNil(t, response.Exception)
+ })
+
+ t.Run("response with invalid attachments", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_VALUE_WITH_ATTACHMENTS)
+ _ = encoder.Encode("test")
+ _ = encoder.Encode("invalid attachments")
+
+ pkg := &DubboPackage{}
+ err := unmarshalResponseBody(encoder.Buffer(), pkg)
+ assert.Error(t, err)
+ })
+}
+
+func TestBuildServerSidePackageBody(t *testing.T) {
+ // Register a test service
+ common.ServiceMap.Register("com.test.Interface", "dubbo", "testGroup",
"1.0.0", &TestService{})
+
+ t.Run("build package body", func(t *testing.T) {
+ req := []any{
+ "2.0.2",
+ "com.test.Path",
+ "1.0.0",
+ "test",
+ "Ljava/lang/String;",
+ []any{"arg1"},
+ map[string]any{
+ "path": "com.test.Path",
+ "interface": "com.test.Interface",
+ "group": "testGroup",
+ },
+ }
+
+ pkg := &DubboPackage{
+ Body: req,
+ }
+
+ buildServerSidePackageBody(pkg)
+
+ body, ok := pkg.GetBody().(map[string]any)
+ assert.True(t, ok)
+ assert.Equal(t, "2.0.2", body["dubboVersion"])
+ assert.Equal(t, "Ljava/lang/String;", body["argsTypes"])
+ assert.NotNil(t, body["attachments"])
+ })
+
+ t.Run("empty body", func(t *testing.T) {
+ pkg := &DubboPackage{
+ Body: []any{},
+ }
+
+ buildServerSidePackageBody(pkg)
+ // Should not panic
+ })
+}
+
+type TestService struct{}
+
+func TestHessianSerializer_Marshal(t *testing.T) {
+ serializer := HessianSerializer{}
+
+ t.Run("marshal request", func(t *testing.T) {
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageRequest,
+ },
+ Service: Service{
+ Path: "test.Service",
+ Version: "1.0.0",
+ Method: "test",
+ },
+ Body: &RequestPayload{
+ Params: []any{int32(123)},
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := serializer.Marshal(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+
+ t.Run("marshal response", func(t *testing.T) {
+ pkg := DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ ResponseStatus: Response_OK,
+ },
+ Body: &ResponsePayload{
+ RspObj: "test",
+ Attachments: map[string]any{},
+ },
+ }
+
+ data, err := serializer.Marshal(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ })
+}
+
+func TestHessianSerializer_Unmarshal(t *testing.T) {
+ serializer := HessianSerializer{}
+
+ t.Run("unmarshal heartbeat", func(t *testing.T) {
+ pkg := &DubboPackage{
+ Header: DubboHeader{
+ Type: PackageHeartbeat | PackageResponse,
+ },
+ }
+
+ err := serializer.Unmarshal([]byte{}, pkg)
+ assert.NoError(t, err)
+ })
+
+ t.Run("unmarshal request", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode("2.0.2")
+ _ = encoder.Encode("com.test.Service")
+ _ = encoder.Encode("1.0.0")
+ _ = encoder.Encode("test")
+ _ = encoder.Encode("")
+ _ = encoder.Encode(map[any]any{"key": "value"})
+
+ pkg := &DubboPackage{
+ Header: DubboHeader{
+ Type: PackageRequest,
+ },
+ }
+
+ err := serializer.Unmarshal(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ })
+
+ t.Run("unmarshal response", func(t *testing.T) {
+ encoder := hessian.NewEncoder()
+ _ = encoder.Encode(RESPONSE_VALUE)
+ _ = encoder.Encode("test")
+
+ rspObj := ""
+ pkg := &DubboPackage{
+ Header: DubboHeader{
+ Type: PackageResponse,
+ },
+ Body: &ResponsePayload{
+ RspObj: &rspObj,
+ },
+ }
+
+ err := serializer.Unmarshal(encoder.Buffer(), pkg)
+ assert.NoError(t, err)
+ assert.Equal(t, "test", rspObj)
+ })
+}
diff --git a/protocol/dubbo/impl/package_test.go
b/protocol/dubbo/impl/package_test.go
new file mode 100644
index 000000000..7d3beea1e
--- /dev/null
+++ b/protocol/dubbo/impl/package_test.go
@@ -0,0 +1,440 @@
+/*
+ * 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 impl
+
+import (
+ "bytes"
+ "testing"
+ "time"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// TestNewDubboPackage tests creating a new DubboPackage
+func TestNewDubboPackage(t *testing.T) {
+ tests := []struct {
+ desc string
+ data *bytes.Buffer
+ expectNil bool
+ }{
+ {
+ desc: "with nil data",
+ data: nil,
+ expectNil: false,
+ },
+ {
+ desc: "with empty buffer",
+ data: bytes.NewBuffer([]byte{}),
+ expectNil: false,
+ },
+ {
+ desc: "with buffer containing data",
+ data: bytes.NewBuffer([]byte{0x01, 0x02, 0x03}),
+ expectNil: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(test.data)
+
+ if test.expectNil {
+ assert.Nil(t, pkg)
+ } else {
+ assert.NotNil(t, pkg)
+ assert.NotNil(t, pkg.Codec)
+ assert.Nil(t, pkg.Body)
+ assert.Nil(t, pkg.Err)
+ }
+ })
+ }
+}
+
+// TestDubboPackageIsHeartBeat tests IsHeartBeat method
+func TestDubboPackageIsHeartBeat(t *testing.T) {
+ tests := []struct {
+ desc string
+ headerType PackageType
+ expected bool
+ }{
+ {
+ desc: "heartbeat package",
+ headerType: PackageHeartbeat,
+ expected: true,
+ },
+ {
+ desc: "request package",
+ headerType: PackageRequest,
+ expected: false,
+ },
+ {
+ desc: "response package",
+ headerType: PackageResponse,
+ expected: false,
+ },
+ {
+ desc: "heartbeat with other flags",
+ headerType: PackageHeartbeat | PackageRequest,
+ expected: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = test.headerType
+
+ result := pkg.IsHeartBeat()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageIsRequest tests IsRequest method
+func TestDubboPackageIsRequest(t *testing.T) {
+ tests := []struct {
+ desc string
+ headerType PackageType
+ expected bool
+ }{
+ {
+ desc: "request package",
+ headerType: PackageRequest,
+ expected: true,
+ },
+ {
+ desc: "two way request package",
+ headerType: PackageRequest_TwoWay,
+ expected: true,
+ },
+ {
+ desc: "response package",
+ headerType: PackageResponse,
+ expected: false,
+ },
+ {
+ desc: "heartbeat package",
+ headerType: PackageHeartbeat,
+ expected: false,
+ },
+ {
+ desc: "request with twoway flag",
+ headerType: PackageRequest | PackageRequest_TwoWay,
+ expected: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = test.headerType
+
+ result := pkg.IsRequest()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageIsResponse tests IsResponse method
+func TestDubboPackageIsResponse(t *testing.T) {
+ tests := []struct {
+ desc string
+ headerType PackageType
+ expected bool
+ }{
+ {
+ desc: "response package",
+ headerType: PackageResponse,
+ expected: true,
+ },
+ {
+ desc: "request package",
+ headerType: PackageRequest,
+ expected: false,
+ },
+ {
+ desc: "heartbeat package",
+ headerType: PackageHeartbeat,
+ expected: false,
+ },
+ {
+ desc: "response with exception",
+ headerType: PackageResponse | PackageResponse_Exception,
+ expected: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = test.headerType
+
+ result := pkg.IsResponse()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageIsResponseWithException tests IsResponseWithException method
+func TestDubboPackageIsResponseWithException(t *testing.T) {
+ tests := []struct {
+ desc string
+ headerType PackageType
+ expected bool
+ }{
+ {
+ desc: "response with exception",
+ headerType: PackageResponse | PackageResponse_Exception,
+ expected: true,
+ },
+ {
+ desc: "response without exception",
+ headerType: PackageResponse,
+ expected: false,
+ },
+ {
+ desc: "request package",
+ headerType: PackageRequest,
+ expected: false,
+ },
+ {
+ desc: "only exception flag",
+ headerType: PackageResponse_Exception,
+ expected: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.Type = test.headerType
+
+ result := pkg.IsResponseWithException()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageGetBodyLen tests GetBodyLen method
+func TestDubboPackageGetBodyLen(t *testing.T) {
+ tests := []struct {
+ desc string
+ bodyLen int
+ expected int
+ }{
+ {
+ desc: "zero length body",
+ bodyLen: 0,
+ expected: 0,
+ },
+ {
+ desc: "100 bytes body",
+ bodyLen: 100,
+ expected: 100,
+ },
+ {
+ desc: "1000 bytes body",
+ bodyLen: 1000,
+ expected: 1000,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.BodyLen = test.bodyLen
+
+ result := pkg.GetBodyLen()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageGetLen tests GetLen method
+func TestDubboPackageGetLen(t *testing.T) {
+ tests := []struct {
+ desc string
+ bodyLen int
+ expected int
+ }{
+ {
+ desc: "zero length body",
+ bodyLen: 0,
+ expected: 16, // HEADER_LENGTH = 16
+ },
+ {
+ desc: "100 bytes body",
+ bodyLen: 100,
+ expected: 116, // 16 + 100
+ },
+ {
+ desc: "1000 bytes body",
+ bodyLen: 1000,
+ expected: 1016, // 16 + 1000
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.BodyLen = test.bodyLen
+
+ result := pkg.GetLen()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageGetBody tests GetBody method
+func TestDubboPackageGetBody(t *testing.T) {
+ tests := []struct {
+ desc string
+ body any
+ expected any
+ }{
+ {
+ desc: "with string body",
+ body: "test body",
+ expected: "test body",
+ },
+ {
+ desc: "with integer body",
+ body: 42,
+ expected: 42,
+ },
+ {
+ desc: "with map body",
+ body: map[string]any{"key": "value"},
+ expected: map[string]any{"key": "value"},
+ },
+ {
+ desc: "with nil body",
+ body: nil,
+ expected: nil,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Body = test.body
+
+ result := pkg.GetBody()
+ assert.Equal(t, test.expected, result)
+ })
+ }
+}
+
+// TestDubboPackageSetBody tests SetBody method
+func TestDubboPackageSetBody(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ testBody := "test body content"
+
+ pkg.SetBody(testBody)
+
+ assert.Equal(t, testBody, pkg.Body)
+ assert.Equal(t, testBody, pkg.GetBody())
+}
+
+// TestDubboPackageSetAndGetHeader tests SetHeader and GetHeader methods
+func TestDubboPackageSetAndGetHeader(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ header := DubboHeader{
+ SerialID: 1,
+ Type: PackageRequest,
+ ID: 12345,
+ BodyLen: 100,
+ ResponseStatus: 0,
+ }
+
+ pkg.SetHeader(header)
+
+ result := pkg.GetHeader()
+ assert.Equal(t, header, result)
+ assert.Equal(t, header.ID, result.ID)
+ assert.Equal(t, header.Type, result.Type)
+}
+
+// TestDubboPackageSetAndGetID tests SetID method
+func TestDubboPackageSetAndGetID(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ testID := int64(999999)
+
+ pkg.SetID(testID)
+
+ assert.Equal(t, testID, pkg.Header.ID)
+ assert.Equal(t, testID, pkg.GetHeader().ID)
+}
+
+// TestDubboPackageSetAndGetService tests SetService and GetService methods
+func TestDubboPackageSetAndGetService(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ service := Service{
+ Path: "com.example.service",
+ Interface: "UserService",
+ Group: "production",
+ Version: "1.0.0",
+ Method: "getUser",
+ Timeout: 5 * time.Second,
+ }
+
+ pkg.SetService(service)
+
+ result := pkg.GetService()
+ assert.Equal(t, service, result)
+ assert.Equal(t, "com.example.service", result.Path)
+ assert.Equal(t, "UserService", result.Interface)
+}
+
+// TestDubboPackageSetResponseStatus tests SetResponseStatus method
+func TestDubboPackageSetResponseStatus(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ status := byte(20)
+
+ pkg.SetResponseStatus(status)
+
+ assert.Equal(t, status, pkg.Header.ResponseStatus)
+}
+
+// TestDubboPackageString tests String method
+func TestDubboPackageString(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Service.Path = "test.service"
+ pkg.Body = "test body"
+
+ result := pkg.String()
+
+ assert.NotEmpty(t, result)
+ assert.Contains(t, result, "HessianPackage")
+ assert.Contains(t, result, "test.service")
+}
+
+// TestDubboPackageSetSerializer tests SetSerializer method
+func TestDubboPackageSetSerializer(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ serializer := &HessianSerializer{}
+
+ // Should not panic
+ assert.NotPanics(t, func() {
+ pkg.SetSerializer(serializer)
+ })
+
+ assert.NotNil(t, pkg.Codec)
+}
diff --git a/protocol/dubbo/impl/request_test.go
b/protocol/dubbo/impl/request_test.go
new file mode 100644
index 000000000..87017f7be
--- /dev/null
+++ b/protocol/dubbo/impl/request_test.go
@@ -0,0 +1,206 @@
+/*
+ * 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 impl
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// TestNewRequestPayload tests creating a new RequestPayload with various
inputs
+func TestNewRequestPayload(t *testing.T) {
+ tests := []struct {
+ desc string
+ args any
+ attachments map[string]any
+ expectParams any
+ expectAttachments map[string]any
+ }{
+ {
+ desc: "with all parameters",
+ args: "request args",
+ attachments: map[string]any{"key": "value"},
+ expectParams: "request args",
+ expectAttachments: map[string]any{"key": "value"},
+ },
+ {
+ desc: "with nil attachments",
+ args: "request args",
+ attachments: nil,
+ expectParams: "request args",
+ expectAttachments: make(map[string]any),
+ },
+ {
+ desc: "with nil args",
+ args: nil,
+ attachments: map[string]any{"trace_id": "12345"},
+ expectParams: nil,
+ expectAttachments: map[string]any{"trace_id": "12345"},
+ },
+ {
+ desc: "with empty attachments",
+ args: []string{"arg1", "arg2"},
+ attachments: map[string]any{},
+ expectParams: []string{"arg1", "arg2"},
+ expectAttachments: map[string]any{},
+ },
+ {
+ desc: "with complex args",
+ args: map[string]any{"user_id": 123,
"name": "test"},
+ attachments: map[string]any{"version": "1.0"},
+ expectParams: map[string]any{"user_id": 123,
"name": "test"},
+ expectAttachments: map[string]any{"version": "1.0"},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := NewRequestPayload(test.args,
test.attachments)
+
+ assert.NotNil(t, payload)
+ assert.Equal(t, test.expectParams, payload.Params)
+ assert.NotNil(t, payload.Attachments)
+ assert.Equal(t, test.expectAttachments,
payload.Attachments)
+ })
+ }
+}
+
+// TestEnsureRequestPayload tests EnsureRequestPayload with various body types
+func TestEnsureRequestPayload(t *testing.T) {
+ tests := []struct {
+ desc string
+ body any
+ expectParams any
+ expectNotNil bool
+ }{
+ {
+ desc: "with RequestPayload object",
+ body: NewRequestPayload("args",
map[string]any{"key": "value"}),
+ expectParams: "args",
+ expectNotNil: true,
+ },
+ {
+ desc: "with string object",
+ body: "string args",
+ expectParams: "string args",
+ expectNotNil: true,
+ },
+ {
+ desc: "with integer object",
+ body: 42,
+ expectParams: 42,
+ expectNotNil: true,
+ },
+ {
+ desc: "with slice object",
+ body: []int{1, 2, 3},
+ expectParams: []int{1, 2, 3},
+ expectNotNil: true,
+ },
+ {
+ desc: "with map object",
+ body: map[string]any{"method": "login", "user":
"admin"},
+ expectParams: map[string]any{"method": "login", "user":
"admin"},
+ expectNotNil: true,
+ },
+ {
+ desc: "with nil object",
+ body: nil,
+ expectParams: nil,
+ expectNotNil: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := EnsureRequestPayload(test.body)
+
+ if test.expectNotNil {
+ assert.NotNil(t, payload)
+ assert.NotNil(t, payload.Attachments)
+ }
+ assert.Equal(t, test.expectParams, payload.Params)
+ })
+ }
+}
+
+// TestNewRequestPayloadNilAttachments tests that nil attachments are
converted to empty map
+func TestNewRequestPayloadNilAttachments(t *testing.T) {
+ payload := NewRequestPayload("args", nil)
+
+ assert.NotNil(t, payload.Attachments)
+ assert.Equal(t, 0, len(payload.Attachments))
+}
+
+// TestRequestPayloadFields tests that RequestPayload fields are correctly set
+func TestRequestPayloadFields(t *testing.T) {
+ tests := []struct {
+ desc string
+ args any
+ attachments map[string]any
+ }{
+ {
+ desc: "request with args and attachments",
+ args: "user login",
+ attachments: map[string]any{"session": "xxx",
"timestamp": 1234567890},
+ },
+ {
+ desc: "request with only args",
+ args: []any{"param1", "param2"},
+ attachments: map[string]any{},
+ },
+ {
+ desc: "request with complex structure",
+ args: map[string]any{"data": []int{1, 2, 3}},
+ attachments: map[string]any{"trace": "abc123", "span":
"def456"},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := NewRequestPayload(test.args,
test.attachments)
+
+ assert.Equal(t, test.args, payload.Params)
+ assert.Equal(t, test.attachments, payload.Attachments)
+ })
+ }
+}
+
+// TestEnsureRequestPayloadWithRequestPayload tests EnsureRequestPayload
returns the same RequestPayload
+func TestEnsureRequestPayloadWithRequestPayload(t *testing.T) {
+ original := NewRequestPayload("args data", map[string]any{"key":
"value"})
+ result := EnsureRequestPayload(original)
+
+ assert.Equal(t, original, result)
+ assert.Equal(t, original.Params, result.Params)
+ assert.Equal(t, original.Attachments, result.Attachments)
+}
+
+// TestEnsureRequestPayloadCreateNewPayload tests EnsureRequestPayload creates
new payload for non-RequestPayload objects
+func TestEnsureRequestPayloadCreateNewPayload(t *testing.T) {
+ testArgs := "test arguments"
+ payload := EnsureRequestPayload(testArgs)
+
+ assert.NotNil(t, payload)
+ assert.Equal(t, testArgs, payload.Params)
+ assert.NotNil(t, payload.Attachments)
+ assert.Equal(t, 0, len(payload.Attachments))
+}
diff --git a/protocol/dubbo/impl/response_test.go
b/protocol/dubbo/impl/response_test.go
new file mode 100644
index 000000000..76ed20d93
--- /dev/null
+++ b/protocol/dubbo/impl/response_test.go
@@ -0,0 +1,219 @@
+/*
+ * 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 impl
+
+import (
+ "errors"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// TestNewResponsePayload tests creating a new ResponsePayload with various
inputs
+func TestNewResponsePayload(t *testing.T) {
+ // Pre-define error variables to ensure consistent comparison
+ testError := errors.New("test error")
+ errorOccurred := errors.New("error occurred")
+
+ tests := []struct {
+ desc string
+ rspObj any
+ exception error
+ attachments map[string]any
+ expectRspObj any
+ expectException error
+ expectAttachments map[string]any
+ }{
+ {
+ desc: "with all parameters",
+ rspObj: "response data",
+ exception: testError,
+ attachments: map[string]any{"key": "value"},
+ expectRspObj: "response data",
+ expectException: testError,
+ expectAttachments: map[string]any{"key": "value"},
+ },
+ {
+ desc: "with nil attachments",
+ rspObj: "response data",
+ exception: nil,
+ attachments: nil,
+ expectRspObj: "response data",
+ expectException: nil,
+ expectAttachments: make(map[string]any),
+ },
+ {
+ desc: "with nil rspObj and exception",
+ rspObj: nil,
+ exception: errorOccurred,
+ attachments: map[string]any{"error_code": 500},
+ expectRspObj: nil,
+ expectException: errorOccurred,
+ expectAttachments: map[string]any{"error_code": 500},
+ },
+ {
+ desc: "with empty attachments",
+ rspObj: 123,
+ exception: nil,
+ attachments: map[string]any{},
+ expectRspObj: 123,
+ expectException: nil,
+ expectAttachments: map[string]any{},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := NewResponsePayload(test.rspObj,
test.exception, test.attachments)
+
+ assert.NotNil(t, payload)
+ assert.Equal(t, test.expectRspObj, payload.RspObj)
+ if test.expectException != nil {
+ assert.Error(t, payload.Exception)
+ } else {
+ assert.NoError(t, payload.Exception)
+ }
+ assert.NotNil(t, payload.Attachments)
+ })
+ }
+}
+
+// TestEnsureResponsePayload tests EnsureResponsePayload with various body
types
+func TestEnsureResponsePayload(t *testing.T) {
+ tests := []struct {
+ desc string
+ body any
+ expectRspObj any
+ expectException error
+ expectAttachments map[string]any
+ expectNotNil bool
+ }{
+ {
+ desc: "with ResponsePayload object",
+ body: NewResponsePayload("data", nil,
map[string]any{"key": "value"}),
+ expectRspObj: "data",
+ expectException: nil,
+ expectNotNil: true,
+ },
+ {
+ desc: "with error object",
+ body: errors.New("test error"),
+ expectRspObj: nil,
+ expectNotNil: true,
+ },
+ {
+ desc: "with string object",
+ body: "string response",
+ expectRspObj: "string response",
+ expectException: nil,
+ expectNotNil: true,
+ },
+ {
+ desc: "with integer object",
+ body: 42,
+ expectRspObj: 42,
+ expectException: nil,
+ expectNotNil: true,
+ },
+ {
+ desc: "with map object",
+ body: map[string]any{"result": "success"},
+ expectRspObj: map[string]any{"result": "success"},
+ expectException: nil,
+ expectNotNil: true,
+ },
+ {
+ desc: "with nil object",
+ body: nil,
+ expectRspObj: nil,
+ expectException: nil,
+ expectNotNil: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := EnsureResponsePayload(test.body)
+
+ if test.expectNotNil {
+ assert.NotNil(t, payload)
+ assert.NotNil(t, payload.Attachments)
+ }
+ assert.Equal(t, test.expectRspObj, payload.RspObj)
+ })
+ }
+}
+
+// TestNewResponsePayloadNilAttachments tests that nil attachments are
converted to empty map
+func TestNewResponsePayloadNilAttachments(t *testing.T) {
+ payload := NewResponsePayload("response", nil, nil)
+
+ assert.NotNil(t, payload.Attachments)
+ assert.Equal(t, 0, len(payload.Attachments))
+}
+
+// TestResponsePayloadFields tests that ResponsePayload fields are correctly
set
+func TestResponsePayloadFields(t *testing.T) {
+ tests := []struct {
+ desc string
+ rspObj any
+ exception error
+ attachments map[string]any
+ }{
+ {
+ desc: "response with data and attachments",
+ rspObj: "user data",
+ exception: nil,
+ attachments: map[string]any{"status": "ok", "code":
200},
+ },
+ {
+ desc: "response with exception",
+ rspObj: nil,
+ exception: errors.New("service error"),
+ attachments: map[string]any{"error_id": "ERR_001"},
+ },
+ {
+ desc: "response with only data",
+ rspObj: []int{1, 2, 3},
+ exception: nil,
+ attachments: map[string]any{},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ payload := NewResponsePayload(test.rspObj,
test.exception, test.attachments)
+
+ assert.Equal(t, test.rspObj, payload.RspObj)
+ assert.Equal(t, test.exception, payload.Exception)
+ assert.Equal(t, test.attachments, payload.Attachments)
+ })
+ }
+}
+
+// TestEnsureResponsePayloadWithResponsePayload tests EnsureResponsePayload
returns the same ResponsePayload
+func TestEnsureResponsePayloadWithResponsePayload(t *testing.T) {
+ original := NewResponsePayload("data", nil, map[string]any{"key":
"value"})
+ result := EnsureResponsePayload(original)
+
+ assert.Equal(t, original, result)
+ assert.Equal(t, original.RspObj, result.RspObj)
+ assert.Equal(t, original.Attachments, result.Attachments)
+}
diff --git a/protocol/dubbo/impl/serialization_test.go
b/protocol/dubbo/impl/serialization_test.go
new file mode 100644
index 000000000..ec95117e4
--- /dev/null
+++ b/protocol/dubbo/impl/serialization_test.go
@@ -0,0 +1,157 @@
+/*
+ * 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 impl
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+// TestGetSerializerById tests GetSerializerById with valid and invalid
serializer IDs
+func TestGetSerializerById(t *testing.T) {
+ // Setup: Create a mock serializer for testing
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ tests := []struct {
+ desc string
+ id byte
+ shouldPanic bool
+ }{
+ {
+ desc: "valid Hessian2 serializer ID",
+ id: constant.SHessian2,
+ shouldPanic: false,
+ },
+ {
+ desc: "valid Protobuf serializer ID",
+ id: constant.SProto,
+ shouldPanic: true, // Will panic because serializer is
not registered
+ },
+ {
+ desc: "invalid serializer ID 255",
+ id: 255,
+ shouldPanic: true, // Will panic because ID is not in
nameMaps
+ },
+ {
+ desc: "default Hessian2 serializer ID 0",
+ id: 0,
+ shouldPanic: true, // Will panic because ID 0 is not
registered in nameMaps
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ if test.shouldPanic {
+ assert.Panics(t, func() {
+ GetSerializerById(test.id)
+ })
+ } else {
+ assert.NotPanics(t, func() {
+ serializer, err :=
GetSerializerById(test.id)
+ assert.NoError(t, err)
+ assert.NotNil(t, serializer)
+ assert.Equal(t, mockSerializer,
serializer)
+ })
+ }
+ })
+ }
+}
+
+// TestGetSerializerByIdConsistency tests that GetSerializerById returns
consistent results
+func TestGetSerializerByIdConsistency(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ // Call multiple times, should get same result
+ result1, err1 := GetSerializerById(constant.SHessian2)
+ result2, err2 := GetSerializerById(constant.SHessian2)
+ result3, err3 := GetSerializerById(constant.SHessian2)
+
+ assert.NoError(t, err1)
+ assert.NoError(t, err2)
+ assert.NoError(t, err3)
+ assert.Equal(t, result1, result2)
+ assert.Equal(t, result2, result3)
+}
+
+// TestSetSerializer tests SetSerializer can register a serializer
+func TestSetSerializer(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+
+ // SetSerializer should not panic
+ assert.NotPanics(t, func() {
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+ })
+
+ // After setting, GetSerializerById should return the same serializer
+ result, err := GetSerializerById(constant.SHessian2)
+ assert.NoError(t, err)
+ assert.Equal(t, mockSerializer, result)
+}
+
+// TestSetSerializerReplaces tests that SetSerializer can replace an existing
serializer
+func TestSetSerializerReplaces(t *testing.T) {
+ mockSerializer1 := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer1)
+
+ result1, _ := GetSerializerById(constant.SHessian2)
+ assert.Equal(t, mockSerializer1, result1)
+
+ // Replace with a new serializer
+ mockSerializer2 := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer2)
+
+ result2, _ := GetSerializerById(constant.SHessian2)
+ assert.Equal(t, mockSerializer2, result2)
+}
+
+// TestSetSerializerWithMultipleNames tests SetSerializer with different
serialization names
+func TestSetSerializerWithMultipleNames(t *testing.T) {
+ hessianSerializer := &HessianSerializer{}
+
+ // Register the same serializer with multiple names
+ SetSerializer(constant.Hessian2Serialization, hessianSerializer)
+
+ // Verify it's registered
+ result, err := GetSerializerById(constant.SHessian2)
+ assert.NoError(t, err)
+ assert.Equal(t, hessianSerializer, result)
+}
+
+// TestSetSerializerNotNil tests SetSerializer with non-nil serializer
+func TestSetSerializerNotNil(t *testing.T) {
+ serializer := &HessianSerializer{}
+
+ // SetSerializer should accept non-nil serializer
+ assert.NotPanics(t, func() {
+ SetSerializer(constant.Hessian2Serialization, serializer)
+ })
+
+ // Verify the serializer is set correctly
+ result, err := GetSerializerById(constant.SHessian2)
+ assert.NoError(t, err)
+ assert.NotNil(t, result)
+}
diff --git a/protocol/dubbo/impl/serialize_test.go
b/protocol/dubbo/impl/serialize_test.go
new file mode 100644
index 000000000..12a21264e
--- /dev/null
+++ b/protocol/dubbo/impl/serialize_test.go
@@ -0,0 +1,486 @@
+/*
+ * 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 impl
+
+import (
+ "bytes"
+ "fmt"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+// TestLoadSerializerWithDefaultHessian2 tests LoadSerializer with default
Hessian2 serialization
+func TestLoadSerializerWithDefaultHessian2(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = 0 // Default SerialID
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerWithHessian2 tests LoadSerializer with explicit Hessian2
serialization
+func TestLoadSerializerWithHessian2(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerWithDifferentValidID tests LoadSerializer with different
valid serializer ID
+func TestLoadSerializerWithDifferentValidID(t *testing.T) {
+ mockHessianSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockHessianSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerWithInvalidID tests LoadSerializer with invalid
serializer ID panics
+func TestLoadSerializerWithInvalidID(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = 255 // Invalid serializer ID
+
+ assert.Panics(t, func() {
+ LoadSerializer(pkg)
+ })
+}
+
+// TestLoadSerializerWithZeroIDDefaultsToHessian2 tests that SerialID 0
defaults to Hessian2
+func TestLoadSerializerWithZeroIDDefaultsToHessian2(t *testing.T) {
+ mockHessianSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockHessianSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = 0
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ // Verify that the loaded serializer is for Hessian2
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerWithNilPackage tests LoadSerializer handles package
correctly
+func TestLoadSerializerWithNilPackage(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ assert.NotPanics(t, func() {
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ })
+}
+
+// TestLoadSerializerMultipleInstances tests that LoadSerializer works with
multiple packages
+func TestLoadSerializerMultipleInstances(t *testing.T) {
+ mockHessianSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockHessianSerializer)
+
+ pkg1 := NewDubboPackage(nil)
+ pkg1.Header.SerialID = constant.SHessian2
+
+ pkg2 := NewDubboPackage(nil)
+ pkg2.Header.SerialID = 0 // Default to Hessian2
+
+ err1 := LoadSerializer(pkg1)
+ err2 := LoadSerializer(pkg2)
+
+ assert.NoError(t, err1)
+ assert.NoError(t, err2)
+ assert.NotNil(t, pkg1.Codec)
+ assert.NotNil(t, pkg2.Codec)
+}
+
+// TestLoadSerializerWithBufferedData tests LoadSerializer with buffered data
in package
+func TestLoadSerializerWithBufferedData(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ data := bytes.NewBuffer([]byte{0x01, 0x02, 0x03})
+ pkg := NewDubboPackage(data)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerDoesNotPanicWithValidID tests LoadSerializer doesn't
panic with valid IDs
+func TestLoadSerializerDoesNotPanicWithValidID(t *testing.T) {
+ mockHessianSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockHessianSerializer)
+
+ tests := []struct {
+ desc string
+ serialID byte
+ shouldFail bool
+ }{
+ {
+ desc: "default Hessian2 (0)",
+ serialID: 0,
+ shouldFail: false,
+ },
+ {
+ desc: "explicit Hessian2",
+ serialID: constant.SHessian2,
+ shouldFail: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = test.serialID
+
+ if test.shouldFail {
+ assert.Panics(t, func() {
+ LoadSerializer(pkg)
+ })
+ } else {
+ assert.NotPanics(t, func() {
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ })
+ }
+ })
+ }
+}
+
+// TestLoadSerializerSetsSerializerInCodec tests that LoadSerializer properly
sets the serializer in the codec
+func TestLoadSerializerSetsSerializerInCodec(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+ assert.NotNil(t, pkg.Codec.serializer)
+}
+
+// TestSerializerInterface tests that Serializer interface is properly defined
+func TestSerializerInterface(t *testing.T) {
+ // Verify that HessianSerializer implements Serializer interface
+ var _ Serializer = (*HessianSerializer)(nil)
+
+ mockSerializer := &HessianSerializer{}
+ assert.NotNil(t, mockSerializer)
+}
+
+// TestLoadSerializerConsistency tests that LoadSerializer gives consistent
results for same input
+func TestLoadSerializerConsistency(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg1 := NewDubboPackage(nil)
+ pkg1.Header.SerialID = constant.SHessian2
+
+ pkg2 := NewDubboPackage(nil)
+ pkg2.Header.SerialID = constant.SHessian2
+
+ err1 := LoadSerializer(pkg1)
+ err2 := LoadSerializer(pkg2)
+
+ assert.NoError(t, err1)
+ assert.NoError(t, err2)
+ assert.Equal(t, err1, err2)
+}
+
+// TestLoadSerializerReplacesExistingSerializer tests that LoadSerializer can
replace existing serializer
+func TestLoadSerializerReplacesExistingSerializer(t *testing.T) {
+ mockSerializer1 := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer1)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err1 := LoadSerializer(pkg)
+ assert.NoError(t, err1)
+
+ // Create a new mock serializer and replace
+ mockSerializer2 := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer2)
+
+ err2 := LoadSerializer(pkg)
+ assert.NoError(t, err2)
+}
+
+// TestLoadSerializerWithAllBytesValues tests LoadSerializer with various byte
values
+func TestLoadSerializerWithAllBytesValues(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ validValues := []byte{
+ 0, // Default - should map to Hessian2
+ constant.SHessian2, // Explicit Hessian2
+ }
+
+ for _, byteVal := range validValues {
+ t.Run(fmt.Sprintf("serial_id_%d", byteVal), func(t *testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = byteVal
+
+ if byteVal == 0 || byteVal == constant.SHessian2 {
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+ }
+ })
+ }
+}
+
+// TestLoadSerializerWithInvalidByteValues tests that invalid byte values panic
+func TestLoadSerializerWithInvalidByteValues(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ // Use a known invalid value that should not be registered as a
serializer ID.
+ invalidValues := []byte{255}
+
+ for _, byteVal := range invalidValues {
+ t.Run(fmt.Sprintf("invalid_serial_id_%d", byteVal), func(t
*testing.T) {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = byteVal
+
+ assert.Panics(t, func() {
+ LoadSerializer(pkg)
+ })
+ })
+ }
+}
+
+// TestLoadSerializerErrorHandling tests error handling in LoadSerializer
+func TestLoadSerializerErrorHandling(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ // Should not raise error with valid setup
+ assert.NotPanics(t, func() {
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ })
+}
+
+// TestLoadSerializerCodecIsNotNil tests that Codec is properly initialized
+func TestLoadSerializerCodecIsNotNil(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ // Before LoadSerializer, Codec should not be nil (created by
NewDubboPackage)
+ assert.NotNil(t, pkg.Codec)
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+
+ // After LoadSerializer, Codec should still exist
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestLoadSerializerWithHeaderData tests LoadSerializer properly uses
Header.SerialID
+func TestLoadSerializerWithHeaderData(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ // Explicitly set Header fields
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 12345
+ pkg.Header.Type = PackageRequest
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.Equal(t, constant.SHessian2, pkg.Header.SerialID)
+ assert.Equal(t, int64(12345), pkg.Header.ID)
+}
+
+// TestLoadSerializerSequentialCalls tests multiple sequential LoadSerializer
calls
+func TestLoadSerializerSequentialCalls(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ for i := 0; i < 5; i++ {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = int64(i)
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+ }
+}
+
+// TestLoadSerializerWithDataBuffer tests LoadSerializer with data buffer
+func TestLoadSerializerWithDataBuffer(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ testData := []byte{0xDA, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+ data := bytes.NewBuffer(testData)
+ pkg := NewDubboPackage(data)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec)
+}
+
+// TestHessianSerializerMarshalRequest tests Hessian serializer marshal for
request
+func TestHessianSerializerMarshalRequest(t *testing.T) {
+ serializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, serializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Body = []any{"test"}
+ pkg.Header.Type = PackageRequest
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 12345
+ pkg.Service.Interface = "TestInterface"
+ pkg.Service.Path = "test.path"
+ pkg.Service.Method = "testMethod"
+ pkg.SetSerializer(serializer)
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ assert.Greater(t, data.Len(), 0)
+}
+
+// TestHessianSerializerMarshalResponse tests Hessian serializer marshal for
response
+func TestHessianSerializerMarshalResponse(t *testing.T) {
+ serializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, serializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Body = "response data"
+ pkg.Header.Type = PackageResponse
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 54321
+ pkg.Header.ResponseStatus = Response_OK
+ pkg.SetSerializer(serializer)
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ assert.Greater(t, data.Len(), 0)
+}
+
+// TestHessianSerializerMarshalResponseWithException tests Hessian marshaling
for exception response
+func TestHessianSerializerMarshalResponseWithException(t *testing.T) {
+ serializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, serializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Body = []any{} // Empty body for exception response
+ pkg.Header.Type = PackageResponse
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 99999
+ pkg.Header.ResponseStatus = Response_SERVICE_ERROR
+ pkg.SetSerializer(serializer)
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+}
+
+// TestHessianSerializerMarshalHeartbeat tests Hessian serializer marshal for
heartbeat
+func TestHessianSerializerMarshalHeartbeat(t *testing.T) {
+ serializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, serializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Body = []any{}
+ pkg.Header.Type = PackageHeartbeat
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.ID = 77777
+ pkg.SetSerializer(serializer)
+
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+ assert.Greater(t, data.Len(), 0)
+}
+
+// TestLoadSerializerWithMultipleCalls tests LoadSerializer consistency
+func TestLoadSerializerWithMultipleCalls(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ // Call LoadSerializer multiple times
+ for i := 0; i < 5; i++ {
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+ assert.NotNil(t, pkg.Codec.serializer)
+ }
+}
+
+// TestLoadSerializerPackageInheritance tests that loaded serializer persists
in package
+func TestLoadSerializerPackageInheritance(t *testing.T) {
+ mockSerializer := &HessianSerializer{}
+ SetSerializer(constant.Hessian2Serialization, mockSerializer)
+
+ pkg := NewDubboPackage(nil)
+ pkg.Header.SerialID = constant.SHessian2
+ pkg.Header.Type = PackageRequest
+ pkg.Header.ID = 55555
+ pkg.Service.Interface = "TestInterface"
+ pkg.Body = []any{"test"}
+
+ err := LoadSerializer(pkg)
+ assert.NoError(t, err)
+
+ // Verify serializer is accessible for marshaling
+ data, err := pkg.Marshal()
+ assert.NoError(t, err)
+ assert.NotNil(t, data)
+}
diff --git a/protocol/dubbo/opentracing_test.go
b/protocol/dubbo/opentracing_test.go
new file mode 100644
index 000000000..35319bdbf
--- /dev/null
+++ b/protocol/dubbo/opentracing_test.go
@@ -0,0 +1,275 @@
+/*
+ * 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 dubbo
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/opentracing/opentracing-go"
+ "github.com/opentracing/opentracing-go/mocktracer"
+
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+)
+
+// TestFilterContext tests the filterContext function
+func TestFilterContext(t *testing.T) {
+ tests := []struct {
+ desc string
+ attachments map[string]any
+ expectedLen int
+ expectedContains map[string]string
+ notExpectedContain []string
+ }{
+ {
+ desc: "empty attachments",
+ attachments: make(map[string]any),
+ expectedLen: 0,
+ expectedContains: map[string]string{},
+ notExpectedContain: []string{},
+ },
+ {
+ desc: "only string values",
+ attachments: map[string]any{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ },
+ expectedLen: 3,
+ expectedContains: map[string]string{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ },
+ notExpectedContain: []string{},
+ },
+ {
+ desc: "mixed types",
+ attachments: map[string]any{
+ "stringKey": "stringValue",
+ "intKey": 123,
+ "boolKey": true,
+ "anotherStringKey": "anotherValue",
+ },
+ expectedLen: 2,
+ expectedContains: map[string]string{
+ "stringKey": "stringValue",
+ "anotherStringKey": "anotherValue",
+ },
+ notExpectedContain: []string{"intKey", "boolKey"},
+ },
+ {
+ desc: "with nil values",
+ attachments: map[string]any{
+ "stringKey": "stringValue",
+ "nilKey": nil,
+ "intKey": 456,
+ },
+ expectedLen: 1,
+ expectedContains: map[string]string{
+ "stringKey": "stringValue",
+ },
+ notExpectedContain: []string{"nilKey", "intKey"},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ result := filterContext(test.attachments)
+ assert.Equal(t, test.expectedLen, len(result))
+ for k, v := range test.expectedContains {
+ assert.Equal(t, v, result[k])
+ }
+ for _, k := range test.notExpectedContain {
+ assert.NotContains(t, result, k)
+ }
+ })
+ }
+}
+
+// TestFillTraceAttachments tests the fillTraceAttachments function
+func TestFillTraceAttachments(t *testing.T) {
+ tests := []struct {
+ desc string
+ initialAttachments map[string]any
+ traceAttachment map[string]string
+ expectedAttachments map[string]any
+ }{
+ {
+ desc: "filling empty attachments",
+ initialAttachments: make(map[string]any),
+ traceAttachment: map[string]string{
+ "trace.key1": "trace.value1",
+ "trace.key2": "trace.value2",
+ },
+ expectedAttachments: map[string]any{
+ "trace.key1": "trace.value1",
+ "trace.key2": "trace.value2",
+ },
+ },
+ {
+ desc: "filling attachments with existing values",
+ initialAttachments: map[string]any{
+ "existing": "value",
+ },
+ traceAttachment: map[string]string{
+ "trace.key1": "trace.value1",
+ },
+ expectedAttachments: map[string]any{
+ "existing": "value",
+ "trace.key1": "trace.value1",
+ },
+ },
+ {
+ desc: "with empty trace attachments",
+ initialAttachments: map[string]any{
+ "key": "value",
+ },
+ traceAttachment: make(map[string]string),
+ expectedAttachments: map[string]any{
+ "key": "value",
+ },
+ },
+ {
+ desc: "overwriting existing keys",
+ initialAttachments: map[string]any{
+ "key": "oldValue",
+ },
+ traceAttachment: map[string]string{
+ "key": "newValue",
+ },
+ expectedAttachments: map[string]any{
+ "key": "newValue",
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ fillTraceAttachments(test.initialAttachments,
test.traceAttachment)
+ assert.Equal(t, len(test.expectedAttachments),
len(test.initialAttachments))
+ for k, v := range test.expectedAttachments {
+ assert.Equal(t, v, test.initialAttachments[k])
+ }
+ })
+ }
+}
+
+// TestInjectTraceCtx tests the injectTraceCtx function
+func TestInjectTraceCtx(t *testing.T) {
+ opentracing.SetGlobalTracer(mocktracer.New())
+
+ tests := []struct {
+ desc string
+ attachments map[string]any
+ expectError bool
+ }{
+ {
+ desc: "with empty attachments",
+ attachments: make(map[string]any),
+ expectError: false,
+ },
+ {
+ desc: "with existing string attachments",
+ attachments: map[string]any{
+ constant.VersionKey: "1.0",
+ constant.GroupKey: "testGroup",
+ },
+ expectError: false,
+ },
+ {
+ desc: "with mixed type attachments",
+ attachments: map[string]any{
+ "stringKey": "stringValue",
+ "intKey": 123,
+ "boolKey": true,
+ },
+ expectError: false,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ inv := invocation.NewRPCInvocation("testMethod",
[]any{}, test.attachments)
+ span := opentracing.StartSpan("test-span")
+ err := injectTraceCtx(span, inv)
+ span.Finish()
+
+ if test.expectError {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, inv.Attachments())
+ }
+ })
+ }
+}
+
+// TestInjectTraceCtxIntegration tests the complete flow of filtering and
filling trace attachments
+func TestInjectTraceCtxIntegration(t *testing.T) {
+ mockTracer := mocktracer.New()
+ opentracing.SetGlobalTracer(mockTracer)
+
+ tests := []struct {
+ desc string
+ attachments map[string]any
+ methods []any
+ }{
+ {
+ desc: "with version and group",
+ attachments: map[string]any{
+ constant.VersionKey: "1.0.0",
+ constant.GroupKey: "myGroup",
+ "customKey": "customValue",
+ },
+ methods: []any{"arg1", "arg2"},
+ },
+ {
+ desc: "with minimal attachments",
+ attachments: map[string]any{
+ constant.VersionKey: "1.0",
+ },
+ methods: []any{},
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.desc, func(t *testing.T) {
+ inv := invocation.NewRPCInvocation("myMethod",
test.methods, test.attachments)
+
+ span := opentracing.StartSpan("test-operation")
+ err := injectTraceCtx(span, inv)
+ span.Finish()
+
+ assert.NoError(t, err)
+ finalAttachments := inv.Attachments()
+ assert.NotNil(t, finalAttachments)
+
+ // Verify original attachments are preserved
+ for k, v := range test.attachments {
+ assert.Equal(t, v, finalAttachments[k])
+ }
+ })
+ }
+}