diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 43053b2..8b79612 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -2,6 +2,8 @@ package cli import ( + "fmt" + "git.eeqj.de/sneak/secret/internal/secret" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -57,3 +59,8 @@ func (cli *Instance) SetStateDir(stateDir string) { func (cli *Instance) GetStateDir() string { return cli.stateDir } + +// Print outputs to the command's configured output writer +func (cli *Instance) Print(a ...interface{}) (n int, err error) { + return fmt.Fprint(cli.cmd.OutOrStdout(), a...) +} diff --git a/internal/cli/secrets.go b/internal/cli/secrets.go index 6af444f..4a3996d 100644 --- a/internal/cli/secrets.go +++ b/internal/cli/secrets.go @@ -278,6 +278,9 @@ func (cli *Instance) GetSecret(cmd *cobra.Command, secretName string) error { func (cli *Instance) GetSecretWithVersion(cmd *cobra.Command, secretName string, version string) error { secret.Debug("GetSecretWithVersion called", "secretName", secretName, "version", version) + // Store the command for output + cli.cmd = cmd + // Get current vault vlt, err := vault.GetCurrentVault(cli.fs, cli.stateDir) if err != nil { @@ -302,8 +305,8 @@ func (cli *Instance) GetSecretWithVersion(cmd *cobra.Command, secretName string, secret.Debug("Got secret value", "valueLength", len(value)) // Print the secret value to stdout - cmd.Print(string(value)) - secret.Debug("Printed value to cmd") + _, _ = cli.Print(string(value)) + secret.Debug("Printed value to stdout") // Debug: Log what we're actually printing secret.Debug("Secret retrieval debug info", diff --git a/internal/cli/stdout_stderr_test.go b/internal/cli/stdout_stderr_test.go new file mode 100644 index 0000000..14042af --- /dev/null +++ b/internal/cli/stdout_stderr_test.go @@ -0,0 +1,72 @@ +package cli_test + +import ( + "bytes" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestGetCommandOutputsToStdout tests that 'secret get' outputs the secret value to stdout, not stderr +func TestGetCommandOutputsToStdout(t *testing.T) { + // Create a temporary directory for our vault + tempDir := t.TempDir() + + // Set environment variables for the test + t.Setenv("SB_SECRET_STATE_DIR", tempDir) + + // Find the secret binary path + wd, err := filepath.Abs("../..") + require.NoError(t, err, "should get working directory") + secretPath := filepath.Join(wd, "secret") + + testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + testPassphrase := "test-passphrase" + + // Initialize vault + cmd := exec.Command(secretPath, "init") + cmd.Env = []string{ + "SB_SECRET_STATE_DIR=" + tempDir, + "SB_SECRET_MNEMONIC=" + testMnemonic, + "SB_UNLOCK_PASSPHRASE=" + testPassphrase, + "PATH=" + "/usr/bin:/bin", + } + output, err := cmd.CombinedOutput() + require.NoError(t, err, "init should succeed: %s", string(output)) + + // Add a secret + cmd = exec.Command(secretPath, "add", "test/secret") + cmd.Env = []string{ + "SB_SECRET_STATE_DIR=" + tempDir, + "SB_SECRET_MNEMONIC=" + testMnemonic, + "PATH=" + "/usr/bin:/bin", + } + cmd.Stdin = strings.NewReader("test-secret-value") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "add should succeed: %s", string(output)) + + // Test that 'secret get' outputs to stdout, not stderr + cmd = exec.Command(secretPath, "get", "test/secret") + cmd.Env = []string{ + "SB_SECRET_STATE_DIR=" + tempDir, + "SB_SECRET_MNEMONIC=" + testMnemonic, + "PATH=" + "/usr/bin:/bin", + } + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err = cmd.Run() + require.NoError(t, err, "get should succeed") + + // The secret value should be in stdout + assert.Equal(t, "test-secret-value", strings.TrimSpace(stdout.String()), "secret value should be in stdout") + + // Nothing should be in stderr + assert.Empty(t, stderr.String(), "stderr should be empty") +}