368 lines
8.4 KiB
Go
368 lines
8.4 KiB
Go
package kong_test
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.company.lan/gopkg/kong"
|
|
"github.com/alecthomas/assert/v2"
|
|
)
|
|
|
|
type envMap map[string]string
|
|
|
|
func newEnvParser(t *testing.T, cli any, env envMap, options ...kong.Option) *kong.Kong {
|
|
t.Helper()
|
|
for name, value := range env {
|
|
t.Setenv(name, value)
|
|
}
|
|
parser := mustNew(t, cli, options...)
|
|
return parser
|
|
}
|
|
|
|
func TestEnvarsFlagBasic(t *testing.T) {
|
|
var cli struct {
|
|
String string `env:"KONG_STRING"`
|
|
Slice []int `env:"KONG_SLICE"`
|
|
Interp string `env:"${kongInterp}"`
|
|
}
|
|
kongInterpEnv := "KONG_INTERP"
|
|
parser := newEnvParser(t, &cli,
|
|
envMap{
|
|
"KONG_STRING": "bye",
|
|
"KONG_SLICE": "5,2,9",
|
|
"KONG_INTERP": "foo",
|
|
},
|
|
kong.Vars{
|
|
"kongInterp": kongInterpEnv,
|
|
},
|
|
)
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "bye", cli.String)
|
|
assert.Equal(t, []int{5, 2, 9}, cli.Slice)
|
|
assert.Equal(t, "foo", cli.Interp)
|
|
}
|
|
|
|
func TestEnvarsFlagMultiple(t *testing.T) {
|
|
var cli struct {
|
|
FirstENVPresent string `env:"KONG_TEST1_1,KONG_TEST1_2"`
|
|
SecondENVPresent string `env:"KONG_TEST2_1,KONG_TEST2_2"`
|
|
}
|
|
parser := newEnvParser(t, &cli,
|
|
envMap{
|
|
"KONG_TEST1_1": "value1.1",
|
|
"KONG_TEST1_2": "value1.2",
|
|
"KONG_TEST2_2": "value2.2",
|
|
},
|
|
)
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "value1.1", cli.FirstENVPresent)
|
|
assert.Equal(t, "value2.2", cli.SecondENVPresent)
|
|
}
|
|
|
|
func TestEnvarsFlagOverride(t *testing.T) {
|
|
var cli struct {
|
|
Flag string `env:"KONG_FLAG"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"})
|
|
|
|
_, err := parser.Parse([]string{"--flag=hello"})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "hello", cli.Flag)
|
|
}
|
|
|
|
func TestEnvarsTag(t *testing.T) {
|
|
var cli struct {
|
|
Slice []int `env:"KONG_NUMBERS"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"})
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []int{5, 2, 9}, cli.Slice)
|
|
}
|
|
|
|
func TestEnvarsEnvPrefix(t *testing.T) {
|
|
type Anonymous struct {
|
|
Slice []int `env:"NUMBERS"`
|
|
}
|
|
var cli struct {
|
|
Anonymous `envprefix:"KONG_"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "1,2,3"})
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []int{1, 2, 3}, cli.Slice)
|
|
}
|
|
|
|
func TestEnvarsEnvPrefixMultiple(t *testing.T) {
|
|
type Anonymous struct {
|
|
Slice1 []int `env:"NUMBERS1_1,NUMBERS1_2"`
|
|
Slice2 []int `env:"NUMBERS2_1,NUMBERS2_2"`
|
|
}
|
|
var cli struct {
|
|
Anonymous `envprefix:"KONG_"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{"KONG_NUMBERS1_1": "1,2,3", "KONG_NUMBERS2_2": "5,6,7"})
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []int{1, 2, 3}, cli.Slice1)
|
|
assert.Equal(t, []int{5, 6, 7}, cli.Slice2)
|
|
}
|
|
|
|
func TestEnvarsNestedEnvPrefix(t *testing.T) {
|
|
type NestedAnonymous struct {
|
|
String string `env:"STRING"`
|
|
}
|
|
type Anonymous struct {
|
|
NestedAnonymous `envprefix:"ANON_"`
|
|
}
|
|
var cli struct {
|
|
Anonymous `envprefix:"KONG_"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{"KONG_ANON_STRING": "abc"})
|
|
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "abc", cli.String)
|
|
}
|
|
|
|
func TestEnvarsWithDefault(t *testing.T) {
|
|
var cli struct {
|
|
Flag string `env:"KONG_FLAG" default:"default"`
|
|
}
|
|
parser := newEnvParser(t, &cli, envMap{})
|
|
|
|
_, err := parser.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "default", cli.Flag)
|
|
|
|
parser = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"})
|
|
_, err = parser.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "moo", cli.Flag)
|
|
}
|
|
|
|
func TestEnv(t *testing.T) {
|
|
type Embed struct {
|
|
Flag string
|
|
}
|
|
type Cli struct {
|
|
One Embed `prefix:"one-" embed:""`
|
|
Two Embed `prefix:"two." embed:""`
|
|
Three Embed `prefix:"three_" embed:""`
|
|
Four Embed `prefix:"four_" embed:""`
|
|
Five bool
|
|
Six bool `env:"-"`
|
|
}
|
|
|
|
var cli Cli
|
|
|
|
expected := Cli{
|
|
One: Embed{Flag: "one"},
|
|
Two: Embed{Flag: "two"},
|
|
Three: Embed{Flag: "three"},
|
|
Four: Embed{Flag: "four"},
|
|
Five: true,
|
|
}
|
|
|
|
// With the prefix
|
|
parser := newEnvParser(t, &cli, envMap{
|
|
"KONG_ONE_FLAG": "one",
|
|
"KONG_TWO_FLAG": "two",
|
|
"KONG_THREE_FLAG": "three",
|
|
"KONG_FOUR_FLAG": "four",
|
|
"KONG_FIVE": "true",
|
|
"KONG_SIX": "true",
|
|
}, kong.DefaultEnvars("KONG"))
|
|
|
|
_, err := parser.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, cli)
|
|
|
|
// Without the prefix
|
|
parser = newEnvParser(t, &cli, envMap{
|
|
"ONE_FLAG": "one",
|
|
"TWO_FLAG": "two",
|
|
"THREE_FLAG": "three",
|
|
"FOUR_FLAG": "four",
|
|
"FIVE": "true",
|
|
"SIX": "true",
|
|
}, kong.DefaultEnvars(""))
|
|
|
|
_, err = parser.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expected, cli)
|
|
}
|
|
|
|
func TestJSONBasic(t *testing.T) {
|
|
type Embed struct {
|
|
String string
|
|
}
|
|
|
|
var cli struct {
|
|
String string
|
|
Slice []int
|
|
SliceWithCommas []string
|
|
Bool bool
|
|
|
|
One Embed `prefix:"one." embed:""`
|
|
Two Embed `prefix:"two." embed:""`
|
|
}
|
|
|
|
json := `{
|
|
"string": "🍕",
|
|
"slice": [5, 8],
|
|
"bool": true,
|
|
"sliceWithCommas": ["a,b", "c"],
|
|
"one":{
|
|
"string": "one value"
|
|
},
|
|
"two.string": "two value"
|
|
}`
|
|
|
|
r, err := kong.JSON(strings.NewReader(json))
|
|
assert.NoError(t, err)
|
|
|
|
parser := mustNew(t, &cli, kong.Resolvers(r))
|
|
_, err = parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "🍕", cli.String)
|
|
assert.Equal(t, []int{5, 8}, cli.Slice)
|
|
assert.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas)
|
|
assert.Equal(t, "one value", cli.One.String)
|
|
assert.Equal(t, "two value", cli.Two.String)
|
|
assert.True(t, cli.Bool)
|
|
}
|
|
|
|
type testUppercaseMapper struct{}
|
|
|
|
func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
|
|
var value string
|
|
err := ctx.Scan.PopValueInto("lowercase", &value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
target.SetString(strings.ToUpper(value))
|
|
return nil
|
|
}
|
|
|
|
func TestResolversWithMappers(t *testing.T) {
|
|
var cli struct {
|
|
Flag string `env:"KONG_MOO" type:"upper"`
|
|
}
|
|
|
|
t.Setenv("KONG_MOO", "meow")
|
|
|
|
parser := newEnvParser(t, &cli,
|
|
envMap{"KONG_MOO": "meow"},
|
|
kong.NamedMapper("upper", testUppercaseMapper{}),
|
|
)
|
|
_, err := parser.Parse([]string{})
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "MEOW", cli.Flag)
|
|
}
|
|
|
|
func TestResolverWithBool(t *testing.T) {
|
|
var cli struct {
|
|
Bool bool
|
|
}
|
|
|
|
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
if flag.Name == "bool" {
|
|
return true, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
p := mustNew(t, &cli, kong.Resolvers(resolver))
|
|
|
|
_, err := p.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.True(t, cli.Bool)
|
|
}
|
|
|
|
func TestLastResolverWins(t *testing.T) {
|
|
var cli struct {
|
|
Int []int
|
|
}
|
|
|
|
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
if flag.Name == "int" {
|
|
return 1, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
if flag.Name == "int" {
|
|
return 2, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
p := mustNew(t, &cli, kong.Resolvers(first, second))
|
|
_, err := p.Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, []int{2}, cli.Int)
|
|
}
|
|
|
|
func TestResolverSatisfiesRequired(t *testing.T) {
|
|
var cli struct {
|
|
Int int `required`
|
|
}
|
|
var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
if flag.Name == "int" {
|
|
return 1, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, 1, cli.Int)
|
|
}
|
|
|
|
func TestResolverTriggersHooks(t *testing.T) {
|
|
ctx := &hookContext{}
|
|
|
|
var cli struct {
|
|
Flag hookValue
|
|
}
|
|
|
|
var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
if flag.Name == "flag" {
|
|
return "one", nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
_, err := mustNew(t, &cli, kong.Bind(ctx), kong.Resolvers(first)).Parse(nil)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "one", string(cli.Flag))
|
|
assert.Equal(t, []string{"before:", "after:one"}, ctx.values)
|
|
}
|
|
|
|
type validatingResolver struct {
|
|
err error
|
|
}
|
|
|
|
func (v *validatingResolver) Validate(app *kong.Application) error { return v.err }
|
|
func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (any, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestValidatingResolverErrors(t *testing.T) {
|
|
resolver := &validatingResolver{err: errors.New("invalid")}
|
|
var cli struct{}
|
|
_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
|
|
assert.EqualError(t, err, "invalid")
|
|
}
|