Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/io/confirm.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package io

import (
"fmt"

"github.com/charmbracelet/huh"
)

func Confirm(confirmed *bool, title string) error {
// If already confirmed via flag, skip the prompt
if *confirmed {
return nil
}

// In non-TTY environments, fail by default with yes flag message
if !IsTerminal() {
return fmt.Errorf("confirmation required but not running in a terminal; use -y or --yes to confirm")
}


form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Expand Down
75 changes: 75 additions & 0 deletions internal/io/confirm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io

import (
"os"
"testing"
)

func TestConfirm(t *testing.T) {
tests := []struct {
name string
confirmed bool
isTTY bool
expectError bool
errorMsg string
}{
{
name: "already confirmed via flag",
confirmed: true,
isTTY: false,
expectError: false,
},
{
name: "not confirmed and not TTY",
confirmed: false,
isTTY: false,
expectError: true,
errorMsg: "confirmation required but not running in a terminal; use -y or --yes to confirm",
},
{
name: "not confirmed and TTY (interactive test skipped)",
confirmed: false,
isTTY: true,
expectError: false, // We'll skip this test as it requires user interaction
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == "not confirmed and TTY (interactive test skipped)" {
t.Skip("Skipping interactive test")
}

// Mock TTY detection by temporarily redirecting stdout
if !tt.isTTY {
// Create a pipe to simulate non-TTY
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer r.Close()
defer w.Close()

// Temporarily replace stdout
oldStdout := os.Stdout
os.Stdout = w
defer func() {
os.Stdout = oldStdout
}()
}

confirmed := tt.confirmed
err := Confirm(&confirmed, "Test confirmation")

if tt.expectError {
if err == nil {
t.Errorf("Expected error but got none")
} else if err.Error() != tt.errorMsg {
t.Errorf("Expected error message %q but got %q", tt.errorMsg, err.Error())
}
} else if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
}
}
5 changes: 1 addition & 4 deletions internal/io/spinner.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package io

import (
"os"

"github.com/charmbracelet/huh/spinner"
"github.com/mattn/go-isatty"
)

func SpinWhile(name string, action func()) error {
if !isatty.IsTerminal(os.Stdout.Fd()) {
if !IsTerminal() {
// No TTY available, just run the action without spinner
action()
return nil
Expand Down
5 changes: 1 addition & 4 deletions internal/io/spinner_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io

import (
"os"
"testing"

"github.com/mattn/go-isatty"
)

func TestSpinWhileWithoutTTY(t *testing.T) {
Expand Down Expand Up @@ -59,7 +56,7 @@ func TestSpinWhileWithError(t *testing.T) {
func TestSpinWhileTTYDetection(t *testing.T) {
// Test that TTY detection works as expected
// This test documents the behavior rather than forcing specific outcomes
isTTY := isatty.IsTerminal(os.Stdout.Fd())
isTTY := IsTerminal()

actionCalled := false
err := SpinWhile("TTY detection test", func() {
Expand Down
12 changes: 12 additions & 0 deletions internal/io/tty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io

import (
"os"

"github.com/mattn/go-isatty"
)

// IsTerminal returns true if stdout is a terminal
func IsTerminal() bool {
return isatty.IsTerminal(os.Stdout.Fd())
}