latest from ai, it broke the tests

This commit is contained in:
2025-06-20 05:40:20 -07:00
parent 6958b2a6e2
commit 0b31fba663
19 changed files with 1201 additions and 328 deletions

View File

@@ -10,15 +10,50 @@ import (
"testing"
"time"
"git.eeqj.de/sneak/secret/internal/cli"
"git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMain runs before all tests and ensures the binary is built
func TestMain(m *testing.M) {
// Get the current working directory
wd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get working directory: %v\n", err)
os.Exit(1)
}
// Navigate up from internal/cli to project root
projectRoot := filepath.Join(wd, "..", "..")
// Build the binary
cmd := exec.Command("go", "build", "-o", "secret", "./cmd/secret")
cmd.Dir = projectRoot
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to build secret binary: %v\nOutput: %s\n", err, output)
os.Exit(1)
}
// Run the tests
code := m.Run()
// Clean up the binary
os.Remove(filepath.Join(projectRoot, "secret"))
os.Exit(code)
}
// TestSecretManagerIntegration is a comprehensive integration test that exercises
// all functionality of the secret manager using a real filesystem in a temporary directory.
// This test serves as both validation and documentation of the program's behavior.
func TestSecretManagerIntegration(t *testing.T) {
// Enable debug logging to diagnose test failures
os.Setenv("GODEBUG", "berlin.sneak.pkg.secret")
defer os.Unsetenv("GODEBUG")
// Test configuration
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
testPassphrase := "test-passphrase-123"
@@ -30,48 +65,30 @@ func TestSecretManagerIntegration(t *testing.T) {
os.Setenv("SB_SECRET_STATE_DIR", tempDir)
defer os.Unsetenv("SB_SECRET_STATE_DIR")
// Find the secret binary path
// Look for it relative to the test file location
// Find the secret binary path (needed for tests that still use exec.Command)
wd, err := os.Getwd()
require.NoError(t, err, "should get working directory")
// Navigate up from internal/cli to project root
projectRoot := filepath.Join(wd, "..", "..")
secretPath := filepath.Join(projectRoot, "secret")
// Verify the binary exists
_, err = os.Stat(secretPath)
require.NoError(t, err, "secret binary should exist at %s", secretPath)
// Helper function to run the secret command
runSecret := func(args ...string) (string, error) {
cmd := exec.Command(secretPath, args...)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
output, err := cmd.CombinedOutput()
return string(output), err
return cli.ExecuteCommandInProcess(args, "", nil)
}
// Helper function to run secret with environment variables
runSecretWithEnv := func(env map[string]string, args ...string) (string, error) {
cmd := exec.Command(secretPath, args...)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
for k, v := range env {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
}
output, err := cmd.CombinedOutput()
return string(output), err
return cli.ExecuteCommandInProcess(args, "", env)
}
// Helper function to run secret with stdin
runSecretWithStdin := func(stdin string, env map[string]string, args ...string) (string, error) {
return cli.ExecuteCommandInProcess(args, stdin, env)
}
// Declare runSecret to avoid unused variable error - will be used in later tests
_ = runSecret
_ = runSecretWithStdin
// Test 1: Initialize secret manager
// Command: secret init
@@ -81,7 +98,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// - currentvault symlink -> vaults.d/default
// - default vault has pub.age file
// - default vault has unlockers.d directory with passphrase unlocker
test01Initialize(t, tempDir, secretPath, testMnemonic, testPassphrase, runSecretWithEnv)
test01Initialize(t, tempDir, testMnemonic, testPassphrase, runSecretWithEnv)
// Test 2: Vault management - List vaults
// Command: secret vault list
@@ -113,7 +130,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// - secrets.d/database%password/versions/YYYYMMDD.001/ created
// - Version directory contains: pub.age, priv.age, value.age, metadata.age
// - current symlink points to version directory
test05AddSecret(t, tempDir, secretPath, testMnemonic, runSecret, runSecretWithEnv)
test05AddSecret(t, tempDir, testMnemonic, runSecret, runSecretWithEnv, runSecretWithStdin)
// Test 6: Retrieve secret
// Command: secret get database/password
@@ -128,7 +145,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// - New version directory YYYYMMDD.002 created
// - current symlink updated to new version
// - Old version still exists
test07AddSecretVersion(t, tempDir, secretPath, testMnemonic, runSecret, runSecretWithEnv)
test07AddSecretVersion(t, tempDir, testMnemonic, runSecret, runSecretWithEnv, runSecretWithStdin)
// Test 8: List secret versions
// Command: secret version list database/password
@@ -154,13 +171,13 @@ func TestSecretManagerIntegration(t *testing.T) {
// Command: secret list
// Purpose: Show all secrets in current vault
// Expected: Shows database/password with metadata
test11ListSecrets(t, tempDir, secretPath, testMnemonic, runSecret)
test11ListSecrets(t, tempDir, testMnemonic, runSecret, runSecretWithStdin)
// Test 12: Add secrets with different name formats
// Commands: Various secret names (paths, dots, underscores)
// Purpose: Test secret name validation and storage encoding
// Expected: Proper filesystem encoding (/ -> %)
test12SecretNameFormats(t, tempDir, secretPath, testMnemonic, runSecretWithEnv)
test12SecretNameFormats(t, tempDir, testMnemonic, runSecretWithEnv, runSecretWithStdin)
// Test 13: Unlocker management
// Commands: secret unlockers list, secret unlockers add pgp
@@ -180,7 +197,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// Test 15: Cross-vault isolation
// Purpose: Verify secrets in one vault aren't accessible from another
// Expected: Secrets from work vault not visible in default vault
test15VaultIsolation(t, tempDir, secretPath, testMnemonic, runSecret, runSecretWithEnv)
test15VaultIsolation(t, tempDir, testMnemonic, runSecret, runSecretWithEnv, runSecretWithStdin)
// Test 16: Generate random secrets
// Command: secret generate secret api/key --length 32 --type base58
@@ -192,7 +209,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// Command: secret import ssh/key --source ~/.ssh/id_rsa
// Purpose: Import existing file as secret
// Expected: File contents stored as secret value
test17ImportFromFile(t, tempDir, secretPath, testMnemonic, runSecretWithEnv)
test17ImportFromFile(t, tempDir, testMnemonic, runSecretWithEnv, runSecretWithStdin)
// Test 18: Age key management
// Commands: secret encrypt/decrypt using stored age keys
@@ -277,7 +294,7 @@ func TestSecretManagerIntegration(t *testing.T) {
// Helper functions for each test section
func test01Initialize(t *testing.T, tempDir, secretPath, testMnemonic, testPassphrase string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test01Initialize(t *testing.T, tempDir, testMnemonic, testPassphrase string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {
// Run init with environment variables to avoid prompts
output, err := runSecretWithEnv(map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
@@ -343,12 +360,18 @@ func test01Initialize(t *testing.T, tempDir, secretPath, testMnemonic, testPassp
// Read and verify vault metadata content
metadataBytes := readFile(t, vaultMetadata)
t.Logf("Vault metadata raw content: %s", string(metadataBytes))
var metadata map[string]interface{}
err = json.Unmarshal(metadataBytes, &metadata)
require.NoError(t, err, "vault metadata should be valid JSON")
assert.Equal(t, "default", metadata["name"], "vault name should be default")
t.Logf("Parsed metadata: %+v", metadata)
// Verify metadata fields
assert.Equal(t, float64(0), metadata["derivation_index"], "first vault should have index 0")
assert.Contains(t, metadata, "public_key_hash", "should contain public key hash")
assert.Contains(t, metadata, "createdAt", "should contain creation timestamp")
// Verify the longterm.age file in passphrase unlocker
longtermKeyFile := filepath.Join(passphraseUnlockerDir, "longterm.age")
@@ -367,6 +390,10 @@ func test02ListVaults(t *testing.T, runSecret func(...string) (string, error)) {
jsonOutput, err := runSecret("vault", "list", "--json")
require.NoError(t, err, "vault list --json should succeed")
// Debug: log the raw JSON output to see what we're getting
t.Logf("Raw JSON output: %q", jsonOutput)
t.Logf("JSON output length: %d", len(jsonOutput))
// Parse JSON output
var response map[string]interface{}
err = json.Unmarshal([]byte(jsonOutput), &response)
@@ -481,7 +508,6 @@ func test04ImportMnemonic(t *testing.T, tempDir, testMnemonic, testPassphrase st
err = json.Unmarshal(metadataBytes, &metadata)
require.NoError(t, err, "vault metadata should be valid JSON")
assert.Equal(t, "work", metadata["name"], "vault name should be work")
// Work vault should have a different derivation index than default (0)
derivIndex, ok := metadata["derivation_index"].(float64)
require.True(t, ok, "derivation_index should be a number")
@@ -494,7 +520,7 @@ func test04ImportMnemonic(t *testing.T, tempDir, testMnemonic, testPassphrase st
assert.NotEmpty(t, pubKeyHash, "public key hash should not be empty")
}
func test05AddSecret(t *testing.T, tempDir, secretPath, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test05AddSecret(t *testing.T, tempDir, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Switch back to default vault which has derivation index 0
// matching our mnemonic environment variable
_, err := runSecret("vault", "select", "default")
@@ -502,17 +528,11 @@ func test05AddSecret(t *testing.T, tempDir, secretPath, testMnemonic string, run
// Add a secret with environment variables set
secretValue := "password123"
cmd := exec.Command(secretPath, "add", "database/password")
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader(secretValue)
output, err := cmd.CombinedOutput()
output, err := runSecretWithStdin(secretValue, map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", "database/password")
require.NoError(t, err, "add secret should succeed: %s", string(output))
require.NoError(t, err, "add secret should succeed: %s", output)
// The add command has minimal output by design
// Verify filesystem structure
@@ -584,6 +604,9 @@ func test06GetSecret(t *testing.T, testMnemonic string, runSecret func(...string
"SB_SECRET_MNEMONIC": testMnemonic,
}, "get", "database/password")
t.Logf("Get secret output: %q (length=%d)", output, len(output))
t.Logf("Get secret error: %v", err)
require.NoError(t, err, "get secret should succeed")
assert.Equal(t, "password123", strings.TrimSpace(output), "should return correct secret value")
@@ -593,24 +616,18 @@ func test06GetSecret(t *testing.T, testMnemonic string, runSecret func(...string
assert.Contains(t, output, "failed to unlock vault", "should indicate unlock failure")
}
func test07AddSecretVersion(t *testing.T, tempDir, secretPath, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test07AddSecretVersion(t *testing.T, tempDir, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Make sure we're in default vault
_, err := runSecret("vault", "select", "default")
require.NoError(t, err, "vault select should succeed")
// Add new version of existing secret
newSecretValue := "newpassword456"
cmd := exec.Command(secretPath, "add", "database/password", "--force")
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader(newSecretValue)
output, err := cmd.CombinedOutput()
output, err := runSecretWithStdin(newSecretValue, map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", "database/password", "--force")
require.NoError(t, err, "add secret with --force should succeed: %s", string(output))
require.NoError(t, err, "add secret with --force should succeed: %s", output)
// Verify filesystem structure
defaultVaultDir := filepath.Join(tempDir, "vaults.d", "default")
@@ -800,22 +817,16 @@ func test10PromoteVersion(t *testing.T, tempDir, testMnemonic string, runSecret
}
}
func test11ListSecrets(t *testing.T, tempDir, secretPath, testMnemonic string, runSecret func(...string) (string, error)) {
func test11ListSecrets(t *testing.T, tempDir, testMnemonic string, runSecret func(...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Make sure we're in default vault
_, err := runSecret("vault", "select", "default")
require.NoError(t, err, "vault select should succeed")
// Add a couple more secrets to make the list more interesting
for _, secretName := range []string{"api/key", "config/database.yaml"} {
cmd := exec.Command(secretPath, "add", secretName)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader(fmt.Sprintf("test-value-%s", secretName))
_, err := cmd.CombinedOutput()
_, err := runSecretWithStdin(fmt.Sprintf("test-value-%s", secretName), map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", secretName)
require.NoError(t, err, "add %s should succeed", secretName)
}
@@ -878,17 +889,10 @@ func test11ListSecrets(t *testing.T, tempDir, secretPath, testMnemonic string, r
assert.True(t, secretNames["database/password"], "should have database/password")
}
func test12SecretNameFormats(t *testing.T, tempDir, secretPath, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test12SecretNameFormats(t *testing.T, tempDir, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Make sure we're in default vault
runSecret := func(args ...string) (string, error) {
cmd := exec.Command(secretPath, args...)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
output, err := cmd.CombinedOutput()
return string(output), err
return cli.ExecuteCommandInProcess(args, "", nil)
}
_, err := runSecret("vault", "select", "default")
@@ -916,16 +920,10 @@ func test12SecretNameFormats(t *testing.T, tempDir, secretPath, testMnemonic str
// Add each test secret
for _, tc := range testCases {
t.Run(tc.secretName, func(t *testing.T) {
cmd := exec.Command(secretPath, "add", tc.secretName)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader(tc.value)
output, err := cmd.CombinedOutput()
require.NoError(t, err, "add %s should succeed: %s", tc.secretName, string(output))
output, err := runSecretWithStdin(tc.value, map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", tc.secretName)
require.NoError(t, err, "add %s should succeed: %s", tc.secretName, output)
// Verify filesystem storage
secretDir := filepath.Join(secretsDir, tc.storageName)
@@ -971,15 +969,9 @@ func test12SecretNameFormats(t *testing.T, tempDir, secretPath, testMnemonic str
}
t.Run("invalid_"+testName, func(t *testing.T) {
cmd := exec.Command(secretPath, "add", invalidName)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader("test-value")
output, err := cmd.CombinedOutput()
output, err := runSecretWithStdin("test-value", map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", invalidName)
// Some of these might not be invalid after all (e.g., leading/trailing slashes might be stripped, .hidden might be allowed)
// For now, just check the ones we know should definitely fail
@@ -1105,21 +1097,15 @@ func test14SwitchVault(t *testing.T, tempDir string, runSecret func(...string) (
assert.Contains(t, output, "does not exist", "should indicate vault doesn't exist")
}
func test15VaultIsolation(t *testing.T, tempDir, secretPath, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test15VaultIsolation(t *testing.T, tempDir, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Make sure we're in default vault
_, err := runSecret("vault", "select", "default")
require.NoError(t, err, "vault select should succeed")
// Add a unique secret to default vault
cmd := exec.Command(secretPath, "add", "default-only/secret", "--force")
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader("default-vault-secret")
_, err = cmd.CombinedOutput()
_, err = runSecretWithStdin("default-vault-secret", map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", "default-only/secret", "--force")
require.NoError(t, err, "add secret to default vault should succeed")
// Switch to work vault
@@ -1134,15 +1120,9 @@ func test15VaultIsolation(t *testing.T, tempDir, secretPath, testMnemonic string
assert.Contains(t, output, "not found", "should indicate secret not found")
// Add a unique secret to work vault
cmd = exec.Command(secretPath, "add", "work-only/secret", "--force")
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("SB_SECRET_MNEMONIC=%s", testMnemonic),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
cmd.Stdin = strings.NewReader("work-vault-secret")
_, err = cmd.CombinedOutput()
_, err = runSecretWithStdin("work-vault-secret", map[string]string{
"SB_SECRET_MNEMONIC": testMnemonic,
}, "add", "work-only/secret", "--force")
require.NoError(t, err, "add secret to work vault should succeed")
// Switch back to default vault
@@ -1225,17 +1205,10 @@ func test16GenerateSecret(t *testing.T, tempDir, testMnemonic string, runSecret
verifyFileExists(t, versionsDir)
}
func test17ImportFromFile(t *testing.T, tempDir, secretPath, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error)) {
func test17ImportFromFile(t *testing.T, tempDir, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
// Make sure we're in default vault
runSecret := func(args ...string) (string, error) {
cmd := exec.Command(secretPath, args...)
cmd.Env = []string{
fmt.Sprintf("SB_SECRET_STATE_DIR=%s", tempDir),
fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
}
output, err := cmd.CombinedOutput()
return string(output), err
return cli.ExecuteCommandInProcess(args, "", nil)
}
_, err := runSecret("vault", "select", "default")