Skip to content

Commit

Permalink
add metadata unit test (apache#2665)
Browse files Browse the repository at this point in the history
  • Loading branch information
FoghostCn authored Apr 29, 2024
1 parent e386c9e commit cefda66
Show file tree
Hide file tree
Showing 17 changed files with 1,443 additions and 125 deletions.
19 changes: 10 additions & 9 deletions global/application_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ func (c *ApplicationConfig) Clone() *ApplicationConfig {
}

return &ApplicationConfig{
Organization: c.Organization,
Name: c.Name,
Module: c.Module,
Group: c.Group,
Version: c.Version,
Owner: c.Owner,
Environment: c.Environment,
MetadataType: c.MetadataType,
Tag: c.Tag,
Organization: c.Organization,
Name: c.Name,
Module: c.Module,
Group: c.Group,
Version: c.Version,
Owner: c.Owner,
Environment: c.Environment,
MetadataType: c.MetadataType,
Tag: c.Tag,
MetadataServicePort: c.MetadataServicePort,
}
}
41 changes: 13 additions & 28 deletions metadata/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ package metadata
import (
"context"
"encoding/json"
"time"
)

import (
Expand All @@ -37,47 +36,33 @@ import (
"dubbo.apache.org/dubbo-go/v3/registry"
)

const metadataProxyDefaultTimeout = 5000
const defaultTimeout = "5s" // s

// GetMetadataFromMetadataReport test depends on dubbo protocol, if dubbo not dependent on config package, can move to metadata dir
func GetMetadataFromMetadataReport(revision string, instance registry.ServiceInstance) (*info.MetadataInfo, error) {
report := GetMetadataReport()
if report == nil {
return nil, perrors.New("no metadata report instance found,please check ")
}
return report.GetAppMetadata(instance.GetServiceName(), revision)
}

func GetMetadataFromRpc(revision string, instance registry.ServiceInstance) (*info.MetadataInfo, error) {
service, destroy, err := createRpcClient(instance)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(metadataProxyDefaultTimeout))
defer cancel()
defer destroy()
return service.GetMetadataInfo(ctx, revision)
}

type remoteMetadataService struct {
GetMetadataInfo func(context context.Context, revision string) (*info.MetadataInfo, error) `dubbo:"getMetadataInfo"`
}

func createRpcClient(instance registry.ServiceInstance) (*remoteMetadataService, func(), error) {
params := getMetadataServiceUrlParams(instance.GetMetadata()[constant.MetadataServiceURLParamsPropertyName])
url := buildMetadataServiceURL(instance.GetServiceName(), instance.GetHost(), params)
return createRpcClientByUrl(url)
}

func createRpcClientByUrl(url *common.URL) (*remoteMetadataService, func(), error) {
url.SetParam(constant.TimeoutKey, defaultTimeout)
rpcService := &remoteMetadataService{}
invoker := extension.GetProtocol(constant.Dubbo).Refer(url)
if invoker == nil {
return nil, nil, perrors.New("create invoker error, can not connect to the metadata report server: " + url.Ip + ":" + url.Port)
return nil, perrors.New("create invoker error, can not connect to the metadata report server: " + url.Ip + ":" + url.Port)
}
proxy := extension.GetProxyFactory(constant.DefaultKey).GetProxy(invoker, url)
proxy.Implement(rpcService)
destroy := func() {
invoker.Destroy()
}
return rpcService, destroy, nil
defer invoker.Destroy()
return rpcService.GetMetadataInfo(context.TODO(), revision)
}

type remoteMetadataService struct {
GetMetadataInfo func(context context.Context, revision string) (*info.MetadataInfo, error) `dubbo:"getMetadataInfo"`
}

