package cmd import ( "errors" "os" "runtime" "strings" "testing" "github.com/warrenronsiek/ctask/internal/shell" ) // withTTYCheck swaps the package-level isTTYCheck for the duration of the // test. Tests that exercise refusal paths must NOT run in parallel — they // mutate a package global. func withTTYCheck(t *testing.T, fn func() bool) { t.Helper() orig := isTTYCheck isTTYCheck = fn t.Cleanup(func() { isTTYCheck = orig }) } func TestPreflightRefusesNativeWindows(t *testing.T) { if runtime.GOOS != "windows" { t.Skip("native-Windows refusal applies only on Windows") } had := os.Getenv("WSL_DISTRO_NAME") os.Unsetenv("WSL_DISTRO_NAME") t.Cleanup(func() { if had != "" { os.Setenv("WSL_DISTRO_NAME", had) } }) _, err := preflightPersistentEntry("resume") if err == nil { t.Fatal("expected refusal on native Windows") } if !strings.Contains(err.Error(), "tmux") || !strings.Contains(err.Error(), "WSL") { t.Errorf("expected tmux+WSL message: %v", err) } } func TestPreflightRefusesNestedTmux(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("nested-tmux check runs on Unix paths only in this test") } withTTYCheck(t, func() bool { return true }) os.Setenv("TMUX", "/tmp/tmux-1000/default,1234,0") t.Cleanup(func() { os.Unsetenv("TMUX") }) _, err := preflightPersistentEntry("resume") if err == nil { t.Fatal("expected refusal when $TMUX is set") } if !strings.Contains(err.Error(), "already inside tmux") { t.Errorf("expected nested-tmux message: %v", err) } } func TestPreflightRefusesNonTTY(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("focus this case on Unix; Windows TTY semantics covered by manual smoke") } os.Unsetenv("TMUX") withTTYCheck(t, func() bool { return false }) _, err := preflightPersistentEntry("resume") if err == nil { t.Fatal("expected refusal when not a TTY") } if !strings.Contains(err.Error(), "interactive terminal") { t.Errorf("expected interactive-terminal message: %v", err) } if !strings.Contains(err.Error(), "ssh -t") { t.Errorf("error should mention ssh -t: %v", err) } if !strings.Contains(err.Error(), "ctask resume") { t.Errorf("error should mention command-specific bypass: %v", err) } } func TestPreflightCommandNameRendersInHints(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("path-based check covered on Unix") } os.Unsetenv("TMUX") withTTYCheck(t, func() bool { return false }) _, err := preflightPersistentEntry("attach") if err == nil || !strings.Contains(err.Error(), "ctask attach") { t.Errorf("commandName must appear in hint: %v", err) } } func TestPreflightTmuxNotFound(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("PATH manipulation applies on Unix here") } os.Unsetenv("TMUX") withTTYCheck(t, func() bool { return true }) orig := os.Getenv("PATH") t.Cleanup(func() { os.Setenv("PATH", orig) }) os.Setenv("PATH", "") _, err := preflightPersistentEntry("resume") if err == nil { t.Fatal("expected refusal when tmux missing") } if !strings.Contains(err.Error(), "tmux is not installed") { t.Errorf("expected install-hint message: %v", err) } } // Confirm the helper returns the validated tmux path on the happy path so // callers can pass it through LaunchOpts.TmuxPath without re-resolving. func TestPreflightSuccessReturnsTmuxPath(t *testing.T) { if _, _, err := shell.LookupTmux(); err != nil { if errors.Is(err, shell.ErrTmuxNotFound) || errors.Is(err, shell.ErrTmuxTooOld) { t.Skip("tmux not adequate on this host") } } if runtime.GOOS == "windows" { t.Skip("happy-path test on WSL/Linux only") } os.Unsetenv("TMUX") withTTYCheck(t, func() bool { return true }) tmuxPath, err := preflightPersistentEntry("resume") if err != nil { t.Fatalf("expected success, got %v", err) } if tmuxPath == "" { t.Error("expected non-empty tmuxPath on success") } }