1package kong_test
2
3import (
4	"errors"
5	"os"
6	"reflect"
7	"strings"
8	"testing"
9
10	"github.com/stretchr/testify/require"
11
12	"github.com/alecthomas/kong"
13)
14
15type envMap map[string]string
16
17func tempEnv(env envMap) func() {
18	for k, v := range env {
19		os.Setenv(k, v)
20	}
21
22	return func() {
23		for k := range env {
24			os.Unsetenv(k)
25		}
26	}
27}
28
29func newEnvParser(t *testing.T, cli interface{}, env envMap) (*kong.Kong, func()) {
30	t.Helper()
31	restoreEnv := tempEnv(env)
32	parser := mustNew(t, cli)
33	return parser, restoreEnv
34}
35
36func TestEnvarsFlagBasic(t *testing.T) {
37	var cli struct {
38		String string `env:"KONG_STRING"`
39		Slice  []int  `env:"KONG_SLICE"`
40	}
41	parser, unsetEnvs := newEnvParser(t, &cli, envMap{
42		"KONG_STRING": "bye",
43		"KONG_SLICE":  "5,2,9",
44	})
45	defer unsetEnvs()
46
47	_, err := parser.Parse([]string{})
48	require.NoError(t, err)
49	require.Equal(t, "bye", cli.String)
50	require.Equal(t, []int{5, 2, 9}, cli.Slice)
51}
52
53func TestEnvarsFlagOverride(t *testing.T) {
54	var cli struct {
55		Flag string `env:"KONG_FLAG"`
56	}
57	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_FLAG": "bye"})
58	defer restoreEnv()
59
60	_, err := parser.Parse([]string{"--flag=hello"})
61	require.NoError(t, err)
62	require.Equal(t, "hello", cli.Flag)
63}
64
65func TestEnvarsTag(t *testing.T) {
66	var cli struct {
67		Slice []int `env:"KONG_NUMBERS"`
68	}
69	parser, restoreEnv := newEnvParser(t, &cli, envMap{"KONG_NUMBERS": "5,2,9"})
70	defer restoreEnv()
71
72	_, err := parser.Parse([]string{})
73	require.NoError(t, err)
74	require.Equal(t, []int{5, 2, 9}, cli.Slice)
75}
76
77func TestEnvarsWithDefault(t *testing.T) {
78	var cli struct {
79		Flag string `env:"KONG_FLAG" default:"default"`
80	}
81	parser, restoreEnv := newEnvParser(t, &cli, envMap{})
82	defer restoreEnv()
83
84	_, err := parser.Parse(nil)
85	require.NoError(t, err)
86	require.Equal(t, "default", cli.Flag)
87
88	parser, restoreEnv = newEnvParser(t, &cli, envMap{"KONG_FLAG": "moo"})
89	defer restoreEnv()
90	_, err = parser.Parse(nil)
91	require.NoError(t, err)
92	require.Equal(t, "moo", cli.Flag)
93}
94
95func TestJSONBasic(t *testing.T) {
96	var cli struct {
97		String          string
98		Slice           []int
99		SliceWithCommas []string
100		Bool            bool
101	}
102
103	json := `{
104		"string": "��",
105		"slice": [5, 8],
106		"bool": true,
107		"slice_with_commas": ["a,b", "c"]
108	}`
109
110	r, err := kong.JSON(strings.NewReader(json))
111	require.NoError(t, err)
112
113	parser := mustNew(t, &cli, kong.Resolvers(r))
114	_, err = parser.Parse([]string{})
115	require.NoError(t, err)
116	require.Equal(t, "��", cli.String)
117	require.Equal(t, []int{5, 8}, cli.Slice)
118	require.Equal(t, []string{"a,b", "c"}, cli.SliceWithCommas)
119	require.True(t, cli.Bool)
120}
121
122type testUppercaseMapper struct{}
123
124func (testUppercaseMapper) Decode(ctx *kong.DecodeContext, target reflect.Value) error {
125	var value string
126	err := ctx.Scan.PopValueInto("lowercase", &value)
127	if err != nil {
128		return err
129	}
130	target.SetString(strings.ToUpper(value))
131	return nil
132}
133
134func TestResolversWithMappers(t *testing.T) {
135	var cli struct {
136		Flag string `env:"KONG_MOO" type:"upper"`
137	}
138
139	restoreEnv := tempEnv(envMap{"KONG_MOO": "meow"})
140	defer restoreEnv()
141
142	parser := mustNew(t, &cli,
143		kong.NamedMapper("upper", testUppercaseMapper{}),
144	)
145	_, err := parser.Parse([]string{})
146	require.NoError(t, err)
147	require.Equal(t, "MEOW", cli.Flag)
148}
149
150func TestResolverWithBool(t *testing.T) {
151	var cli struct {
152		Bool bool
153	}
154
155	var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
156		if flag.Name == "bool" {
157			return true, nil
158		}
159		return nil, nil
160	}
161
162	p := mustNew(t, &cli, kong.Resolvers(resolver))
163
164	_, err := p.Parse(nil)
165	require.NoError(t, err)
166	require.True(t, cli.Bool)
167}
168
169func TestLastResolverWins(t *testing.T) {
170	var cli struct {
171		Int []int
172	}
173
174	var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
175		if flag.Name == "int" {
176			return 1, nil
177		}
178		return nil, nil
179	}
180
181	var second kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
182		if flag.Name == "int" {
183			return 2, nil
184		}
185		return nil, nil
186	}
187
188	p := mustNew(t, &cli, kong.Resolvers(first, second))
189	_, err := p.Parse(nil)
190	require.NoError(t, err)
191	require.Equal(t, []int{2}, cli.Int)
192}
193
194func TestResolverSatisfiesRequired(t *testing.T) {
195	// nolint: govet
196	var cli struct {
197		Int int `required`
198	}
199	var resolver kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
200		if flag.Name == "int" {
201			return 1, nil
202		}
203		return nil, nil
204	}
205	_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
206	require.NoError(t, err)
207	require.Equal(t, 1, cli.Int)
208}
209
210func TestResolverTriggersHooks(t *testing.T) {
211	ctx := &hookContext{}
212
213	var cli struct {
214		Flag hookValue
215	}
216
217	var first kong.ResolverFunc = func(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
218		if flag.Name == "flag" {
219			return "one", nil
220		}
221		return nil, nil
222	}
223
224	_, err := mustNew(t, &cli, kong.Bind(ctx), kong.Resolvers(first)).Parse(nil)
225	require.NoError(t, err)
226
227	require.Equal(t, "one", string(cli.Flag))
228	require.Equal(t, []string{"before:", "after:one"}, ctx.values)
229}
230
231type validatingResolver struct {
232	err error
233}
234
235func (v *validatingResolver) Validate(app *kong.Application) error { return v.err }
236func (v *validatingResolver) Resolve(context *kong.Context, parent *kong.Path, flag *kong.Flag) (interface{}, error) {
237	return nil, nil
238}
239
240func TestValidatingResolverErrors(t *testing.T) {
241	resolver := &validatingResolver{err: errors.New("invalid")}
242	var cli struct{}
243	_, err := mustNew(t, &cli, kong.Resolvers(resolver)).Parse(nil)
244	require.EqualError(t, err, "invalid")
245}
246