mirror of
https://github.com/juanfont/headscale.git
synced 2025-09-25 17:51:11 +02:00
util: add YesNo prompt
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
e1dd414be4
commit
3001fc2b49
24
hscontrol/util/prompt.go
Normal file
24
hscontrol/util/prompt.go
Normal file
@ -0,0 +1,24 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// YesNo takes a question and prompts the user to answer the
|
||||
// question with a yes or no. It appends a [y/n] to the message.
|
||||
// The question is written to stderr so that content can be redirected
|
||||
// without interfering with the prompt.
|
||||
func YesNo(msg string) bool {
|
||||
fmt.Fprint(os.Stderr, msg+" [y/n] ")
|
||||
|
||||
var resp string
|
||||
fmt.Scanln(&resp)
|
||||
resp = strings.ToLower(resp)
|
||||
switch resp {
|
||||
case "y", "yes", "sure":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
209
hscontrol/util/prompt_test.go
Normal file
209
hscontrol/util/prompt_test.go
Normal file
@ -0,0 +1,209 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestYesNo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "y answer",
|
||||
input: "y\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Y answer",
|
||||
input: "Y\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "yes answer",
|
||||
input: "yes\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "YES answer",
|
||||
input: "YES\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "sure answer",
|
||||
input: "sure\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "SURE answer",
|
||||
input: "SURE\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "n answer",
|
||||
input: "n\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no answer",
|
||||
input: "no\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty answer",
|
||||
input: "\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid answer",
|
||||
input: "maybe\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "random text",
|
||||
input: "foobar\n",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Capture stdin
|
||||
oldStdin := os.Stdin
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
stderrR, stderrW, _ := os.Pipe()
|
||||
os.Stderr = stderrW
|
||||
|
||||
// Write test input
|
||||
go func() {
|
||||
defer w.Close()
|
||||
w.WriteString(tt.input)
|
||||
}()
|
||||
|
||||
// Call the function
|
||||
result := YesNo("Test question")
|
||||
|
||||
// Restore stdin and stderr
|
||||
os.Stdin = oldStdin
|
||||
os.Stderr = oldStderr
|
||||
stderrW.Close()
|
||||
|
||||
// Check the result
|
||||
if result != tt.expected {
|
||||
t.Errorf("YesNo() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
|
||||
// Check that the prompt was written to stderr
|
||||
var stderrBuf bytes.Buffer
|
||||
io.Copy(&stderrBuf, stderrR)
|
||||
stderrR.Close()
|
||||
|
||||
expectedPrompt := "Test question [y/n] "
|
||||
actualPrompt := stderrBuf.String()
|
||||
if actualPrompt != expectedPrompt {
|
||||
t.Errorf("Expected prompt %q, got %q", expectedPrompt, actualPrompt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYesNoPromptMessage(t *testing.T) {
|
||||
// Capture stdin
|
||||
oldStdin := os.Stdin
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
|
||||
// Capture stderr
|
||||
oldStderr := os.Stderr
|
||||
stderrR, stderrW, _ := os.Pipe()
|
||||
os.Stderr = stderrW
|
||||
|
||||
// Write test input
|
||||
go func() {
|
||||
defer w.Close()
|
||||
w.WriteString("n\n")
|
||||
}()
|
||||
|
||||
// Call the function with a custom message
|
||||
customMessage := "Do you want to continue with this dangerous operation?"
|
||||
YesNo(customMessage)
|
||||
|
||||
// Restore stdin and stderr
|
||||
os.Stdin = oldStdin
|
||||
os.Stderr = oldStderr
|
||||
stderrW.Close()
|
||||
|
||||
// Check that the custom message was included in the prompt
|
||||
var stderrBuf bytes.Buffer
|
||||
io.Copy(&stderrBuf, stderrR)
|
||||
stderrR.Close()
|
||||
|
||||
expectedPrompt := customMessage + " [y/n] "
|
||||
actualPrompt := stderrBuf.String()
|
||||
if actualPrompt != expectedPrompt {
|
||||
t.Errorf("Expected prompt %q, got %q", expectedPrompt, actualPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYesNoCaseInsensitive(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{"y\n", true},
|
||||
{"Y\n", true},
|
||||
{"yes\n", true},
|
||||
{"Yes\n", true},
|
||||
{"YES\n", true},
|
||||
{"yEs\n", true},
|
||||
{"sure\n", true},
|
||||
{"Sure\n", true},
|
||||
{"SURE\n", true},
|
||||
{"SuRe\n", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run("input_"+strings.TrimSpace(tc.input), func(t *testing.T) {
|
||||
// Capture stdin
|
||||
oldStdin := os.Stdin
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdin = r
|
||||
|
||||
// Capture stderr to avoid output during tests
|
||||
oldStderr := os.Stderr
|
||||
stderrR, stderrW, _ := os.Pipe()
|
||||
os.Stderr = stderrW
|
||||
|
||||
// Write test input
|
||||
go func() {
|
||||
defer w.Close()
|
||||
w.WriteString(tc.input)
|
||||
}()
|
||||
|
||||
// Call the function
|
||||
result := YesNo("Test")
|
||||
|
||||
// Restore stdin and stderr
|
||||
os.Stdin = oldStdin
|
||||
os.Stderr = oldStderr
|
||||
stderrW.Close()
|
||||
|
||||
// Drain stderr
|
||||
io.Copy(io.Discard, stderrR)
|
||||
stderrR.Close()
|
||||
|
||||
if result != tc.expected {
|
||||
t.Errorf("Input %q: expected %v, got %v", strings.TrimSpace(tc.input), tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user