package cli //nolint:testpackage // sets the unexported rootFlags directly import ( "bytes" "fmt" "os" "path/filepath" "strings" "testing" "github.com/spf13/cobra" ) // setRootFlags overrides the global rootFlags for the duration of one // test and restores it afterward. These tests must not run in parallel: // the flags are process-global, so the whole struct is saved and put // back rather than left mutated for the next test. func setRootFlags(t *testing.T, f RootFlags) { t.Helper() old := rootFlags rootFlags = f t.Cleanup(func() { rootFlags = old }) } // seedFile writes content to a fresh file and returns its path. func seedFile(t *testing.T, dir, name, content string) string { t.Helper() path := filepath.Join(dir, name) err := os.WriteFile(path, []byte(content), 0o600) if err != nil { t.Fatalf("seeding %s: %v", name, err) } return path } // mustExecute runs a command with its output captured and fails the test // if it errors, returning what the command printed. func mustExecute(t *testing.T, cmd *cobra.Command, args ...string) string { t.Helper() var out bytes.Buffer cmd.SetOut(&out) cmd.SetArgs(args) err := cmd.Execute() if err != nil { t.Fatalf("%s failed: %v", cmd.Name(), err) } return out.String() } // TestVersionQuietSuppressesReport checks that --quiet silences the whole // version report: it is human-facing output, not a scriptable value. // //nolint:paralleltest // mutates the process-global rootFlags func TestVersionQuietSuppressesReport(t *testing.T) { setRootFlags(t, RootFlags{Quiet: true}) out := mustExecute(t, NewVersionCommand()) if out != "" { t.Errorf("--quiet version printed %q, want nothing", out) } } // TestConfigGetIgnoresQuiet checks that a config value is printed even // under --quiet: it is scriptable output a caller depends on, so --quiet // must not suppress it, and it stays machine-plain (no marker, no color). // //nolint:paralleltest // mutates the process-global rootFlags func TestConfigGetIgnoresQuiet(t *testing.T) { dir := t.TempDir() path := seedFile(t, dir, "config.yml", "storage_url: file:///mnt/x\n") setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path}) out := mustExecute(t, newConfigGetCommand(), "storage_url") if out != "file:///mnt/x\n" { t.Errorf("config get --quiet = %q, want the plain value", out) } } // TestConfigSetQuietSuppressesConfirmation checks that --quiet silences // the confirmation line while still writing the value to the file. // //nolint:paralleltest // mutates the process-global rootFlags func TestConfigSetQuietSuppressesConfirmation(t *testing.T) { dir := t.TempDir() path := seedFile(t, dir, "config.yml", "compression_level: 3\n") setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path}) out := mustExecute(t, newConfigSetCommand(), "compression_level", "9") if out != "" { t.Errorf("--quiet config set printed %q, want nothing", out) } data, err := os.ReadFile(path) //nolint:gosec // G304: test-controlled path if err != nil { t.Fatalf("reading config back: %v", err) } if !strings.Contains(string(data), "compression_level: 9") { t.Errorf("config set did not write the value under --quiet:\n%s", data) } } // TestConfigSetConfirmsWhenNotQuiet checks that the confirmation names // the key (styled) when --quiet is not set. // //nolint:paralleltest // mutates the process-global rootFlags func TestConfigSetConfirmsWhenNotQuiet(t *testing.T) { dir := t.TempDir() path := seedFile(t, dir, "config.yml", "compression_level: 3\n") setRootFlags(t, RootFlags{ConfigPath: path}) out := mustExecute(t, newConfigSetCommand(), "compression_level", "9") if !strings.Contains(out, "compression_level") { t.Errorf("config set did not confirm the key: %q", out) } } // TestConfigInitQuietSuppressesConfirmation checks that --quiet silences // the "config written" confirmation while still writing the file. // //nolint:paralleltest // mutates the process-global rootFlags func TestConfigInitQuietSuppressesConfirmation(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "new-config.yml") setRootFlags(t, RootFlags{Quiet: true, ConfigPath: path}) out := mustExecute(t, newConfigInitCommand()) if out != "" { t.Errorf("--quiet config init printed %q, want nothing", out) } _, err := os.Stat(path) if err != nil { t.Errorf("config init did not write the file under --quiet: %v", err) } } // seedDatabaseDeleteConfig writes a valid config whose index_path is a // seeded database file, and returns both paths. func seedDatabaseDeleteConfig(t *testing.T, dir string) (string, string) { t.Helper() dbPath := seedFile(t, dir, "index.sqlite", "not-a-real-db") cfg := fmt.Sprintf(hermeticConfig, filepath.Join(dir, "source"), filepath.Join(dir, "store"), dbPath) cfgPath := seedFile(t, dir, "config.yml", cfg) return dbPath, cfgPath } // TestDatabaseDeleteQuietSuppressesMessage checks that --quiet silences // the "database deleted" line while still removing the file. // //nolint:paralleltest // mutates the process-global rootFlags func TestDatabaseDeleteQuietSuppressesMessage(t *testing.T) { dir := t.TempDir() dbPath, cfgPath := seedDatabaseDeleteConfig(t, dir) setRootFlags(t, RootFlags{Quiet: true, ConfigPath: cfgPath}) out := mustExecute(t, newDatabaseDeleteCommand(), "--force") if out != "" { t.Errorf("--quiet database delete printed %q, want nothing", out) } _, err := os.Stat(dbPath) if !os.IsNotExist(err) { t.Errorf("database delete did not remove the file: stat err = %v", err) } } // TestDatabaseDeleteReportsWhenNotQuiet checks that the deletion is // reported when --quiet is not set. // //nolint:paralleltest // mutates the process-global rootFlags func TestDatabaseDeleteReportsWhenNotQuiet(t *testing.T) { dir := t.TempDir() _, cfgPath := seedDatabaseDeleteConfig(t, dir) setRootFlags(t, RootFlags{ConfigPath: cfgPath}) out := mustExecute(t, newDatabaseDeleteCommand(), "--force") if !strings.Contains(out, "deleted") { t.Errorf("database delete did not report the deletion: %q", out) } }