github.com/maresnic/mr-kong@v1.0.0/resolver_test.go (about)

     1  package kong_test
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/alecthomas/assert/v2"
    11  	"github.com/maresnic/mr-kong"
    12  )
    13  
    14  type envMap map[string]string
    15  
    16  func tempEnv(env envMap) func() {
    17  	for k, v := range env {
    18  		os.Setenv(k, v)
    19  	}
    20  
    21  	return func() {
    22  		for k := range env {
    23  			os.Unsetenv(k)
    24  		}
    25  	}
    26  }
    27  
    28  func newEnvParser(t *testing.T, cli interface{}, env envMap, options ...kong.Option) (*kong.Kong, func()) {
    29  	t.Helper()
    30  	restoreEnv := tempEnv(env)
    31  	parser := mustNew(t, cli, options...)
    32  	return parser, restoreEnv
    33  }
    34  
    35  func TestEnvarsFlagBasic(t *testing.T) {
    36  	var cli struct {
    37  		String string `env:"KONG_STRING"`
    38  		Slice  []int  `env:"KONG_SLICE"`
    39  		Interp string `env:"${kongInterp}"`
    40  	}
    41  	kongInterpEnv := "KONG_INTERP"
    42  	parser, unsetEnvs := newEnvParser(t, &cli,
    43  		envMap{
    44  			"KONG_STRING": "bye",
    45  			"KONG_SLICE":  "5,2,9",
    46  			"KONG_INTERP": "foo",
    47  		},
    48  		kong.Vars{
    49  			"kongInterp": kongInterpEnv,
    50  		},
    51  	)
    52  	defer unsetEnvs()
    53  
    54  	_, err := parser.Parse([]string{})
    55  	assert.NoError(t, err)
    56  	assert.Equal(t, "bye", cli.String)
    57  	assert.Equal(t, []int{5, 2, 9}, cli.Slice)
    58  	assert.Equal(t, "foo", cli.Interp)
    59  }
    60  
    61  func TestEnvarsFlagMultiple(t *testing.T) {
    62  	var cli struct {
    63  		FirstENVPresent  string `env:"KONG_TEST1_1,KONG_TEST1_2"`
    64  		SecondENVPresent string `env:"KONG_TEST2_1,KONG_TEST2_2"`
    65  	}
    66  	parser, unsetEnvs := newEnvParser(t, &cli,
    67  		envMap{
    68  			"KONG_TEST1_1": "value1.1",
    69  			"KONG_TEST1_2": "value1.2",
    70  			"KONG_TEST2_2": "value2.2",
    71  		},
    72  	)
    73  	defer unsetEnvs()
    74  
    75  	_, err := parser.Parse([]string{})
    76  	assert.NoError(t, err)
    77  	assert.Equal(t, "value1.1", cli.FirstENVPresent)
    78  	assert.Equal(t, "value2.2", cli.SecondENVPresent)
    79  }
    80  
    81  func TestEnvarsFlagOverride(t *testing.T) {
    82  	var cli struct {
    83  		Flag string `env:"KONG_FLAG"`
    84  	}
    85  	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"})
    86  	defer restoreEnv()
    87  
    88  	_, err := parser.Parse([]string{"--flag=hello"})
    89  	assert.NoError(t, err)
    90  	assert.Equal(t, "hello", cli.Flag)
    91  }
    92  
    93  func TestEnvarsTag(t *testing.T) {
    94  	var cli struct {
    95  		Slice []int `env:"KONG_NUMBERS"`
    96  	}
    97  	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"})
    98  	defer restoreEnv()
    99  
   100  	_, err := parser.Parse([]string{})
   101  	assert.NoError(t, err)
   102  	assert.Equal(t, []int{5, 2, 9}, cli.Slice)
   103  }
   104  
   105  func TestEnvarsEnvPrefix(t *testing.T) {
   106  	type Anonymous struct {
   107  		Slice []int `env:"NUMBERS"`
   108  	}
   109  	var cli struct {
   110  		Anonymous `envprefix:"KONG_"`
   111  	}
   112  	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "1,2,3"})
   113  	defer restoreEnv()
   114  
   115  	_, err := parser.Parse([]string{})
   116  	assert.NoError(t, err)
   117  	assert.Equal(t, []int{1, 2, 3}, cli.Slice)
   118  }
   119  
   120  func TestEnvarsEnvPrefixMultiple(t *testing.T) {
   121  	type Anonymous struct {
   122  		Slice1 []int `env:"NUMBERS1_1,NUMBERS1_2"`
   123  		Slice2 []int `env:"NUMBERS2_1,NUMBERS2_2"`
   124  	}
   125  	var cli struct {
   126  		Anonymous `envprefix:"KONG_"`
   127  	}
   128  	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS1_1": "1,2,3", "KONG_NUMBERS2_2": "5,6,7"})
   129  	defer restoreEnv()
   130  
   131  	_, err := parser.Parse([]string{})
   132  	assert.NoError(t, err)
   133  	assert.Equal(t, []int{1, 2, 3}, cli.Slice1)
   134  	assert.Equal(t, []int{5, 6, 7}, cli.Slice2)
   135  }
   136  
   137  func TestEnvarsNestedEnvPrefix(t *testing.T) {
   138  	type NestedAnonymous struct {
   139  		String string `env:"STRING"`
   140  	}
   141  	type Anonymous struct {
   142  		NestedAnonymous `envprefix:"ANON_"`
   143  	}
   144  	var cli struct {
   145  		Anonymous `envprefix:"KONG_"`
   146  	}
   147  	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_ANON_STRING": "abc"})
   148  	defer restoreEnv()
   149  
   150  	_, err := parser.Parse([]string{})
   151  	assert.NoError(t, err)
   152  	assert.Equal(t, "abc", cli.String)
   153  }
   154  
   155  func TestEnvarsWithDefault(t *testing.T) {
   156  	var cli struct {
   157  		Flag string `env:"KONG_FLAG" default:"default"`
   158  	}
   159  	parser, restoreEnv := newEnvParser(t, &cli, envMap{})
   160  	defer restoreEnv()
   161  
   162  	_, err := parser.Parse(nil)
   163  	assert.NoError(t, err)
   164  	assert.Equal(t, "default", cli.Flag)
   165  
   166  	parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"})
   167  	defer restoreEnv()
   168  	_, err = parser.Parse(nil)
   169  	assert.NoError(t, err)
   170  	assert.Equal(t, "moo", cli.Flag)
   171  }
   172  
   173  func TestEnv(t *testing.T) {
   174  	type Embed struct {
   175  		Flag string
   176  	}
   177  	type Cli struct {
   178  		One   Embed `prefix:"one-" embed:""`
   179  		Two   Embed `prefix:"two." embed:""`
   180  		Three Embed `prefix:"three_" embed:""`
   181  		Four  Embed `prefix:"four_" embed:""`
   182  		Five  bool
   183  		Six   bool `env:"-"`
   184  	}
   185  
   186  	var cli Cli
   187  
   188  	expected := Cli{
   189  		One:   Embed{Flag: "one"},
   190  		Two:   Embed{Flag: "two"},
   191  		Three: Embed{Flag: "three"},
   192  		Four:  Embed{Flag: "four"},
   193  		Five:  true,
   194  	}
   195  
   196  	// With the prefix
   197  	parser, unsetEnvs := newEnvParser(t, &cli, envMap{
   198  		"KONG_ONE_FLAG":   "one",
   199  		"KONG_TWO_FLAG":   "two",
   200  		"KONG_THREE_FLAG": "three",
   201  		"KONG_FOUR_FLAG":  "four",
   202  		"KONG_FIVE":       "true",
   203  		"KONG_SIX":        "true",
   204  	}, kong.DefaultEnvars("KONG"))
   205  	defer unsetEnvs()
   206  
   207  	_, err := parser.Parse(nil)
   208  	assert.NoError(t, err)
   209  	assert.Equal(t, expected, cli)
   210  
   211  	// Without the prefix
   212  	parser, unsetEnvs = newEnvParser(t, &cli, envMap{
   213  		"ONE_FLAG":   "one",
   214  		"TWO_FLAG":   "two",
   215  		"THREE_FLAG": "three",
   216  		"FOUR_FLAG":  "four",
   217  		"FIVE":       "true",
   218  		"SIX":        "true",
   219  	}, kong.DefaultEnvars(""))
   220  	defer unsetEnvs()
   221  
   222  	_, err = parser.Parse(nil)
   223  	assert.NoError(t, err)
   224  	assert.Equal(t, expected, cli)
   225  }
   226  
   227  func TestJSONBasic(t *testing.T) {
   228  	type Embed struct {
   229  		String string
   230  	}
   231  
   232  	var cli struct {
   233  		String          string
   234  		Slice           []int
   235  		SliceWithCommas []string
   236  		Bool            bool
   237  
   238  		One Embed `prefix:"one." embed:""`
   239  		Two Embed `prefix:"two." embed:""`
   240  	}
   241  
   242  	json := `{
   243  		"string": "🍕",
   244  		"slice": [5, 8],
   245  		"bool": true,
   246  		"sliceWithCommas": ["a,b", "c"],
   247  		"one":{
   248  			"string": "one value"
   249  		},
   250  		"two.string": "two value"
   251  	}`
   252  
   253  	r, err := kong.JSON(strings.NewReader(json))
   254  	assert.NoError(t, err)
   255  
   256  	parser := mustNew(t, &cli, kong.Resolvers(r))
   257  	_, err = parser.Parse([]string{})
   258  	assert.NoError(t, err)
   259  	assert.Equal(t, "🍕", cli.String)
   260  	assert.Equal(t, []int{5, 8}, cli.Slice)
   261  	assert.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas)
   262  	assert.Equal(t, "one value", cli.One.String)
   263  	assert.Equal(t, "two value", cli.Two.String)
   264  	assert.True(t, cli.Bool)
   265  }
   266  
   267  type testUppercaseMapper struct{}
   268  
   269  func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
   270  	var value string
   271  	err := ctx.Scan.PopValueInto("lowercase", &value)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	target.SetString(strings.ToUpper(value))
   276  	return nil
   277  }
   278  
   279  func TestResolversWithMappers(t *testing.T) {
   280  	var cli struct {
   281  		Flag string `env:"KONG_MOO" type:"upper"`
   282  	}
   283  
   284  	restoreEnv := tempEnv(envMap{"KONG_MOO": "meow"})
   285  	defer restoreEnv()
   286  
   287  	parser := mustNew(t, &cli,
   288  		kong.NamedMapper("upper", testUppercaseMapper{}),
   289  	)
   290  	_, err := parser.Parse([]string{})
   291  	assert.NoError(t, err)
   292  	assert.Equal(t, "MEOW", cli.Flag)
   293  }
   294  
   295  func TestResolverWithBool(t *testing.T) {
   296  	var cli struct {
   297  		Bool bool
   298  	}
   299  
   300  	var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   301  		if flag.Name == "bool" {
   302  			return true, nil
   303  		}
   304  		return nil, nil
   305  	}
   306  
   307  	p := mustNew(t, &cli, kong.Resolvers(resolver))
   308  
   309  	_, err := p.Parse(nil)
   310  	assert.NoError(t, err)
   311  	assert.True(t, cli.Bool)
   312  }
   313  
   314  func TestLastResolverWins(t *testing.T) {
   315  	var cli struct {
   316  		Int []int
   317  	}
   318  
   319  	var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   320  		if flag.Name == "int" {
   321  			return 1, nil
   322  		}
   323  		return nil, nil
   324  	}
   325  
   326  	var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   327  		if flag.Name == "int" {
   328  			return 2, nil
   329  		}
   330  		return nil, nil
   331  	}
   332  
   333  	p := mustNew(t, &cli, kong.Resolvers(first, second))
   334  	_, err := p.Parse(nil)
   335  	assert.NoError(t, err)
   336  	assert.Equal(t, []int{2}, cli.Int)
   337  }
   338  
   339  func TestResolverSatisfiesRequired(t *testing.T) {
   340  	var cli struct {
   341  		Int int `required`
   342  	}
   343  	var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   344  		if flag.Name == "int" {
   345  			return 1, nil
   346  		}
   347  		return nil, nil
   348  	}
   349  	_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
   350  	assert.NoError(t, err)
   351  	assert.Equal(t, 1, cli.Int)
   352  }
   353  
   354  func TestResolverTriggersHooks(t *testing.T) {
   355  	ctx := &hookContext{}
   356  
   357  	var cli struct {
   358  		Flag hookValue
   359  	}
   360  
   361  	var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   362  		if flag.Name == "flag" {
   363  			return "one", nil
   364  		}
   365  		return nil, nil
   366  	}
   367  
   368  	_, err := mustNew(t, &cli, kong.Bind(ctx), kong.Resolvers(first)).Parse(nil)
   369  	assert.NoError(t, err)
   370  
   371  	assert.Equal(t, "one", string(cli.Flag))
   372  	assert.Equal(t, []string{"before:", "after:one"}, ctx.values)
   373  }
   374  
   375  type validatingResolver struct {
   376  	err error
   377  }
   378  
   379  func (v *validatingResolver) Validate(app *kong.Application) error { return v.err }
   380  func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
   381  	return nil, nil
   382  }
   383  
   384  func TestValidatingResolverErrors(t *testing.T) {
   385  	resolver := &validatingResolver{err: errors.New("invalid")}
   386  	var cli struct{}
   387  	_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
   388  	assert.EqualError(t, err, "invalid")
   389  }