// buildMetadataServiceURL will use standard format to build the metadata service url.
Expand Down Expand Up @@ -108,7 +93,7 @@ func getMetadataServiceUrlParams(jsonStr string) map[string]string {
if len(jsonStr) > 0 {
err := json.Unmarshal([]byte(jsonStr), &res)
if err != nil {
logger.Errorf("could not parse the metadata service url parameters to map", err)
logger.Errorf("could not parse the metadata service url parameters '%s' to map", jsonStr)
}
}
return res
Expand Down
280 changes: 280 additions & 0 deletions metadata/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/*
* 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 metadata

import (
"context"
"testing"
)

import (
"github.com/pkg/errors"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

import (
"dubbo.apache.org/dubbo-go/v3/common"
"dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/common/extension"
"dubbo.apache.org/dubbo-go/v3/metadata/info"
"dubbo.apache.org/dubbo-go/v3/protocol"
_ "dubbo.apache.org/dubbo-go/v3/proxy/proxy_factory"
"dubbo.apache.org/dubbo-go/v3/registry"
)

var (
ins = &registry.DefaultServiceInstance{
ID: "1",
Metadata: map[string]string{
constant.MetadataServiceURLParamsPropertyName: `{
"application": "dubbo-go",
"group": "BDTService",
"port": "64658",
"protocol": "dubbo",
"version": "1.0.0"
}`,
},
Host: "dubbo.io",
ServiceName: "dubbo-app",
}
metadataInfo = &info.MetadataInfo{
App: "dubbo-app",
}
)

func TestGetMetadataFromMetadataReport(t *testing.T) {
t.Run("no report instance", func(t *testing.T) {
_, err := GetMetadataFromMetadataReport("1", ins)
assert.NotNil(t, err)
})
mockReport := new(mockMetadataReport)
defer mockReport.AssertExpectations(t)
instances["default"] = mockReport
t.Run("normal", func(t *testing.T) {
mockReport.On("GetAppMetadata").Return(metadataInfo, nil).Once()
got, err := GetMetadataFromMetadataReport("1", ins)
assert.Nil(t, err)
assert.Equal(t, metadataInfo, got)
})
t.Run("error", func(t *testing.T) {
mockReport.On("GetAppMetadata").Return(metadataInfo, errors.New("mock error")).Once()
_, err := GetMetadataFromMetadataReport("1", ins)
assert.NotNil(t, err)
})
}

func TestGetMetadataFromRpc(t *testing.T) {
mockInvoker := new(mockInvoker)
defer mockInvoker.AssertExpectations(t)
mockProtocol := new(mockProtocol)
defer mockProtocol.AssertExpectations(t)
extension.SetProtocol("dubbo", func() protocol.Protocol {
return mockProtocol
})

result := &protocol.RPCResult{
Attrs: map[string]interface{}{},
Err: nil,
Rest: metadataInfo,
}
t.Run("normal", func(t *testing.T) {
mockProtocol.On("Refer").Return(mockInvoker).Once()
mockInvoker.On("Invoke").Return(result).Once()
mockInvoker.On("Destroy").Once()
metadata, err := GetMetadataFromRpc("111", ins)
assert.Nil(t, err)
assert.Equal(t, metadata, result.Rest)
})
t.Run("refer error", func(t *testing.T) {
mockProtocol.On("Refer").Return(nil).Once()
_, err := GetMetadataFromRpc("111", ins)
assert.NotNil(t, err)
})
t.Run("invoke timeout", func(t *testing.T) {
mockProtocol.On("Refer").Return(mockInvoker).Once()
mockInvoker.On("Invoke").Return(&protocol.RPCResult{
Attrs: map[string]interface{}{},
Err: errors.New("timeout error"),
Rest: metadataInfo,
}).Once()
mockInvoker.On("Destroy").Once()
_, err := GetMetadataFromRpc("111", ins)
assert.NotNil(t, err)
})
}

func Test_buildMetadataServiceURL(t *testing.T) {
type args struct {
serviceName string
host string
params map[string]string
}
tests := []struct {
name string
args args
want *common.URL
}{
{
name: "normal",
args: args{
serviceName: "dubbo-app",
host: "dubbo.io",
params: map[string]string{
constant.ProtocolKey: "dubbo",
constant.PortKey: "3000",
},
},
want: common.NewURLWithOptions(
common.WithIp("dubbo.io"),
common.WithProtocol("dubbo"),
common.WithPath(constant.MetadataServiceName),
common.WithProtocol("dubbo"),
common.WithPort("3000"),
common.WithParams(map[string][]string{
constant.ProtocolKey: {"dubbo"},
constant.PortKey: {"3000"},
}),
common.WithParamsValue(constant.GroupKey, "dubbo-app"),
common.WithParamsValue(constant.InterfaceKey, constant.MetadataServiceName),
),
},
{
name: "no protocol",
args: args{
serviceName: "dubbo-app",
host: "dubbo.io",
params: map[string]string{},
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, buildMetadataServiceURL(tt.args.serviceName, tt.args.host, tt.args.params), "buildMetadataServiceURL(%v, %v, %v)", tt.args.serviceName, tt.args.host, tt.args.params)
})
}
}

func Test_getMetadataServiceUrlParams(t *testing.T) {
type args struct {
jsonStr string
}
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "normal",
args: args{
jsonStr: `{
"application": "BDTService",
"group": "BDTService",
"port": "64658",
"protocol": "dubbo",
"release": "dubbo-golang-3.0.0",
"timestamp": "1713432877",
"version": "1.0.0"
}`,
},
want: map[string]string{
"application": "BDTService",
"group": "BDTService",
"port": "64658",
"protocol": "dubbo",
"release": "dubbo-golang-3.0.0",
"timestamp": "1713432877",
"version": "1.0.0",
},
},
{
name: "wrong format",
args: args{
jsonStr: "xxx",
},
want: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, getMetadataServiceUrlParams(tt.args.jsonStr), "getMetadataServiceUrlParams(%v)", tt.args.jsonStr)
})
}
}

type mockProtocol struct {
mock.Mock
}

func (m *mockProtocol) Export(invoker protocol.Invoker) protocol.Exporter {
args := m.Called()
return args.Get(0).(protocol.Exporter)
}

func (m *mockProtocol) Refer(url *common.URL) protocol.Invoker {
args := m.Called()
if args.Get(0) == nil {
return nil
}
return args.Get(0).(protocol.Invoker)
}

func (m *mockProtocol) Destroy() {
}

type mockInvoker struct {
mock.Mock
}

func (m *mockInvoker) GetURL() *common.URL {
return nil
}

func (m *mockInvoker) IsAvailable() bool {
return true
}

func (m *mockInvoker) Destroy() {
m.Called()
}

func (m *mockInvoker) Invoke(ctx context.Context, inv protocol.Invocation) protocol.Result {
args := m.Mock.Called()
meta := args.Get(0).(protocol.Result).Result().(*info.MetadataInfo)
reply := inv.Reply().(*info.MetadataInfo)
reply.App = meta.App
reply.Tag = meta.Tag
reply.Revision = meta.Revision
reply.Services = meta.Services
return args.Get(0).(protocol.Result)
}

type mockExporter struct {
mock.Mock
}

func (m *mockExporter) GetInvoker() protocol.Invoker {
args := m.Called()
return args.Get(0).(protocol.Invoker)
}

func (m *mockExporter) UnExport() {
m.Called()
}
Loading

0 comments on commit cefda66

Please sign in to comment.