package env import ( "os" "path/filepath" "reflect" "testing" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config/file" ) const _testJSON = ` { "test":{ "server":{ "name":"${SERVICE_NAME}", "addr":"${ADDR:127.0.0.1}", "port":"${PORT:8080}" } }, "foo":[ { "name":"Tom", "age":"${AGE}" } ] }` func TestEnvWithPrefix(t *testing.T) { var ( path = filepath.Join(t.TempDir(), "test_config") filename = filepath.Join(path, "test.json") data = []byte(_testJSON) ) defer os.Remove(path) if err := os.MkdirAll(path, 0o700); err != nil { t.Error(err) } if err := os.WriteFile(filename, data, 0o666); err != nil { t.Error(err) } // set env prefix1, prefix2 := "KRATOS_", "FOO" envs := map[string]string{ prefix1 + "SERVICE_NAME": "kratos_app", prefix2 + "ADDR": "192.168.0.1", prefix1 + "AGE": "20", // only prefix prefix2: "foo", prefix2 + "_": "foo_", } for k, v := range envs { os.Setenv(k, v) } c := config.New(config.WithSource( file.NewSource(path), NewSource(prefix1, prefix2), )) if err := c.Load(); err != nil { t.Fatal(err) } tests := []struct { name string path string expect interface{} }{ { name: "test $KEY", path: "test.server.name", expect: "kratos_app", }, { name: "test ${KEY:DEFAULT} without default", path: "test.server.addr", expect: "192.168.0.1", }, { name: "test ${KEY:DEFAULT} with default", path: "test.server.port", expect: "8080", }, { name: "test ${KEY} in array", path: "foo", expect: []interface{}{ map[string]interface{}{ "name": "Tom", "age": "20", }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var err error v := c.Value(test.path) if v.Load() != nil { var actual interface{} switch test.expect.(type) { case int: if actual, err = v.Int(); err == nil { if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { t.Errorf("expect %v, actual %v", test.expect, actual) } } case string: if actual, err = v.String(); err == nil { if !reflect.DeepEqual(test.expect.(string), actual.(string)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } case bool: if actual, err = v.Bool(); err == nil { if !reflect.DeepEqual(test.expect.(bool), actual.(bool)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } case float64: if actual, err = v.Float(); err == nil { if !reflect.DeepEqual(test.expect.(float64), actual.(float64)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } default: actual = v.Load() if !reflect.DeepEqual(test.expect, actual) { t.Logf("\nexpect: %#v\nactural: %#v", test.expect, actual) t.Fail() } } if err != nil { t.Error(err) } } else { t.Error("value path not found") } }) } } func TestEnvWithoutPrefix(t *testing.T) { var ( path = filepath.Join(t.TempDir(), "test_config") filename = filepath.Join(path, "test.json") data = []byte(_testJSON) ) defer os.Remove(path) if err := os.MkdirAll(path, 0o700); err != nil { t.Error(err) } if err := os.WriteFile(filename, data, 0o666); err != nil { t.Error(err) } // set env envs := map[string]string{ "SERVICE_NAME": "kratos_app", "ADDR": "192.168.0.1", "AGE": "20", } for k, v := range envs { os.Setenv(k, v) } c := config.New(config.WithSource( NewSource(), file.NewSource(path), )) if err := c.Load(); err != nil { t.Fatal(err) } tests := []struct { name string path string expect interface{} }{ { name: "test $KEY", path: "test.server.name", expect: "kratos_app", }, { name: "test ${KEY:DEFAULT} without default", path: "test.server.addr", expect: "192.168.0.1", }, { name: "test ${KEY:DEFAULT} with default", path: "test.server.port", expect: "8080", }, { name: "test ${KEY} in array", path: "foo", expect: []interface{}{ map[string]interface{}{ "name": "Tom", "age": "20", }, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var err error v := c.Value(test.path) if v.Load() != nil { var actual interface{} switch test.expect.(type) { case int: if actual, err = v.Int(); err == nil { if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { t.Errorf("expect %v, actual %v", test.expect, actual) } } case string: if actual, err = v.String(); err == nil { if !reflect.DeepEqual(test.expect.(string), actual.(string)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } case bool: if actual, err = v.Bool(); err == nil { if !reflect.DeepEqual(test.expect.(bool), actual.(bool)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } case float64: if actual, err = v.Float(); err == nil { if !reflect.DeepEqual(test.expect.(float64), actual.(float64)) { t.Errorf(`expect %v, actual %v`, test.expect, actual) } } default: actual = v.Load() if !reflect.DeepEqual(test.expect, actual) { t.Logf("\nexpect: %#v\nactural: %#v", test.expect, actual) t.Fail() } } if err != nil { t.Error(err) } } else { t.Error("value path not found") } }) } } func Test_env_load(t *testing.T) { type fields struct { prefixs []string } type args struct { envStrings []string } tests := []struct { name string fields fields args args want []*config.KeyValue }{ { name: "without prefixes", fields: fields{ prefixs: nil, }, args: args{ envStrings: []string{ "SERVICE_NAME=kratos_app", "ADDR=192.168.0.1", "AGE=20", }, }, want: []*config.KeyValue{ {Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, {Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, {Key: "AGE", Value: []byte("20"), Format: ""}, }, }, { name: "empty prefix", fields: fields{ prefixs: []string{""}, }, args: args{ envStrings: []string{ "__SERVICE_NAME=kratos_app", "__ADDR=192.168.0.1", "__AGE=20", }, }, want: []*config.KeyValue{ {Key: "_SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, {Key: "_ADDR", Value: []byte("192.168.0.1"), Format: ""}, {Key: "_AGE", Value: []byte("20"), Format: ""}, }, }, { name: "underscore prefix", fields: fields{ prefixs: []string{"_"}, }, args: args{ envStrings: []string{ "__SERVICE_NAME=kratos_app", "__ADDR=192.168.0.1", "__AGE=20", }, }, want: []*config.KeyValue{ {Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, {Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, {Key: "AGE", Value: []byte("20"), Format: ""}, }, }, { name: "with prefixes", fields: fields{ prefixs: []string{"KRATOS_", "FOO"}, }, args: args{ envStrings: []string{ "KRATOS_SERVICE_NAME=kratos_app", "KRATOS_ADDR=192.168.0.1", "FOO_AGE=20", }, }, want: []*config.KeyValue{ {Key: "SERVICE_NAME", Value: []byte("kratos_app"), Format: ""}, {Key: "ADDR", Value: []byte("192.168.0.1"), Format: ""}, {Key: "AGE", Value: []byte("20"), Format: ""}, }, }, { name: "should not panic #1", fields: fields{ prefixs: []string{"FOO"}, }, args: args{ envStrings: []string{ "FOO=123", }, }, want: nil, }, { name: "should not panic #2", fields: fields{ prefixs: []string{"FOO=1"}, }, args: args{ envStrings: []string{ "FOO=123", }, }, want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &env{ prefixs: tt.fields.prefixs, } got := e.load(tt.args.envStrings) if !reflect.DeepEqual(tt.want, got) { t.Errorf("env.load() = %v, want %v", got, tt.want) } }) } } func Test_matchPrefix(t *testing.T) { type args struct { prefixes []string s string } tests := []struct { name string args args want string wantOk bool }{ {args: args{prefixes: nil, s: "foo=123"}, want: "", wantOk: false}, {args: args{prefixes: []string{""}, s: "foo=123"}, want: "", wantOk: true}, {args: args{prefixes: []string{"foo"}, s: "foo=123"}, want: "foo", wantOk: true}, {args: args{prefixes: []string{"foo=1"}, s: "foo=123"}, want: "foo=1", wantOk: true}, {args: args{prefixes: []string{"foo=1234"}, s: "foo=123"}, want: "", wantOk: false}, {args: args{prefixes: []string{"bar"}, s: "foo=123"}, want: "", wantOk: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, gotOk := matchPrefix(tt.args.prefixes, tt.args.s) if got != tt.want { t.Errorf("matchPrefix() got = %v, want %v", got, tt.want) } if gotOk != tt.wantOk { t.Errorf("matchPrefix() gotOk = %v, wantOk %v", gotOk, tt.wantOk) } }) } } func Test_env_watch(t *testing.T) { prefixs := []string{"BAR", "FOO"} source := NewSource(prefixs...) w, err := source.Watch() if err != nil { t.Errorf("expect no err, got %v", err) } _ = w.Stop() }