package cli_test import ( "bytes" "encoding/json" "os/exec" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/secret/internal/bip39" "sneak.berlin/go/secret/internal/secret" ) // TestGetCommandOutputsToStdout tests that 'secret get' outputs the secret // value to stdout, not stderr func TestGetCommandOutputsToStdout(t *testing.T) { t.Parallel() // Create a temporary directory for our vault; each command is given it // in its environment tempDir := t.TempDir() // Find the secret binary path wd, err := filepath.Abs("../..") require.NoError(t, err, "should get working directory") secretPath := filepath.Join(wd, "secret") testPassphrase := "test-passphrase" // Initialize vault //nolint:gosec // G204: test executes the freshly built secret binary cmd := exec.CommandContext(t.Context(), secretPath, "init") cmd.Env = []string{ secret.EnvStateDir + "=" + tempDir, secret.EnvMnemonic + "=" + testMnemonic, secret.EnvUnlockPassphrase + "=" + testPassphrase, "PATH=" + "/usr/bin:/bin", } output, err := cmd.CombinedOutput() require.NoError(t, err, "init should succeed: %s", string(output)) // The binary, unlike these tests, encrypts the passphrase unlocker's key // at age's scrypt work factor, 18. age writes the work factor last on the // second line of priv.age: "-> scrypt ". vaultDir := filepath.Join(tempDir, "vaults.d", "default") unlockerName := readFile(t, filepath.Join(vaultDir, "current-unlocker")) unlockerDir := filepath.Join(vaultDir, "unlockers.d", string(unlockerName)) privAge := readFile(t, filepath.Join(unlockerDir, "priv.age")) header := strings.SplitN(string(privAge), "\n", 3) require.Len(t, header, 3, "priv.age should start with an age header") assert.Regexp(t, `^-> scrypt \S+ 18$`, header[1], "the passphrase unlocker should be encrypted at scrypt work factor 18") // Add a secret //nolint:gosec // G204: test executes the freshly built secret binary cmd = exec.CommandContext(t.Context(), secretPath, "add", "test/secret") cmd.Env = []string{ secret.EnvStateDir + "=" + tempDir, secret.EnvMnemonic + "=" + 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 //nolint:gosec // G204: test executes the freshly built secret binary cmd = exec.CommandContext(t.Context(), secretPath, "get", "test/secret") cmd.Env = []string{ secret.EnvStateDir + "=" + tempDir, secret.EnvMnemonic + "=" + 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") } // TestMnemonicAndListingsOutputToStdout tests that 'secret generate // mnemonic', 'secret vault list' and 'secret unlocker list' print their // output to stdout, so that it can be piped func TestMnemonicAndListingsOutputToStdout(t *testing.T) { t.Parallel() tempDir := t.TempDir() secretPath := secretBinaryPath(t) // run runs the secret binary and returns what it wrote to stdout and to // stderr run := func(args ...string) (string, string) { //nolint:gosec // G204: test executes the freshly built secret binary cmd := exec.CommandContext(t.Context(), secretPath, args...) cmd.Env = []string{ secret.EnvStateDir + "=" + tempDir, secret.EnvMnemonic + "=" + testMnemonic, secret.EnvUnlockPassphrase + "=test-passphrase", "PATH=/usr/bin:/bin", } var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() require.NoError(t, err, "%v should succeed: %s", args, stderr.String()) return stdout.String(), stderr.String() } stdout, stderr := run("generate", "mnemonic") assert.True(t, bip39.IsMnemonicValid(strings.TrimSpace(stdout)), "stdout should hold the mnemonic, not %q", stdout) assert.Contains(t, stderr, "Save this mnemonic phrase securely", "the warning should be on stderr") run("init") stdout, _ = run("vault", "list", "--json") var vaults map[string]any require.NoError(t, json.Unmarshal([]byte(stdout), &vaults), "stdout should hold the vaults as JSON, not %q", stdout) assert.Equal(t, "default", vaults["currentVault"]) stdout, _ = run("unlocker", "list", "--json") var unlockers map[string]any require.NoError(t, json.Unmarshal([]byte(stdout), &unlockers), "stdout should hold the unlockers as JSON, not %q", stdout) assert.Len(t, unlockers["unlockers"], 1) stdout, _ = run("vault", "list") assert.Contains(t, stdout, "default (current)") stdout, _ = run("unlocker", "list") assert.Contains(t, stdout, "Total: 1 unlocker(s)") }