Repository: go-kratos/kratos
Pull request #1063 (2021-06-15 09:07:29)

config test


Patch:
diff --git a/config/options.go b/config/options.go
index ec583e3f..5bc9ebf0 100644
--- a/config/options.go
+++ b/config/options.go
@@ -30,7 +30,7 @@ func WithDecoder(d Decoder) Option {
 	}
 }
 
-// WithLogger with config loogger.
+// WithLogger with config logger.
 func WithLogger(l log.Logger) Option {
 	return func(o *options) {
 		o.logger = l
diff --git a/encoding/json/json.go b/encoding/json/json.go
index 826b688e..f2f3097e 100644
--- a/encoding/json/json.go
+++ b/encoding/json/json.go
@@ -13,7 +13,7 @@ import (
 const Name = "json"
 
 var (
-	// MarshalOptions is a configurable JSON format marshaler.
+	// MarshalOptions is a configurable JSON format marshaller.
 	MarshalOptions = protojson.MarshalOptions{
 		EmitUnpopulated: true,
 	}
diff --git a/log/helper.go b/log/helper.go
index 7e6a25d1..dea23c76 100644
--- a/log/helper.go
+++ b/log/helper.go
@@ -3,6 +3,7 @@ package log
 import (
 	"context"
 	"fmt"
+	"os"
 )
 
 // Helper is a logger helper.
@@ -89,3 +90,21 @@ func (h *Helper) Errorf(format string, a ...interface{}) {
 func (h *Helper) Errorw(keyvals ...interface{}) {
 	h.logger.Log(LevelError, keyvals...)
 }
+
+// Fatal logs a message at fatal level.
+func (h *Helper) Fatal(a ...interface{}) {
+	h.logger.Log(LevelFatal, "msg", fmt.Sprint(a...))
+	os.Exit(1)
+}
+
+// Fatalf logs a message at fatal level.
+func (h *Helper) Fatalf(format string, a ...interface{}) {
+	h.logger.Log(LevelFatal, "msg", fmt.Sprintf(format, a...))
+	os.Exit(1)
+}
+
+// Fatalw logs a message at fatal level.
+func (h *Helper) Fatalw(keyvals ...interface{}) {
+	h.logger.Log(LevelFatal, keyvals...)
+	os.Exit(1)
+}
diff --git a/log/level.go b/log/level.go
index d76fc411..2dfa4ed4 100644
--- a/log/level.go
+++ b/log/level.go
@@ -17,6 +17,8 @@ const (
 	LevelWarn
 	// LevelError is logger error level.
 	LevelError
+	// LevelFatal is logger fatal level
+	LevelFatal
 )
 
 func (l Level) String() string {
@@ -29,6 +31,8 @@ func (l Level) String() string {
 		return "WARN"
 	case LevelError:
 		return "ERROR"
+	case LevelFatal:
+		return "FATAL"
 	default:
 		return ""
 	}
@@ -45,6 +49,8 @@ func ParseLevel(s string) Level {
 		return LevelWarn
 	case "ERROR":
 		return LevelError
+	case "FATAL":
+		return LevelFatal
 	}
 	return LevelInfo
 }
diff --git a/middleware/metadata/metadata.go b/middleware/metadata/metadata.go
index aaa56674..d251ec56 100644
--- a/middleware/metadata/metadata.go
+++ b/middleware/metadata/metadata.go
@@ -17,6 +17,16 @@ type options struct {
 	md     metadata.Metadata
 }
 
+func (o *options) hasPrefix(key string) bool {
+	k := strings.ToLower(key)
+	for _, prefix := range o.prefix {
+		if strings.HasPrefix(k, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
 // WithConstants with constant metadata key value.
 func WithConstants(md metadata.Metadata) Option {
 	return func(o *options) {
@@ -33,22 +43,19 @@ func WithPropagatedPrefix(prefix ...string) Option {
 
 // Server is middleware server-side metadata.
 func Server(opts ...Option) middleware.Middleware {
-	options := options{
+	options := &options{
 		prefix: []string{"x-md-"}, // x-md-global-, x-md-local
 	}
 	for _, o := range opts {
-		o(&options)
+		o(options)
 	}
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
 			if tr, ok := transport.FromServerContext(ctx); ok {
-				md := metadata.New()
+				md := options.md.Clone()
 				for _, k := range tr.Header().Keys() {
-					for _, prefix := range options.prefix {
-						if strings.HasPrefix(strings.ToLower(k), prefix) {
-							md.Set(k, tr.Header().Get(k))
-							break
-						}
+					if options.hasPrefix(k) {
+						md.Set(k, tr.Header().Get(k))
 					}
 				}
 				ctx = metadata.NewServerContext(ctx, md)
@@ -60,11 +67,11 @@ func Server(opts ...Option) middleware.Middleware {
 
 // Client is middleware client-side metadata.
 func Client(opts ...Option) middleware.Middleware {
-	options := options{
+	options := &options{
 		prefix: []string{"x-md-global-"},
 	}
 	for _, o := range opts {
-		o(&options)
+		o(options)
 	}
 	return func(handler middleware.Handler) middleware.Handler {
 		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
@@ -81,11 +88,8 @@ func Client(opts ...Option) middleware.Middleware {
 				// x-md-global-
 				if md, ok := metadata.FromServerContext(ctx); ok {
 					for k, v := range md {
-						for _, prefix := range options.prefix {
-							if strings.HasPrefix(k, prefix) {
-								tr.Header().Set(k, v)
-								break
-							}
+						if options.hasPrefix(k) {
+							tr.Header().Set(k, v)
 						}
 					}
 				}


Test patch:
diff --git a/config/reader_test.go b/config/reader_test.go
new file mode 100644
index 00000000..d7c3d179
--- /dev/null
+++ b/config/reader_test.go
@@ -0,0 +1,128 @@
+package config
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/encoding"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReader_Merge(t *testing.T) {
+	var (
+		err error
+		ok  bool
+	)
+	opts := options{
+		decoder: func(kv *KeyValue, v map[string]interface{}) error {
+			if codec := encoding.GetCodec(kv.Format); codec != nil {
+				return codec.Unmarshal(kv.Value, &v)
+			}
+			return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format)
+		},
+	}
+	r := newReader(opts)
+	err = r.Merge(&KeyValue{
+		Key:    "a",
+		Value:  []byte("bad"),
+		Format: "json",
+	})
+	assert.Error(t, err)
+
+	err = r.Merge(&KeyValue{
+		Key:    "b",
+		Value:  []byte(`{"nice": "boat", "x": 1}`),
+		Format: "json",
+	})
+	assert.NoError(t, err)
+	vv, ok := r.Value("nice")
+	assert.True(t, ok)
+	vvv, err := vv.String()
+	assert.NoError(t, err)
+	assert.Equal(t, "boat", vvv)
+
+	err = r.Merge(&KeyValue{
+		Key:    "b",
+		Value:  []byte(`{"x": 2}`),
+		Format: "json",
+	})
+	assert.NoError(t, err)
+	vv, ok = r.Value("x")
+	assert.True(t, ok)
+	vvx, err := vv.Int()
+	assert.NoError(t, err)
+	assert.Equal(t, int64(2), vvx)
+}
+
+func TestReader_Value(t *testing.T) {
+	var (
+		err error
+		ok  bool
+	)
+	opts := options{
+		decoder: func(kv *KeyValue, v map[string]interface{}) error {
+			if codec := encoding.GetCodec(kv.Format); codec != nil {
+				return codec.Unmarshal(kv.Value, &v)
+			}
+			return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format)
+		},
+	}
+	r := newReader(opts)
+	err = r.Merge(&KeyValue{
+		Key:    "b",
+		Value:  []byte(`{"a": {"b": {"X": 1, "Y": "lol", "z": true}}}`),
+		Format: "json",
+	})
+	assert.NoError(t, err)
+	vv, ok := r.Value("a.b.X")
+	assert.True(t, ok)
+	vvv, err := vv.Int()
+	assert.NoError(t, err)
+	assert.Equal(t, int64(1), vvv)
+
+	assert.NoError(t, err)
+	vv, ok = r.Value("a.b.Y")
+	assert.True(t, ok)
+	vvy, err := vv.String()
+	assert.NoError(t, err)
+	assert.Equal(t, "lol", vvy)
+
+	assert.NoError(t, err)
+	vv, ok = r.Value("a.b.z")
+	assert.True(t, ok)
+	vvz, err := vv.Bool()
+	assert.NoError(t, err)
+	assert.Equal(t, true, vvz)
+
+	vv, ok = r.Value("aasasdg=234l.asdfk,")
+	assert.False(t, ok)
+
+	vv, ok = r.Value("aas......asdg=234l.asdfk,")
+	assert.False(t, ok)
+
+	vv, ok = r.Value("a.b.Y.")
+	assert.False(t, ok)
+}
+
+func TestReader_Source(t *testing.T) {
+	var (
+		err error
+	)
+	opts := options{
+		decoder: func(kv *KeyValue, v map[string]interface{}) error {
+			if codec := encoding.GetCodec(kv.Format); codec != nil {
+				return codec.Unmarshal(kv.Value, &v)
+			}
+			return fmt.Errorf("unsupported key: %s format: %s", kv.Key, kv.Format)
+		},
+	}
+	r := newReader(opts)
+	err = r.Merge(&KeyValue{
+		Key:    "b",
+		Value:  []byte(`{"a": {"b": {"X": 1}}}`),
+		Format: "json",
+	})
+	b, err := r.Source()
+	assert.NoError(t, err)
+	assert.Equal(t, []byte(`{"a":{"b":{"X":1}}}`), b)
+}
diff --git a/config/value_test.go b/config/value_test.go
new file mode 100644
index 00000000..4219bdf8
--- /dev/null
+++ b/config/value_test.go
@@ -0,0 +1,121 @@
+package config
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_atomicValue_Bool(t *testing.T) {
+	var vlist []interface{}
+	vlist = []interface{}{"1", "t", "T", "true", "TRUE", "True", true}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Bool()
+		assert.NoError(t, err, b)
+		assert.True(t, b, b)
+	}
+
+	vlist = []interface{}{"0", "f", "F", "false", "FALSE", "False", false}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Bool()
+		assert.NoError(t, err, b)
+		assert.False(t, b, b)
+	}
+
+	vlist = []interface{}{int32(1), 1, uint16(1), "bbb", "-1"}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Bool()
+		assert.Error(t, err, b)
+	}
+}
+
+func Test_atomicValue_Int(t *testing.T) {
+	var vlist []interface{}
+	vlist = []interface{}{"123123", float64(123123), int64(123123)}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Int()
+		assert.NoError(t, err, b)
+		assert.Equal(t, int64(123123), b, b)
+	}
+
+	vlist = []interface{}{int32(1123123), 123131, uint16(1), "bbb", "-x1"}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Int()
+		assert.Error(t, err, b)
+	}
+}
+
+func Test_atomicValue_Float(t *testing.T) {
+	var vlist []interface{}
+	vlist = []interface{}{"123123.1", float64(123123.1)}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Float()
+		assert.NoError(t, err, b)
+		assert.Equal(t, float64(123123.1), b, b)
+	}
+
+	vlist = []interface{}{float32(1123123), 123131, uint16(1), "bbb", "-x1"}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Float()
+		assert.Error(t, err, b)
+	}
+}
+
+func Test_atomicValue_String(t *testing.T) {
+	var vlist []interface{}
+	vlist = []interface{}{"1", float64(1), int64(1)}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.String()
+		assert.NoError(t, err, b)
+		assert.Equal(t, "1", b, b)
+	}
+
+	v := atomicValue{}
+	v.Store(true)
+	b, err := v.String()
+	assert.NoError(t, err, b)
+	assert.Equal(t, "true", b, b)
+}
+
+func Test_atomicValue_Duration(t *testing.T) {
+	var vlist []interface{}
+	vlist = []interface{}{int64(5)}
+	for _, x := range vlist {
+		v := atomicValue{}
+		v.Store(x)
+		b, err := v.Duration()
+		assert.NoError(t, err)
+		assert.Equal(t, time.Duration(5), b)
+	}
+}
+
+func Test_atomicValue_Scan(t *testing.T) {
+	var err error
+	v := atomicValue{}
+	err = v.Scan(&struct {
+		A string `json:"a"`
+	}{"a"})
+	assert.NoError(t, err)
+
+	err = v.Scan(&struct {
+		A string `json:"a"`
+	}{"a"})
+	assert.NoError(t, err)
+}
diff --git a/middleware/metadata/metadata_test.go b/middleware/metadata/metadata_test.go
new file mode 100644
index 00000000..917dea59
--- /dev/null
+++ b/middleware/metadata/metadata_test.go
@@ -0,0 +1,127 @@
+package metadata
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/go-kratos/kratos/v2/metadata"
+	"github.com/go-kratos/kratos/v2/transport"
+)
+
+type headerCarrier http.Header
+
+func (hc headerCarrier) Get(key string) string { return http.Header(hc).Get(key) }
+
+func (hc headerCarrier) Set(key string, value string) { http.Header(hc).Set(key, value) }
+
+// Keys lists the keys stored in this carrier.
+func (hc headerCarrier) Keys() []string {
+	keys := make([]string, 0, len(hc))
+	for k := range http.Header(hc) {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
+type testTransport struct{ header headerCarrier }
+
+func (tr *testTransport) Kind() transport.Kind     { return transport.KindHTTP }
+func (tr *testTransport) Endpoint() string         { return "" }
+func (tr *testTransport) Operation() string        { return "" }
+func (tr *testTransport) Header() transport.Header { return tr.header }
+
+func TestSever(t *testing.T) {
+	var (
+		globalKey   = "x-md-global-key"
+		globalValue = "global-value"
+		localKey    = "x-md-local-key"
+		localValue  = "local-value"
+		constKey    = "x-md-local-const"
+		constValue  = "x-md-local-const"
+	)
+	hs := func(ctx context.Context, in interface{}) (interface{}, error) {
+		md, ok := metadata.FromServerContext(ctx)
+		if !ok {
+			return nil, errors.New("no md")
+		}
+		if md.Get(constKey) != constValue {
+			return nil, errors.New("const not equal")
+		}
+		if md.Get(globalKey) != globalValue {
+			return nil, errors.New("global not equal")
+		}
+		if md.Get(localKey) != localValue {
+			return nil, errors.New("local not equal")
+		}
+		return in, nil
+	}
+	hc := headerCarrier{}
+	hc.Set(globalKey, globalValue)
+	hc.Set(localKey, localValue)
+	ctx := transport.NewServerContext(context.Background(), &testTransport{hc})
+	// const md
+	constMD := metadata.New()
+	constMD.Set(constKey, constValue)
+	reply, err := Server(WithConstants(constMD))(hs)(ctx, "foo")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if reply.(string) != "foo" {
+		t.Fatalf("want foo got %v", reply)
+	}
+}
+
+func TestClient(t *testing.T) {
+	var (
+		globalKey   = "x-md-global-key"
+		globalValue = "global-value"
+		localKey    = "x-md-local-key"
+		localValue  = "local-value"
+		customKey   = "x-md-local-custom"
+		customValue = "custom-value"
+		constKey    = "x-md-local-const"
+		constValue  = "x-md-local-const"
+	)
+	hs := func(ctx context.Context, in interface{}) (interface{}, error) {
+		tr, ok := transport.FromClientContext(ctx)
+		if !ok {
+			return nil, errors.New("no md")
+		}
+		if tr.Header().Get(constKey) != constValue {
+			return nil, errors.New("const not equal")
+		}
+		if tr.Header().Get(customKey) != customValue {
+			return nil, errors.New("custom not equal")
+		}
+		if tr.Header().Get(globalKey) != globalValue {
+			return nil, errors.New("global not equal")
+		}
+		if tr.Header().Get(localKey) != "" {
+			return nil, errors.New("local must empty")
+		}
+		return in, nil
+	}
+	// server md
+	serverMD := metadata.New()
+	serverMD.Set(globalKey, globalValue)
+	serverMD.Set(localKey, localValue)
+	ctx := metadata.NewServerContext(context.Background(), serverMD)
+	// client md
+	clientMD := metadata.New()
+	clientMD.Set(customKey, customValue)
+	ctx = metadata.NewClientContext(ctx, clientMD)
+	// transport carrier
+	ctx = transport.NewClientContext(ctx, &testTransport{headerCarrier{}})
+	// const md
+	constMD := metadata.New()
+	constMD.Set(constKey, constValue)
+	reply, err := Client(WithConstants(constMD))(hs)(ctx, "bar")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if reply.(string) != "bar" {
+		t.Fatalf("want foo got %v", reply)
+	}
+}