@@ -3,123 +3,202 @@ package config_test
33import (
44 "errors"
55 "fmt"
6- "reflect"
7- "strconv"
86 "testing"
97
10- "github.com/go-viper/mapstructure/v2"
118 "github.com/ory/viper"
129 "github.com/stretchr/testify/assert"
10+ "github.com/stretchr/testify/require"
1311
1412 "github.com/tkrop/go-config/config"
13+ "github.com/tkrop/go-config/info"
1514 "github.com/tkrop/go-config/internal/filepath"
15+ ireflect "github.com/tkrop/go-config/internal/reflect"
1616 "github.com/tkrop/go-testing/mock"
1717 ref "github.com/tkrop/go-testing/reflect"
1818 "github.com/tkrop/go-testing/test"
1919)
2020
2121var configPaths = []string {filepath .Normalize ("." )}
2222
23+ func newConfig (
24+ env , level string , setup func (info * info.Info ),
25+ ) * config.Config {
26+ reader := config .NewReader [config.Config ]("TC" , "test" )
27+ config := reader .GetConfig ("test-helper" )
28+
29+ if setup != nil {
30+ setup (config .Info )
31+ }
32+
33+ config .Env = env
34+ config .Log .Level = level
35+
36+ return config
37+ }
38+
2339type ConfigParams struct {
24- setenv func (test.Test )
25- setup func (* config.Reader [config.Config ])
26- expect mock.SetupFunc
27- expectEnv string
28- expectLogLevel string
40+ setup mock.SetupFunc
41+ reader func (test.Test ) * config.Reader [config.Config ]
42+ expect * config.Config
2943}
3044
3145var configTestCases = map [string ]ConfigParams {
3246 "default config without file" : {
33- expectEnv : "prod" ,
34- expectLogLevel : "info" ,
47+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
48+ return config .NewReader [config.Config ]("TC" , "test" )
49+ },
50+ expect : newConfig ("prod" , "info" , nil ),
3551 },
3652
3753 "default config with file" : {
38- setup : func (r * config.Reader [config.Config ]) {
39- r .AddConfigPath ("fixtures" )
54+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
55+ return config .NewReader [config.Config ]("TC" , "test" ).
56+ SetDefaults (func (r * config.Reader [config.Config ]) {
57+ r .AddConfigPath ("fixtures" )
58+ })
4059 },
41- expectEnv : "prod" ,
42- expectLogLevel : "debug" ,
60+ expect : newConfig ("prod" , "debug" , func (info * info.Info ) {
61+ info .Path = "github.com/tkrop/go-config"
62+ }),
4363 },
4464
4565 "read config with overriding env" : {
46- setenv : func (t test.Test ) {
66+ reader : func (t test.Test ) * config. Reader [config. Config ] {
4767 t .Setenv ("TC_ENV" , "test" )
4868 t .Setenv ("TC_LOG_LEVEL" , "trace" )
69+ return config .NewReader [config.Config ]("TC" , "test" ).
70+ SetDefaults (func (r * config.Reader [config.Config ]) {
71+ r .AddConfigPath ("fixtures" )
72+ })
4973 },
50- setup : func (r * config.Reader [config.Config ]) {
51- r .AddConfigPath ("fixtures" )
52- },
53- expectEnv : "test" ,
54- expectLogLevel : "trace" ,
74+ expect : newConfig ("test" , "trace" , nil ),
5575 },
5676
5777 "read config with overriding func" : {
58- setup : func (r * config.Reader [config.Config ]) {
59- r .SetDefault ("log.level" , "trace" )
78+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
79+ return config .NewReader [config.Config ]("TC" , "test" ).
80+ SetDefaults (func (r * config.Reader [config.Config ]) {
81+ r .SetDefault ("log.level" , "trace" )
82+ })
6083 },
61- expectEnv : "prod" ,
62- expectLogLevel : "trace" ,
84+ expect : newConfig ("prod" , "trace" , nil ),
6385 },
6486
6587 "panic after file not found" : {
66- setup : func (r * config.Reader [config.Config ]) {
67- r .SetDefault ("viper.panic.load" , true )
68- },
69- expect : test .Panic (config .NewErrConfig ("loading file" , "test" ,
88+ setup : test .Panic (config .NewErrConfig ("loading file" , "test" ,
7089 ref .NewBuilder [viper.ConfigFileNotFoundError ]().
7190 Set ("locations" , fmt .Sprintf ("%s" , configPaths )).
72- Set ("name" , "test" ).Build ())),
91+ Set ("name" , "test" ).Build ()).Error ()),
92+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
93+ return config .NewReader [config.Config ]("TC" , "test" ).
94+ SetDefaults (func (r * config.Reader [config.Config ]) {
95+ r .SetDefault ("viper.panic.load" , true )
96+ })
97+ },
7398 },
7499
75- "panic after unmarshal failure" : {
76- setup : func (r * config.Reader [config.Config ]) {
77- r .AddConfigPath ("fixtures" )
78- r .SetDefault ("viper.panic.unmarshal" , true )
79- r .SetDefault ("info.dirty" , "5s" )
80- },
81- expect : test .Panic (config .NewErrConfig ("unmarshal config" ,
82- "test" , fmt .Errorf ("decoding failed due to the following error(s):\n \n %v" ,
100+ "panic after unmarshal failure next" : {
101+ setup : test .Panic (config .NewErrConfig ("unmarshal config" ,
102+ "test" , fmt .Errorf (
103+ "decoding failed due to the following error(s):\n \n %v" ,
83104 "'Info.Dirty' cannot parse value as 'bool': " +
84- "strconv.ParseBool: invalid syntax" ,
85- ))),
105+ "strconv.ParseBool: invalid syntax" )).Error ()),
106+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
107+ return config .NewReader [config.Config ]("TC" , "test" ).
108+ SetDefaults (func (r * config.Reader [config.Config ]) {
109+ r .AddConfigPath ("fixtures" )
110+ r .SetDefault ("viper.panic.unmarshal" , true )
111+ r .SetDefault ("info.dirty" , "5s" )
112+ })
113+ },
86114 },
87- // TODO: Improve error wrapping test for unmarshal failure.
88- "panic after unmarshal failure next" : {
89- setup : func (r * config.Reader [config.Config ]) {
90- r .AddConfigPath ("fixtures" )
91- r .SetDefault ("viper.panic.unmarshal" , true )
92- r .SetDefault ("info.dirty" , "5s" )
115+
116+ "error on default config with invalid tag" : {
117+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
118+ return config .NewReader [config.Config ]("TC" , "test" ).
119+ SetDefaults (func (r * config.Reader [config.Config ]) {
120+ r .SetDefaultConfig ("" , & struct {
121+ Field []string `default:"invalid"`
122+ }{}, false )
123+ })
124+ },
125+ expect : newConfig ("prod" , "info" , nil ),
126+ },
127+
128+ "panic on default config with invalid tag" : {
129+ setup : test .Panic (config .NewErrConfig ("creating defaults" , "" ,
130+ ireflect .NewErrTagWalker ("yaml parsing" , "field" , "invalid" ,
131+ errors .New ("yaml: unmarshal errors:\n line 1: " +
132+ "cannot unmarshal !!str `invalid` into []string" ))).
133+ Error ()),
134+ reader : func (_ test.Test ) * config.Reader [config.Config ] {
135+ return config .NewReader [config.Config ]("TC" , "test" ).
136+ SetDefaults (func (r * config.Reader [config.Config ]) {
137+ r .SetDefault ("viper.panic.defaults" , true )
138+ r .SetDefaultConfig ("" , & struct {
139+ Field []string `default:"invalid"`
140+ }{}, false )
141+ })
93142 },
94- expect : test .Panic (config .NewErrConfig ("unmarshal config" ,
95- "test" , fmt .Errorf ("decoding failed due to the following error(s):\n \n %w" ,
96- errors .Join (ref .NewBuilder [* mapstructure.DecodeError ]().
97- Set ("name" , "Info.Dirty" ).
98- Set ("err" , & mapstructure.ParseError {
99- Expected : reflect .ValueOf (true ), Value : "5s" ,
100- Err : & strconv.NumError {Func : "ParseBool" , Num : "5s" },
101- }).Build ())))),
102143 },
103144}
104145
105146func TestConfig (t * testing.T ) {
106147 test .Map (t , configTestCases ).
107- Filter (test .Not (test.Pattern [ConfigParams ](
108- "panic-after-unmarshal-failure-next" ))).
109148 RunSeq (func (t test.Test , param ConfigParams ) {
110149 // Given
111- mock .NewMocks (t ).Expect (param .expect )
112- if param .setenv != nil {
113- param .setenv (t )
114- }
115- reader := config .NewReader [config.Config ]("TC" , "test" ).
116- SetDefaults (param .setup )
150+ mock .NewMocks (t ).Expect (param .setup )
151+
152+ // When
153+ reader := param .reader (t )
154+ config := reader .LoadConfig ("test" )
155+
156+ // Then
157+ assert .Equal (t , param .expect , config )
158+ })
159+ }
160+
161+ // TODO: improve test to provide meaningful insights about defaults.
162+ type AnyConfigParams struct {
163+ // setup mock.SetupFunc
164+ config any
165+ expect any
166+ }
167+
168+ type object struct {
169+ A any
170+ B any
171+ C any
172+ D []int
173+ }
174+
175+ var anyConfigParams = map [string ]AnyConfigParams {
176+ "read struct tag" : {
177+ config : & struct {
178+ S * object `default:"{a: <default>, d: [1,2,3]}"`
179+ }{S : & object {}},
180+ expect : & struct {
181+ S * object `default:"{a: <default>, d: [1,2,3]}"`
182+ }{S : & object {
183+ A : "<default>" ,
184+ D : []int {1 , 2 , 3 },
185+ }},
186+ },
187+ }
188+
189+ func TestAnyConfig (t * testing.T ) {
190+ test .Map (t , anyConfigParams ).
191+ RunSeq (func (t test.Test , param AnyConfigParams ) {
192+ // Given
193+ // mock.NewMocks(t).Expect(param.setup)
194+ reader := config .NewReader [any ]("TC" , "test" )
195+ reader .SetDefaultConfig ("" , param .config , true )
117196
118197 // When
119- reader .LoadConfig ( "test" )
198+ err := reader .Unmarshal ( param . config )
120199
121200 // Then
122- assert . Equal (t , param . expectEnv , reader . GetString ( "env" ) )
123- assert .Equal (t , param .expectLogLevel , reader . GetString ( "log.level" ) )
201+ require . NoError (t , err )
202+ assert .Equal (t , param .expect , param . config )
124203 })
125204}
0 commit comments