-
Notifications
You must be signed in to change notification settings - Fork 5.3k
/
stubber.go
204 lines (179 loc) · 4.79 KB
/
stubber.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package prompt
import (
"fmt"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/core"
"github.com/cli/cli/v2/pkg/surveyext"
)
type AskStubber struct {
stubs []*QuestionStub
}
type testing interface {
Errorf(format string, args ...interface{})
Cleanup(func())
}
func NewAskStubber(t testing) *AskStubber {
as, teardown := InitAskStubber()
t.Cleanup(func() {
teardown()
for _, s := range as.stubs {
if !s.matched {
t.Errorf("unmatched prompt stub: %+v", s)
}
}
})
return as
}
// Deprecated: use NewAskStubber
func InitAskStubber() (*AskStubber, func()) {
origSurveyAsk := SurveyAsk
origSurveyAskOne := SurveyAskOne
as := AskStubber{}
answerFromStub := func(p survey.Prompt, fieldName string, response interface{}) error {
var message string
var defaultValue interface{}
var options []string
switch pt := p.(type) {
case *survey.Confirm:
message = pt.Message
defaultValue = pt.Default
case *survey.Input:
message = pt.Message
defaultValue = pt.Default
case *survey.Select:
message = pt.Message
options = pt.Options
case *survey.MultiSelect:
message = pt.Message
options = pt.Options
case *survey.Password:
message = pt.Message
case *surveyext.GhEditor:
message = pt.Message
defaultValue = pt.Default
default:
return fmt.Errorf("prompt type %T is not supported by the stubber", pt)
}
var stub *QuestionStub
for _, s := range as.stubs {
if !s.matched && (s.message == "" && strings.EqualFold(s.Name, fieldName) || s.message == message) {
stub = s
stub.matched = true
break
}
}
if stub == nil {
return fmt.Errorf("no prompt stub for %q", message)
}
if len(stub.options) > 0 {
if err := compareOptions(stub.options, options); err != nil {
return fmt.Errorf("stubbed options mismatch for %q: %v", message, err)
}
}
userValue := stub.Value
if stringValue, ok := stub.Value.(string); ok && len(options) > 0 {
foundIndex := -1
for i, o := range options {
if o == stringValue {
foundIndex = i
break
}
}
if foundIndex < 0 {
return fmt.Errorf("answer %q not found in options for %q: %v", stringValue, message, options)
}
userValue = core.OptionAnswer{
Value: stringValue,
Index: foundIndex,
}
}
if stub.Default {
if defaultIndex, ok := defaultValue.(int); ok && len(options) > 0 {
userValue = core.OptionAnswer{
Value: options[defaultIndex],
Index: defaultIndex,
}
} else if defaultValue == nil && len(options) > 0 {
userValue = core.OptionAnswer{
Value: options[0],
Index: 0,
}
} else {
userValue = defaultValue
}
}
if err := core.WriteAnswer(response, fieldName, userValue); err != nil {
topic := fmt.Sprintf("field %q", fieldName)
if fieldName == "" {
topic = fmt.Sprintf("%q", message)
}
return fmt.Errorf("AskStubber failed writing the answer for %s: %w", topic, err)
}
return nil
}
SurveyAskOne = func(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
return answerFromStub(p, "", response)
}
SurveyAsk = func(qs []*survey.Question, response interface{}, opts ...survey.AskOpt) error {
for _, q := range qs {
if err := answerFromStub(q.Prompt, q.Name, response); err != nil {
return err
}
}
return nil
}
teardown := func() {
SurveyAsk = origSurveyAsk
SurveyAskOne = origSurveyAskOne
}
return &as, teardown
}
type QuestionStub struct {
Name string
Value interface{}
Default bool
matched bool
message string
options []string
}
// AssertOptions asserts the options presented to the user in Selects and MultiSelects.
func (s *QuestionStub) AssertOptions(opts []string) *QuestionStub {
s.options = opts
return s
}
// AnswerWith defines an answer for the given stub.
func (s *QuestionStub) AnswerWith(v interface{}) *QuestionStub {
s.Value = v
return s
}
// AnswerDefault marks the current stub to be answered with the default value for the prompt question.
func (s *QuestionStub) AnswerDefault() *QuestionStub {
s.Default = true
return s
}
// Deprecated: use StubPrompt
func (as *AskStubber) StubOne(value interface{}) {
as.Stub([]*QuestionStub{{Value: value}})
}
// Deprecated: use StubPrompt
func (as *AskStubber) Stub(stubbedQuestions []*QuestionStub) {
as.stubs = append(as.stubs, stubbedQuestions...)
}
// StubPrompt records a stub for an interactive prompt matched by its message.
func (as *AskStubber) StubPrompt(msg string) *QuestionStub {
stub := &QuestionStub{message: msg}
as.stubs = append(as.stubs, stub)
return stub
}
func compareOptions(expected, got []string) error {
if len(expected) != len(got) {
return fmt.Errorf("expected %v, got %v (length mismatch)", expected, got)
}
for i, v := range expected {
if v != got[i] {
return fmt.Errorf("expected %v, got %v", expected, got)
}
}
return nil
}