email-mcp/internal/cli/setup_test.go
2026-04-10 10:17:38 +02:00

149 lines
3.8 KiB
Go

package cli
import (
"bytes"
"context"
"errors"
"io"
"os"
"strings"
"testing"
)
func TestInteractiveSetupPrompterPromptSetupCollectsCredential(t *testing.T) {
input := strings.NewReader(" imap.example.com \n alice \n secret \n")
output := &bytes.Buffer{}
prompter := NewInteractiveSetupPrompter(input, output)
cred, err := prompter.PromptSetup(context.Background())
if err != nil {
t.Fatalf("PromptSetup returned error: %v", err)
}
if cred.Host != "imap.example.com" || cred.Username != "alice" || cred.Password != "secret" {
t.Fatalf("unexpected credential: %#v", cred)
}
if got := output.String(); got != "IMAP host: Username: Password: " {
t.Fatalf("unexpected prompts: %q", got)
}
}
func TestInteractiveSetupPrompterPromptSetupUsesPasswordReader(t *testing.T) {
output := &bytes.Buffer{}
passwordReader := &passwordReaderStub{password: "secret"}
prompter := NewInteractiveSetupPrompterWithPasswordReader(
strings.NewReader("imap.example.com\nalice\n"),
output,
passwordReader,
)
cred, err := prompter.PromptSetup(context.Background())
if err != nil {
t.Fatalf("PromptSetup returned error: %v", err)
}
if cred.Password != "secret" {
t.Fatalf("expected password from password reader, got %#v", cred)
}
if passwordReader.prompt != "Password: " {
t.Fatalf("unexpected password prompt %q", passwordReader.prompt)
}
}
func TestInteractiveSetupPrompterPromptSetupRejectsMissingFields(t *testing.T) {
input := strings.NewReader("imap.example.com\nalice\n \n")
prompter := NewInteractiveSetupPrompter(input, &bytes.Buffer{})
_, err := prompter.PromptSetup(context.Background())
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Error(), "password is required") {
t.Fatalf("expected password validation error, got %v", err)
}
}
func TestTerminalPasswordReaderReadPasswordUsesHiddenInputForTTY(t *testing.T) {
hiddenCalls := 0
fallbackCalls := 0
reader := terminalPasswordReader{
input: os.Stdin,
output: &bytes.Buffer{},
isTerminal: func(io.Reader) bool {
return true
},
readHiddenPassword: func(file *os.File) ([]byte, error) {
hiddenCalls++
if file != os.Stdin {
t.Fatalf("expected os.Stdin, got %v", file)
}
return []byte("secret"), nil
},
readFallbackPassword: func(string) (string, error) {
fallbackCalls++
return "", nil
},
}
password, err := reader.ReadPassword("Password: ")
if err != nil {
t.Fatalf("ReadPassword returned error: %v", err)
}
if password != "secret" {
t.Fatalf("expected hidden password, got %q", password)
}
if hiddenCalls != 1 {
t.Fatalf("expected hidden reader to be called once, got %d", hiddenCalls)
}
if fallbackCalls != 0 {
t.Fatalf("expected fallback reader not to be called, got %d", fallbackCalls)
}
}
func TestTerminalPasswordReaderReadPasswordFallsBackWhenInputIsNotTTY(t *testing.T) {
hiddenCalls := 0
reader := terminalPasswordReader{
input: strings.NewReader("ignored\n"),
output: &bytes.Buffer{},
isTerminal: func(io.Reader) bool {
return false
},
readHiddenPassword: func(*os.File) ([]byte, error) {
hiddenCalls++
return nil, errors.New("should not be called")
},
readFallbackPassword: func(prompt string) (string, error) {
if prompt != "Password: " {
t.Fatalf("unexpected prompt %q", prompt)
}
return "secret", nil
},
}
password, err := reader.ReadPassword("Password: ")
if err != nil {
t.Fatalf("ReadPassword returned error: %v", err)
}
if password != "secret" {
t.Fatalf("expected fallback password, got %q", password)
}
if hiddenCalls != 0 {
t.Fatalf("expected hidden reader not to be called, got %d", hiddenCalls)
}
}
type passwordReaderStub struct {
password string
err error
prompt string
}
func (p *passwordReaderStub) ReadPassword(prompt string) (string, error) {
p.prompt = prompt
if p.err != nil {
return "", p.err
}
return p.password, nil
}