1package cli
2
3import (
4	"errors"
5	"flag"
6	"fmt"
7	"io/ioutil"
8	"strings"
9	"testing"
10)
11
12func TestCommandFlagParsing(t *testing.T) {
13	cases := []struct {
14		testArgs        []string
15		skipFlagParsing bool
16		skipArgReorder  bool
17		expectedErr     error
18	}{
19		// Test normal "not ignoring flags" flow
20		{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")},
21
22		// Test no arg reorder
23		{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil},
24
25		{[]string{"test-cmd", "blah", "blah"}, true, false, nil},   // Test SkipFlagParsing without any args that look like flags
26		{[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg
27		{[]string{"test-cmd", "blah", "-help"}, true, false, nil},  // Test SkipFlagParsing with "special" help flag arg
28	}
29
30	for _, c := range cases {
31		app := NewApp()
32		app.Writer = ioutil.Discard
33		set := flag.NewFlagSet("test", 0)
34		set.Parse(c.testArgs)
35
36		context := NewContext(app, set, nil)
37
38		command := Command{
39			Name:            "test-cmd",
40			Aliases:         []string{"tc"},
41			Usage:           "this is for testing",
42			Description:     "testing",
43			Action:          func(_ *Context) error { return nil },
44			SkipFlagParsing: c.skipFlagParsing,
45			SkipArgReorder:  c.skipArgReorder,
46		}
47
48		err := command.Run(context)
49
50		expect(t, err, c.expectedErr)
51		expect(t, []string(context.Args()), c.testArgs)
52	}
53}
54
55func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
56	app := NewApp()
57	app.Commands = []Command{
58		{
59			Name: "bar",
60			Before: func(c *Context) error {
61				return fmt.Errorf("before error")
62			},
63			After: func(c *Context) error {
64				return fmt.Errorf("after error")
65			},
66		},
67	}
68
69	err := app.Run([]string{"foo", "bar"})
70	if err == nil {
71		t.Fatalf("expected to receive error from Run, got none")
72	}
73
74	if !strings.Contains(err.Error(), "before error") {
75		t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
76	}
77	if !strings.Contains(err.Error(), "after error") {
78		t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
79	}
80}
81
82func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
83	var receivedMsgFromAction string
84	var receivedMsgFromAfter string
85
86	app := NewApp()
87	app.Commands = []Command{
88		{
89			Name: "bar",
90			Before: func(c *Context) error {
91				c.App.Metadata["msg"] = "hello world"
92				return nil
93			},
94			Action: func(c *Context) error {
95				msg, ok := c.App.Metadata["msg"]
96				if !ok {
97					return errors.New("msg not found")
98				}
99				receivedMsgFromAction = msg.(string)
100				return nil
101			},
102			After: func(c *Context) error {
103				msg, ok := c.App.Metadata["msg"]
104				if !ok {
105					return errors.New("msg not found")
106				}
107				receivedMsgFromAfter = msg.(string)
108				return nil
109			},
110		},
111	}
112
113	err := app.Run([]string{"foo", "bar"})
114	if err != nil {
115		t.Fatalf("expected no error from Run, got %s", err)
116	}
117
118	expectedMsg := "hello world"
119
120	if receivedMsgFromAction != expectedMsg {
121		t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q",
122			receivedMsgFromAction, expectedMsg)
123	}
124	if receivedMsgFromAfter != expectedMsg {
125		t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q",
126			receivedMsgFromAction, expectedMsg)
127	}
128}
129
130func TestCommand_OnUsageError_hasCommandContext(t *testing.T) {
131	app := NewApp()
132	app.Commands = []Command{
133		{
134			Name: "bar",
135			Flags: []Flag{
136				IntFlag{Name: "flag"},
137			},
138			OnUsageError: func(c *Context, err error, _ bool) error {
139				return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error())
140			},
141		},
142	}
143
144	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
145	if err == nil {
146		t.Fatalf("expected to receive error from Run, got none")
147	}
148
149	if !strings.HasPrefix(err.Error(), "intercepted in bar") {
150		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
151	}
152}
153
154func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
155	app := NewApp()
156	app.Commands = []Command{
157		{
158			Name: "bar",
159			Flags: []Flag{
160				IntFlag{Name: "flag"},
161			},
162			OnUsageError: func(c *Context, err error, _ bool) error {
163				if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
164					t.Errorf("Expect an invalid value error, but got \"%v\"", err)
165				}
166				return errors.New("intercepted: " + err.Error())
167			},
168		},
169	}
170
171	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
172	if err == nil {
173		t.Fatalf("expected to receive error from Run, got none")
174	}
175
176	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
177		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
178	}
179}
180
181func TestCommand_OnUsageError_WithSubcommand(t *testing.T) {
182	app := NewApp()
183	app.Commands = []Command{
184		{
185			Name: "bar",
186			Subcommands: []Command{
187				{
188					Name: "baz",
189				},
190			},
191			Flags: []Flag{
192				IntFlag{Name: "flag"},
193			},
194			OnUsageError: func(c *Context, err error, _ bool) error {
195				if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
196					t.Errorf("Expect an invalid value error, but got \"%v\"", err)
197				}
198				return errors.New("intercepted: " + err.Error())
199			},
200		},
201	}
202
203	err := app.Run([]string{"foo", "bar", "--flag=wrong"})
204	if err == nil {
205		t.Fatalf("expected to receive error from Run, got none")
206	}
207
208	if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
209		t.Errorf("Expect an intercepted error, but got \"%v\"", err)
210	}
211}
212
213func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
214	app := NewApp()
215	app.ErrWriter = ioutil.Discard
216	app.Commands = []Command{
217		{
218			Name:  "bar",
219			Usage: "this is for testing",
220			Subcommands: []Command{
221				{
222					Name:  "baz",
223					Usage: "this is for testing",
224					Action: func(c *Context) error {
225						if c.App.ErrWriter != ioutil.Discard {
226							return fmt.Errorf("ErrWriter not passed")
227						}
228
229						return nil
230					},
231				},
232			},
233		},
234	}
235
236	err := app.Run([]string{"foo", "bar", "baz"})
237	if err != nil {
238		t.Fatal(err)
239	}
240}
241