package bonfire import ( "bytes" "context" "errors" "io" "strings" "testing" ) func TestConfirmUsesDefaultWhenNonTTY(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader("n\n"), &buf, &buf) got, err := out.Confirm("continue?", true) if err != nil { t.Fatal(err) } if !got { t.Fatal("non-tty confirm should use default true without reading stdin") } if buf.Len() != 0 { t.Fatalf("confirm wrote %q", buf.String()) } } func TestAskReadsStdinLine(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader("alice\n"), &buf, &buf) got, err := out.Ask("name", "bob") if err != nil { t.Fatal(err) } if got != "alice" { t.Fatalf("ask = %q", got) } } func TestAskEOFUsesDefault(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) got, err := out.Ask("name", "bob") if err != nil { t.Fatal(err) } if got != "bob" { t.Fatalf("ask eof = %q", got) } } func TestChoiceReadsStdinLine(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader("2\n"), &buf, &buf) got, err := out.Choice("pick", []string{"a", "b", "c"}, 0) if err != nil { t.Fatal(err) } if got != "b" { t.Fatalf("choice = %q", got) } } func TestSecretReadsPlainStdinLine(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader("s3cret\n"), &buf, &buf) got, err := out.Secret("token") if err != nil { t.Fatal(err) } if got != "s3cret" { t.Fatalf("secret = %q", got) } if strings.Contains(buf.String(), "s3cret") { t.Fatal("secret value was written to output") } } func TestChoiceEOFUsesDefault(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader(""), &buf, &buf) got, err := out.Choice("pick", []string{"a", "b", "c"}, 2) if err != nil { t.Fatal(err) } if got != "c" { t.Fatalf("choice eof = %q", got) } } func TestChoiceEmptyOptionsError(t *testing.T) { var buf bytes.Buffer out := NewOutput(strings.NewReader("1\n"), &buf, &buf) _, err := out.Choice("pick", nil, 0) if err == nil { t.Fatal("expected empty choice error") } } func TestSecretNotLeakedOnCommandError(t *testing.T) { var stdout, stderr bytes.Buffer root, err := NewRootIO("app", []Command{{ Name: "auth:login", Description: "login", Run: func(ctx context.Context, in Input, out Output) error { secret, err := out.Secret("token") if err != nil { return err } if secret == "" { return errors.New("empty token") } out.Error("login failed") return errors.New("login failed") }, }}, strings.NewReader("s3cret\n"), &stdout, &stderr) if err != nil { t.Fatal(err) } root.SetArgs([]string{"auth:login"}) runErr := root.Execute() if runErr == nil { t.Fatal("expected command error") } combined := stdout.String() + stderr.String() + runErr.Error() if strings.Contains(combined, "s3cret") { t.Fatalf("secret leaked into streams: stdout=%q stderr=%q err=%v", stdout.String(), stderr.String(), runErr) } } func TestClosedStdinDoesNotHang(t *testing.T) { var buf bytes.Buffer out := NewOutput(io.NopCloser(strings.NewReader("")), &buf, &buf) if _, err := out.Ask("name", "def"); err != nil { t.Fatal(err) } ok, err := out.Confirm("ok?", false) if err != nil { t.Fatal(err) } if ok { t.Fatal("confirm default false") } if _, err := out.Secret("pw"); err != nil { t.Fatal(err) } }