check / check (push) Waiting to run
`secret generate mnemonic`, `secret vault list` and `secret unlocker list`, in text and `--json` form, printed through cobra's `cmd.Println` and `cmd.Printf`. Those write to stderr when no output writer is set, and the binary sets none, so a pipe or `$(...)` got nothing. They now write to `cmd.OutOrStdout()`, as `secret list` does, and so does "No versions found" from `secret version list`. Status messages and the warning printed after the mnemonic stay on stderr. A new test runs the built binary and reads the stdout of `generate mnemonic` and of both listings in both forms. Model: opus-5-5
158 lines
5.0 KiB
Go
158 lines
5.0 KiB
Go
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 <salt> <work factor>".
|
|
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)")
|
|
}
|