1515package iostreams
1616
1717import (
18- "fmt"
1918 "testing"
2019
2120 "github.com/slackapi/slack-cli/internal/config"
@@ -24,93 +23,326 @@ import (
2423 "github.com/slackapi/slack-cli/internal/slackerror"
2524 "github.com/spf13/pflag"
2625 "github.com/stretchr/testify/assert"
26+ "github.com/stretchr/testify/require"
2727)
2828
29+ func TestPromptConfigs (t * testing.T ) {
30+ mockFlag := & pflag.Flag {Name : "token" }
31+ mockFlag2 := & pflag.Flag {Name : "team" }
32+
33+ tests := map [string ]struct {
34+ cfg PromptConfig
35+ required bool
36+ flags int
37+ }{
38+ "ConfirmPromptConfig required" : {
39+ cfg : ConfirmPromptConfig {
40+ Required : true ,
41+ },
42+ required : true ,
43+ flags : 0 ,
44+ },
45+ "ConfirmPromptConfig optional" : {
46+ cfg : ConfirmPromptConfig {
47+ Required : false ,
48+ },
49+ required : false ,
50+ flags : 0 ,
51+ },
52+ "InputPromptConfig required" : {
53+ cfg : InputPromptConfig {
54+ Required : true ,
55+ Placeholder : "hint" ,
56+ },
57+ required : true ,
58+ flags : 0 ,
59+ },
60+ "InputPromptConfig optional" : {
61+ cfg : InputPromptConfig {
62+ Required : false ,
63+ },
64+ required : false ,
65+ flags : 0 ,
66+ },
67+ "MultiSelectPromptConfig required" : {
68+ cfg : MultiSelectPromptConfig {
69+ Required : true ,
70+ },
71+ required : true ,
72+ flags : 0 ,
73+ },
74+ "MultiSelectPromptConfig optional" : {
75+ cfg : MultiSelectPromptConfig {
76+ Required : false ,
77+ },
78+ required : false ,
79+ flags : 0 ,
80+ },
81+ "PasswordPromptConfig with flag" : {
82+ cfg : PasswordPromptConfig {
83+ Flag : mockFlag ,
84+ Required : true ,
85+ },
86+ required : true ,
87+ flags : 1 ,
88+ },
89+ "PasswordPromptConfig without flag" : {
90+ cfg : PasswordPromptConfig {
91+ Required : false ,
92+ },
93+ required : false ,
94+ flags : 0 ,
95+ },
96+ "SelectPromptConfig with single flag" : {
97+ cfg : SelectPromptConfig {
98+ Flag : mockFlag ,
99+ Required : true ,
100+ },
101+ required : true ,
102+ flags : 1 ,
103+ },
104+ "SelectPromptConfig with multiple flags" : {
105+ cfg : SelectPromptConfig {
106+ Flags : []* pflag.Flag {mockFlag , mockFlag2 },
107+ Required : true ,
108+ },
109+ required : true ,
110+ flags : 2 ,
111+ },
112+ "SelectPromptConfig without flags" : {
113+ cfg : SelectPromptConfig {
114+ Required : false ,
115+ },
116+ required : false ,
117+ flags : 0 ,
118+ },
119+ }
120+ for name , tc := range tests {
121+ t .Run (name , func (t * testing.T ) {
122+ assert .Equal (t , tc .required , tc .cfg .IsRequired ())
123+ assert .Len (t , tc .cfg .GetFlags (), tc .flags )
124+ })
125+ }
126+ }
127+
128+ func TestRetrieveFlagValue (t * testing.T ) {
129+ tests := map [string ]struct {
130+ flagset []* pflag.Flag
131+ expectedFlag bool
132+ expectedError string
133+ }{
134+ "nil flagset returns nil" : {
135+ flagset : nil ,
136+ expectedFlag : false ,
137+ },
138+ "nil flag in set is skipped" : {
139+ flagset : []* pflag.Flag {nil },
140+ expectedFlag : false ,
141+ },
142+ "unchanged flag returns nil" : {
143+ flagset : []* pflag.Flag {
144+ {Name : "test" , Changed : false },
145+ },
146+ expectedFlag : false ,
147+ },
148+ "single changed flag is returned" : {
149+ flagset : []* pflag.Flag {
150+ {Name : "test" , Changed : true },
151+ },
152+ expectedFlag : true ,
153+ },
154+ "multiple changed flags returns error" : {
155+ flagset : []* pflag.Flag {
156+ {Name : "a" , Changed : true },
157+ {Name : "b" , Changed : true },
158+ },
159+ expectedError : slackerror .ErrMismatchedFlags ,
160+ },
161+ }
162+ for name , tc := range tests {
163+ t .Run (name , func (t * testing.T ) {
164+ io := & IOStreams {}
165+ flag , err := io .retrieveFlagValue (tc .flagset )
166+ if err != nil {
167+ assert .Nil (t , flag )
168+ assert .Equal (t , tc .expectedError , slackerror .ToSlackError (err ).Code )
169+ } else {
170+ assert .NoError (t , err )
171+ assert .Equal (t , tc .expectedFlag , flag != nil )
172+ }
173+ })
174+ }
175+ }
176+
177+ func TestErrInteractivityFlags (t * testing.T ) {
178+ tests := map [string ]struct {
179+ cfg PromptConfig
180+ contains []string
181+ }{
182+ "no flags shows generic message" : {
183+ cfg : ConfirmPromptConfig {},
184+ contains : []string {"not a TTY" },
185+ },
186+ "one flag suggests the flag name" : {
187+ cfg : PasswordPromptConfig {
188+ Flag : & pflag.Flag {Name : "token" },
189+ },
190+ contains : []string {"--token" },
191+ },
192+ "multiple flags lists all flag names" : {
193+ cfg : SelectPromptConfig {Flags : []* pflag.Flag {
194+ {Name : "app" },
195+ {Name : "team" },
196+ }},
197+ contains : []string {"--app" , "--team" },
198+ },
199+ }
200+ for name , tc := range tests {
201+ t .Run (name , func (t * testing.T ) {
202+ err := errInteractivityFlags (tc .cfg )
203+ for _ , s := range tc .contains {
204+ assert .Contains (t , err .Error (), s )
205+ }
206+ })
207+ }
208+ }
209+
29210func TestPasswordPrompt (t * testing.T ) {
30211 tests := map [string ]struct {
31- FlagChanged bool
32- FlagValue string
33- Required bool
34- IsInteractive bool
35- ExpectedError * slackerror. Error
36- ExpectedValue string
212+ flagChanged bool
213+ flagValue string
214+ required bool
215+ expectedError string
216+ expectedValue string
217+ expectedFlag bool
37218 }{
38219 "error if no flag is set" : {
39- IsInteractive : false ,
40- ExpectedError : slackerror .New (slackerror .ErrPrompt ),
220+ expectedError : slackerror .ErrPrompt ,
41221 },
42222 "error if the flag is missing a required value" : {
43- FlagChanged : true ,
44- FlagValue : "" ,
45- Required : true ,
46- ExpectedError : slackerror .New ( slackerror . ErrMissingFlag ) ,
223+ flagChanged : true ,
224+ flagValue : "" ,
225+ required : true ,
226+ expectedError : slackerror .ErrMissingFlag ,
47227 },
48228 "allow an empty flag value if not required" : {
49- FlagChanged : true ,
50- FlagValue : "" ,
51- Required : false ,
52- ExpectedValue : "" ,
229+ flagChanged : true ,
230+ flagValue : "" ,
231+ required : false ,
232+ expectedValue : "" ,
233+ expectedFlag : true ,
53234 },
54235 "use the provided flag value" : {
55- FlagChanged : true ,
56- FlagValue : "something secret" ,
57- ExpectedValue : "something secret" ,
236+ flagChanged : true ,
237+ flagValue : "something secret" ,
238+ expectedValue : "something secret" ,
239+ expectedFlag : true ,
58240 },
59- // "values can be entered interactively": {
60- // IsInteractive: true,
61- // },
62241 }
242+ for name , tc := range tests {
243+ t .Run (name , func (t * testing.T ) {
244+ ctx := slackcontext .MockContext (t .Context ())
63245
64- var mockFlagValue string
65- pflag .StringVar (& mockFlagValue , "mockedflag" , "" , "mock usage" )
66- mockFlag := pflag .Lookup ("mockedflag" )
246+ fsMock := slackdeps .NewFsMock ()
247+ osMock := slackdeps .NewOsMock ()
248+ // TODO: add interactive tests with FileInfoCharDevice for TTY stdout
249+ osMock .On ("Stdout" ).Return (& slackdeps.FileMock {FileInfo : & slackdeps.FileInfoNamedPipe {}})
250+ cfg := config .NewConfig (fsMock , osMock )
251+ io := NewIOStreams (cfg , fsMock , osMock )
67252
68- interactiveStdout := & slackdeps.FileMock {
69- FileInfo : & slackdeps.FileInfoCharDevice {},
70- }
71- nonInteractiveStdout := & slackdeps.FileMock {
72- FileInfo : & slackdeps.FileInfoNamedPipe {},
253+ fs := pflag .NewFlagSet ("test" , pflag .ContinueOnError )
254+ fs .String ("token" , "" , "" )
255+ if tc .flagChanged {
256+ require .NoError (t , fs .Set ("token" , tc .flagValue ))
257+ }
258+ flag := fs .Lookup ("token" )
259+
260+ selection , err := io .PasswordPrompt (ctx , "Enter a password" , PasswordPromptConfig {
261+ Flag : flag ,
262+ Required : tc .required ,
263+ })
264+
265+ if err != nil {
266+ assert .Equal (t , tc .expectedError , slackerror .ToSlackError (err ).Code )
267+ } else {
268+ assert .NoError (t , err )
269+ assert .Equal (t , tc .expectedValue , selection .Value )
270+ assert .Equal (t , tc .expectedFlag , selection .Flag )
271+ }
272+ })
73273 }
274+ }
74275
276+ func TestSelectPrompt (t * testing.T ) {
277+ tests := map [string ]struct {
278+ flagValue string
279+ flagChanged bool
280+ options []string
281+ required bool
282+ expectedError string
283+ expectedResp SelectPromptResponse
284+ }{
285+ "use provided flag value" : {
286+ flagValue : "A123" ,
287+ flagChanged : true ,
288+ options : []string {"A123" , "A456" },
289+ required : true ,
290+ expectedResp : SelectPromptResponse {
291+ Flag : true ,
292+ Option : "A123" ,
293+ },
294+ },
295+ "error if required flag is empty" : {
296+ flagValue : "" ,
297+ flagChanged : true ,
298+ options : []string {"A123" },
299+ required : true ,
300+ expectedError : slackerror .ErrMissingFlag ,
301+ },
302+ "error if options are empty" : {
303+ options : []string {},
304+ required : true ,
305+ expectedError : slackerror .ErrMissingOptions ,
306+ },
307+ "error if non-TTY and required" : {
308+ options : []string {"a" , "b" },
309+ required : true ,
310+ expectedError : slackerror .ErrPrompt ,
311+ },
312+ "no error if non-TTY and optional" : {
313+ options : []string {"a" , "b" },
314+ required : false ,
315+ expectedResp : SelectPromptResponse {},
316+ },
317+ }
75318 for name , tc := range tests {
76319 t .Run (name , func (t * testing.T ) {
77320 ctx := slackcontext .MockContext (t .Context ())
321+
78322 fsMock := slackdeps .NewFsMock ()
79323 osMock := slackdeps .NewOsMock ()
80- if tc .IsInteractive {
81- osMock .On ("Stdout" ).Return (interactiveStdout )
82- } else {
83- osMock .On ("Stdout" ).Return (nonInteractiveStdout )
84- }
85- config := config .NewConfig (fsMock , osMock )
86- ioStreams := NewIOStreams (config , fsMock , osMock )
324+ osMock .On ("Stdout" ).Return (& slackdeps.FileMock {FileInfo : & slackdeps.FileInfoNamedPipe {}})
325+ cfg := config .NewConfig (fsMock , osMock )
326+ io := NewIOStreams (cfg , fsMock , osMock )
87327
88- if tc . FlagChanged {
89- mockFlag . Changed = true
90- _ = mockFlag . Value . Set ( tc . FlagValue )
91- } else {
92- mockFlag . Changed = false
93- _ = mockFlag . Value . Set ( " " )
328+ var flag * pflag. Flag
329+ if tc . flagChanged {
330+ fs := pflag . NewFlagSet ( "test" , pflag . ContinueOnError )
331+ fs . String ( "app" , "" , "" )
332+ require . NoError ( t , fs . Set ( "app" , tc . flagValue ))
333+ flag = fs . Lookup ( "app " )
94334 }
95335
96- selection , err := ioStreams . PasswordPrompt (ctx , "Enter a password " , PasswordPromptConfig {
97- Flag : mockFlag ,
98- Required : tc .Required ,
336+ selection , err := io . SelectPrompt (ctx , "Choose " , tc . options , SelectPromptConfig {
337+ Flag : flag ,
338+ Required : tc .required ,
99339 })
100340
101- if tc .ExpectedError != nil {
102- assert .Equal (t , tc .ExpectedError .Code , slackerror .ToSlackError (err ).Code )
103- if tc .ExpectedError .Code == slackerror .ErrPrompt {
104- assert .Contains (t , err .Error (), fmt .Sprintf ("--%s" , mockFlag .Name ))
105- }
341+ if err != nil {
342+ assert .Equal (t , tc .expectedError , slackerror .ToSlackError (err ).Code )
106343 } else {
107344 assert .NoError (t , err )
108- assert .Equal (t , selection .Value , tc .ExpectedValue )
109- if tc .FlagChanged {
110- assert .Equal (t , selection .Flag , true )
111- } else {
112- assert .Equal (t , selection .Prompt , true )
113- }
345+ assert .Equal (t , tc .expectedResp , selection )
114346 }
115347 })
116348 }
0 commit comments