subcommands_test.go

  1package cmd
  2
  3import (
  4	"os"
  5	"path/filepath"
  6	"strings"
  7	"testing"
  8)
  9
 10func TestSubcommandNoArgsNonInteractiveRunsDirectly(t *testing.T) {
 11	// Not parallel: mutates package-level isStdinTerminal.
 12
 13	// Override stdin terminal check to simulate non-interactive environment.
 14	original := isStdinTerminal
 15	t.Cleanup(func() {
 16		isStdinTerminal = original
 17	})
 18	isStdinTerminal = func() bool { return false }
 19
 20	// Create a config with backup paths defined so the command can run.
 21	configFile := setupConfigWithBackupPaths(t)
 22	setRootFlagValuesForTest(t, "home@cloud", true, configFile)
 23	t.Setenv("KELD_CONFIG_FILE", "")
 24	t.Setenv("KELD_DRYRUN", "")
 25
 26	backup := lookupSubcommand(t, "backup")
 27	out, err := captureStdout(t, func() error {
 28		return backup.RunE(backup, []string{})
 29	})
 30	// Should NOT get a bubbletea TTY error. Instead, should either:
 31	// - Succeed with dry-run output (if config has paths), or
 32	// - Fail with a sensible error about missing inputs.
 33	if err != nil {
 34		errMsg := err.Error()
 35		// The old bug would produce "bubbletea: error opening TTY" here.
 36		if strings.Contains(errMsg, "bubbletea") || strings.Contains(errMsg, "TTY") {
 37			t.Fatalf("non-interactive mode incorrectly tried to open TTY: %v", err)
 38		}
 39		// A different error is acceptable (e.g., missing paths).
 40		// For now we just verify no TTY error occurred.
 41		t.Logf("command returned error (expected for missing paths): %v", err)
 42		return
 43	}
 44
 45	// If no error, we should see dry-run output.
 46	if !strings.Contains(out, `"backup"`) {
 47		t.Fatalf("expected dry-run output to contain backup command, got:\n%s", out)
 48	}
 49}
 50
 51// setupConfigWithBackupPaths creates a config file with a preset that has
 52// backup paths defined, allowing the backup command to run without args.
 53func setupConfigWithBackupPaths(t *testing.T) string {
 54	t.Helper()
 55
 56	dir := t.TempDir()
 57	cfg := filepath.Join(dir, "config.toml")
 58	err := os.WriteFile(cfg, []byte(`
 59[global]
 60repo = "/repos/default"
 61
 62["home@"]
 63tag = "home"
 64
 65["home@".backup]
 66_arguments = "/src /dst"
 67
 68["@cloud"]
 69repo = "/repos/cloud"
 70`), 0o600)
 71	if err != nil {
 72		t.Fatalf("writing fixture: %v", err)
 73	}
 74	t.Setenv("HOME", dir)
 75
 76	return cfg
 77}
 78
 79func TestSubcommandNoArgsInteractiveEntersSession(t *testing.T) {
 80	// Not parallel: mutates package-level isStdinTerminal.
 81
 82	// Override stdin terminal check to simulate interactive terminal.
 83	original := isStdinTerminal
 84	t.Cleanup(func() {
 85		isStdinTerminal = original
 86	})
 87	isStdinTerminal = func() bool { return true }
 88
 89	configFile := setupConfigWithBackupPaths(t)
 90	setRootFlagValuesForTest(t, "", true, configFile)
 91	t.Setenv("KELD_CONFIG_FILE", "")
 92	t.Setenv("KELD_DRYRUN", "")
 93
 94	backup := lookupSubcommand(t, "backup")
 95
 96	// In interactive mode with no args, runInteractive is called.
 97	// Since there's no real TTY attached during test, bubbletea will
 98	// try to open /dev/tty and fail. We assert this behavior is happening
 99	// (rather than the command running directly).
100	_, err := captureStdout(t, func() error {
101		return backup.RunE(backup, []string{})
102	})
103
104	// The expected behavior: bubbletea tries to open TTY and fails.
105	if err == nil {
106		// If it succeeded, either we're not in interactive mode or
107		// the test environment has a TTY (unlikely in CI).
108		t.Fatal("expected bubbletea TTY error when simulating interactive mode, got success")
109	}
110	errMsg := err.Error()
111	if !strings.Contains(errMsg, "TTY") && !strings.Contains(errMsg, "tty") {
112		t.Fatalf("expected bubbletea TTY error in interactive mode, got: %v", err)
113	}
114}