version, info, remote info, config, and database delete wrote plain text straight to stdout, so they were unstyled and ignored --quiet. Output is now governed by internal/ui in two buckets. Status lines and confirmations (config init, config set, the database delete prompt) go through the ui message methods and are silenced by --quiet. The data a command exists to produce is written plain -- the version/info/remote-info reports, the snapshot list table, config get values, and the --json documents -- and is never suppressed, since a script depends on it and a marker would corrupt a table or document. The database delete confirmation prompt is always shown. Pure-cli commands reach ui through a small commandUI helper. Model: opus-4-8
207 lines
5.9 KiB
Go
207 lines
5.9 KiB
Go
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)
|
|
}
|
|
}
|