test: Add comprehensive test suite for secret manager - CLI, debug, secret, and vault tests with in-memory filesystem for fast isolated testing

This commit is contained in:
Jeffrey Paul 2025-05-29 09:52:05 -07:00
parent 7dc14da4af
commit b26794e21a
4 changed files with 969 additions and 0 deletions

View File

@ -0,0 +1,66 @@
package secret
import (
"os"
"path/filepath"
"testing"
"github.com/spf13/afero"
)
func TestCLIInstanceStateDir(t *testing.T) {
// Test the CLI instance state directory functionality
fs := afero.NewMemMapFs()
// Create a test state directory
testStateDir := "/test-state-dir"
cli := NewCLIInstanceWithStateDir(fs, testStateDir)
if cli.GetStateDir() != testStateDir {
t.Errorf("Expected state directory %q, got %q", testStateDir, cli.GetStateDir())
}
}
func TestCLIInstanceWithFs(t *testing.T) {
// Test creating CLI instance with custom filesystem
fs := afero.NewMemMapFs()
cli := NewCLIInstanceWithFs(fs)
// The state directory should be determined automatically
stateDir := cli.GetStateDir()
if stateDir == "" {
t.Error("Expected non-empty state directory")
}
}
func TestDetermineStateDir(t *testing.T) {
// Test the determineStateDir function
// Save original environment and restore it after test
originalStateDir := os.Getenv(EnvStateDir)
defer func() {
if originalStateDir == "" {
os.Unsetenv(EnvStateDir)
} else {
os.Setenv(EnvStateDir, originalStateDir)
}
}()
// Test with environment variable set
testEnvDir := "/test-env-dir"
os.Setenv(EnvStateDir, testEnvDir)
stateDir := determineStateDir("")
if stateDir != testEnvDir {
t.Errorf("Expected state directory %q from environment, got %q", testEnvDir, stateDir)
}
// Test with custom config dir
os.Unsetenv(EnvStateDir)
customConfigDir := "/custom-config"
stateDir = determineStateDir(customConfigDir)
expectedDir := filepath.Join(customConfigDir, AppID)
if stateDir != expectedDir {
t.Errorf("Expected state directory %q with custom config, got %q", expectedDir, stateDir)
}
}

View File

@ -0,0 +1,141 @@
package secret
import (
"bytes"
"log/slog"
"os"
"strings"
"syscall"
"testing"
"golang.org/x/term"
)
func TestDebugLogging(t *testing.T) {
// Save original GODEBUG and restore it
originalGodebug := os.Getenv("GODEBUG")
defer func() {
if originalGodebug == "" {
os.Unsetenv("GODEBUG")
} else {
os.Setenv("GODEBUG", originalGodebug)
}
// Re-initialize debug system with original setting
initDebugLogging()
}()
tests := []struct {
name string
godebug string
expectEnabled bool
}{
{
name: "debug enabled",
godebug: "berlin.sneak.pkg.secret",
expectEnabled: true,
},
{
name: "debug enabled with other flags",
godebug: "other=1,berlin.sneak.pkg.secret,another=value",
expectEnabled: true,
},
{
name: "debug disabled",
godebug: "other=1",
expectEnabled: false,
},
{
name: "debug disabled empty",
godebug: "",
expectEnabled: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set GODEBUG
if tt.godebug == "" {
os.Unsetenv("GODEBUG")
} else {
os.Setenv("GODEBUG", tt.godebug)
}
// Re-initialize debug system
initDebugLogging()
// Test if debug is enabled
enabled := IsDebugEnabled()
if enabled != tt.expectEnabled {
t.Errorf("IsDebugEnabled() = %v, want %v", enabled, tt.expectEnabled)
}
// If debug should be enabled, test that debug output works
if tt.expectEnabled {
// Capture debug output by redirecting the colorized handler
var buf bytes.Buffer
// Override the debug logger for testing
oldLogger := debugLogger
if term.IsTerminal(int(syscall.Stderr)) {
// TTY: use colorized handler with our buffer
debugLogger = slog.New(newColorizedHandler(&buf))
} else {
// Non-TTY: use JSON handler with our buffer
debugLogger = slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
}
// Test debug output
Debug("test message", "key", "value")
// Restore original logger
debugLogger = oldLogger
// Check that output was generated
output := buf.String()
if !strings.Contains(output, "test message") {
t.Errorf("Debug output does not contain expected message. Got: %s", output)
}
}
})
}
}
func TestDebugFunctions(t *testing.T) {
// Enable debug for testing
originalGodebug := os.Getenv("GODEBUG")
os.Setenv("GODEBUG", "berlin.sneak.pkg.secret")
defer func() {
if originalGodebug == "" {
os.Unsetenv("GODEBUG")
} else {
os.Setenv("GODEBUG", originalGodebug)
}
initDebugLogging()
}()
initDebugLogging()
if !IsDebugEnabled() {
t.Skip("Debug not enabled, skipping debug function tests")
}
// Test that debug functions don't panic and can be called
t.Run("Debug", func(t *testing.T) {
Debug("test debug message")
Debug("test with args", "key", "value", "number", 42)
})
t.Run("DebugF", func(t *testing.T) {
DebugF("formatted message: %s %d", "test", 123)
})
t.Run("DebugWith", func(t *testing.T) {
DebugWith("structured message",
slog.String("string_key", "string_value"),
slog.Int("int_key", 42),
slog.Bool("bool_key", true),
)
})
}

View File

@ -0,0 +1,188 @@
package secret
import (
"bytes"
"os"
"path/filepath"
"testing"
"git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/spf13/afero"
)
func TestPerSecretKeyFunctionality(t *testing.T) {
// Create an in-memory filesystem for testing
fs := afero.NewMemMapFs()
// Set up test environment variables
oldMnemonic := os.Getenv(EnvMnemonic)
defer func() {
if oldMnemonic == "" {
os.Unsetenv(EnvMnemonic)
} else {
os.Setenv(EnvMnemonic, oldMnemonic)
}
}()
// Set test mnemonic for direct encryption/decryption
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
os.Setenv(EnvMnemonic, testMnemonic)
// Set up a test vault structure
baseDir := "/test-config/berlin.sneak.pkg.secret"
stateDir := baseDir
vaultDir := filepath.Join(baseDir, "vaults.d", "test-vault")
// Create vault directory structure
err := fs.MkdirAll(filepath.Join(vaultDir, "secrets.d"), 0700)
if err != nil {
t.Fatalf("Failed to create vault directory: %v", err)
}
// Generate a long-term keypair for the vault using the test mnemonic
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to generate long-term identity: %v", err)
}
// Write long-term public key
ltPubKeyPath := filepath.Join(vaultDir, "pub.age")
err = afero.WriteFile(
fs,
ltPubKeyPath,
[]byte(ltIdentity.Recipient().String()),
0600,
)
if err != nil {
t.Fatalf("Failed to write long-term public key: %v", err)
}
// Set current vault
currentVaultPath := filepath.Join(baseDir, "currentvault")
err = afero.WriteFile(fs, currentVaultPath, []byte(vaultDir), 0600)
if err != nil {
t.Fatalf("Failed to set current vault: %v", err)
}
// Create vault instance
vault := NewVault(fs, "test-vault", stateDir)
// Test data
secretName := "test-secret"
secretValue := []byte("this is a test secret value")
// Test AddSecret
t.Run("AddSecret", func(t *testing.T) {
err := vault.AddSecret(secretName, secretValue, false)
if err != nil {
t.Fatalf("AddSecret failed: %v", err)
}
// Verify that all expected files were created
secretDir := filepath.Join(vaultDir, "secrets.d", secretName)
// Check value.age exists (the new per-secret key architecture format)
secretExists, err := afero.Exists(
fs,
filepath.Join(secretDir, "value.age"),
)
if err != nil || !secretExists {
t.Fatalf("value.age file was not created")
}
// Check metadata exists
metadataExists, err := afero.Exists(
fs,
filepath.Join(secretDir, "secret-metadata.json"),
)
if err != nil || !metadataExists {
t.Fatalf("secret-metadata.json file was not created")
}
t.Logf("All expected files created successfully")
})
// Test GetSecret
t.Run("GetSecret", func(t *testing.T) {
retrievedValue, err := vault.GetSecret(secretName)
if err != nil {
t.Fatalf("GetSecret failed: %v", err)
}
if !bytes.Equal(retrievedValue, secretValue) {
t.Fatalf(
"Retrieved value doesn't match original. Expected: %s, Got: %s",
string(secretValue),
string(retrievedValue),
)
}
t.Logf("Successfully retrieved secret: %s", string(retrievedValue))
})
// Test that different secrets get different keys
t.Run("DifferentSecretsGetDifferentKeys", func(t *testing.T) {
secretName2 := "test-secret-2"
secretValue2 := []byte("this is another test secret")
// Add second secret
err := vault.AddSecret(secretName2, secretValue2, false)
if err != nil {
t.Fatalf("Failed to add second secret: %v", err)
}
// Verify both secrets can be retrieved correctly
value1, err := vault.GetSecret(secretName)
if err != nil {
t.Fatalf("Failed to retrieve first secret: %v", err)
}
value2, err := vault.GetSecret(secretName2)
if err != nil {
t.Fatalf("Failed to retrieve second secret: %v", err)
}
if !bytes.Equal(value1, secretValue) {
t.Fatalf("First secret value mismatch")
}
if !bytes.Equal(value2, secretValue2) {
t.Fatalf("Second secret value mismatch")
}
t.Logf(
"Successfully verified that different secrets have different keys",
)
})
}
func TestSecretNameValidation(t *testing.T) {
tests := []struct {
name string
valid bool
}{
{"valid-name", true},
{"valid.name", true},
{"valid_name", true},
{"valid/path/name", true},
{"123valid", true},
{"", false},
{"Invalid-Name", false}, // uppercase not allowed
{"invalid name", false}, // space not allowed
{"invalid@name", false}, // @ not allowed
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := isValidSecretName(test.name)
if result != test.valid {
t.Errorf(
"isValidSecretName(%q) = %v, want %v",
test.name,
result,
test.valid,
)
}
})
}
}

View File

@ -0,0 +1,574 @@
package secret
import (
"os"
"path/filepath"
"testing"
"time"
"filippo.io/age"
"git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/spf13/afero"
)
const testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// setupTestEnvironment sets up the test environment with mock filesystem and environment variables
func setupTestEnvironment(t *testing.T) (afero.Fs, func()) {
// Create mock filesystem
fs := afero.NewMemMapFs()
// Save original environment variables
oldMnemonic := os.Getenv(EnvMnemonic)
oldPassphrase := os.Getenv(EnvUnlockPassphrase)
oldStateDir := os.Getenv(EnvStateDir)
// Create a real temporary directory for the state directory
// This is needed because GetStateDir checks the real filesystem
realTempDir, err := os.MkdirTemp("", "secret-test-*")
if err != nil {
t.Fatalf("Failed to create real temp directory: %v", err)
}
// Set test environment variables
os.Setenv(EnvMnemonic, testMnemonic)
os.Setenv(EnvUnlockPassphrase, "test-passphrase")
os.Setenv(EnvStateDir, realTempDir)
// Also create the directory structure in the mock filesystem
err = fs.MkdirAll(realTempDir, 0700)
if err != nil {
t.Fatalf("Failed to create test state directory in mock fs: %v", err)
}
// Create vaults.d directory in both filesystems
vaultsDir := filepath.Join(realTempDir, "vaults.d")
err = os.MkdirAll(vaultsDir, 0700)
if err != nil {
t.Fatalf("Failed to create real vaults directory: %v", err)
}
err = fs.MkdirAll(vaultsDir, 0700)
if err != nil {
t.Fatalf("Failed to create mock vaults directory: %v", err)
}
// Return cleanup function
cleanup := func() {
// Clean up real temporary directory
os.RemoveAll(realTempDir)
// Restore environment variables
if oldMnemonic == "" {
os.Unsetenv(EnvMnemonic)
} else {
os.Setenv(EnvMnemonic, oldMnemonic)
}
if oldPassphrase == "" {
os.Unsetenv(EnvUnlockPassphrase)
} else {
os.Setenv(EnvUnlockPassphrase, oldPassphrase)
}
if oldStateDir == "" {
os.Unsetenv(EnvStateDir)
} else {
os.Setenv(EnvStateDir, oldStateDir)
}
}
return fs, cleanup
}
func TestCreateVault(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Test creating a new vault
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
if vault.Name != "test-vault" {
t.Errorf("Expected vault name 'test-vault', got '%s'", vault.Name)
}
// Check that vault directory was created
vaultDir, err := vault.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault directory: %v", err)
}
exists, err := afero.DirExists(fs, vaultDir)
if err != nil {
t.Fatalf("Error checking vault directory: %v", err)
}
if !exists {
t.Errorf("Vault directory was not created")
}
// Check that subdirectories were created
secretsDir := filepath.Join(vaultDir, "secrets.d")
exists, err = afero.DirExists(fs, secretsDir)
if err != nil {
t.Fatalf("Error checking secrets directory: %v", err)
}
if !exists {
t.Errorf("Secrets directory was not created")
}
unlockKeysDir := filepath.Join(vaultDir, "unlock.d")
exists, err = afero.DirExists(fs, unlockKeysDir)
if err != nil {
t.Fatalf("Error checking unlock keys directory: %v", err)
}
if !exists {
t.Errorf("Unlock keys directory was not created")
}
// Test creating a vault that already exists
_, err = CreateVault(fs, stateDir, "test-vault")
if err == nil {
t.Errorf("Expected error when creating vault that already exists")
}
}
func TestSelectVault(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create a vault first
_, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
// Test selecting the vault
err = SelectVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to select vault: %v", err)
}
// Check that currentvault symlink was created with correct target
currentVaultPath := filepath.Join(stateDir, "currentvault")
content, err := afero.ReadFile(fs, currentVaultPath)
if err != nil {
t.Fatalf("Failed to read currentvault symlink: %v", err)
}
expectedPath := filepath.Join(stateDir, "vaults.d", "test-vault")
if string(content) != expectedPath {
t.Errorf("Expected currentvault to point to '%s', got '%s'", expectedPath, string(content))
}
// Test selecting a vault that doesn't exist
err = SelectVault(fs, stateDir, "nonexistent-vault")
if err == nil {
t.Errorf("Expected error when selecting nonexistent vault")
}
}
func TestGetCurrentVault(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create and select a vault
_, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
err = SelectVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to select vault: %v", err)
}
// Test getting current vault
vault, err := GetCurrentVault(fs, stateDir)
if err != nil {
t.Fatalf("Failed to get current vault: %v", err)
}
if vault.Name != "test-vault" {
t.Errorf("Expected current vault name 'test-vault', got '%s'", vault.Name)
}
}
func TestListVaults(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Initially no vaults
vaults, err := ListVaults(fs, stateDir)
if err != nil {
t.Fatalf("Failed to list vaults: %v", err)
}
if len(vaults) != 0 {
t.Errorf("Expected no vaults initially, got %d", len(vaults))
}
// Create multiple vaults
vaultNames := []string{"vault1", "vault2", "vault3"}
for _, name := range vaultNames {
_, err := CreateVault(fs, stateDir, name)
if err != nil {
t.Fatalf("Failed to create vault %s: %v", name, err)
}
}
// List vaults
vaults, err = ListVaults(fs, stateDir)
if err != nil {
t.Fatalf("Failed to list vaults: %v", err)
}
if len(vaults) != len(vaultNames) {
t.Errorf("Expected %d vaults, got %d", len(vaultNames), len(vaults))
}
// Check that all created vaults are in the list
vaultMap := make(map[string]bool)
for _, vault := range vaults {
vaultMap[vault] = true
}
for _, name := range vaultNames {
if !vaultMap[name] {
t.Errorf("Expected vault '%s' in list", name)
}
}
}
func TestVaultGetDirectory(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
vault := NewVault(fs, "test-vault", stateDir)
dir, err := vault.GetDirectory()
if err != nil {
t.Fatalf("Failed to get vault directory: %v", err)
}
expectedDir := "/test-secret-state/vaults.d/test-vault"
if dir != expectedDir {
t.Errorf("Expected directory '%s', got '%s'", expectedDir, dir)
}
}
func TestAddSecret(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create vault and set up long-term key
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
// We need to create a long-term public key for the vault
// This simulates what happens during vault initialization
err = setupVaultWithLongTermKey(fs, vault)
if err != nil {
t.Fatalf("Failed to setup vault with long-term key: %v", err)
}
// Test adding a secret
secretName := "test-secret"
secretValue := []byte("super secret value")
err = vault.AddSecret(secretName, secretValue, false)
if err != nil {
t.Fatalf("Failed to add secret: %v", err)
}
// Check that secret directory was created
vaultDir, _ := vault.GetDirectory()
secretDir := filepath.Join(vaultDir, "secrets.d", secretName)
exists, err := afero.DirExists(fs, secretDir)
if err != nil {
t.Fatalf("Error checking secret directory: %v", err)
}
if !exists {
t.Errorf("Secret directory was not created")
}
// Check that encrypted secret file exists
secretFile := filepath.Join(secretDir, "value.age")
exists, err = afero.Exists(fs, secretFile)
if err != nil {
t.Fatalf("Error checking secret file: %v", err)
}
if !exists {
t.Errorf("Secret file was not created")
}
// Check that metadata file exists
metadataFile := filepath.Join(secretDir, "secret-metadata.json")
exists, err = afero.Exists(fs, metadataFile)
if err != nil {
t.Fatalf("Error checking metadata file: %v", err)
}
if !exists {
t.Errorf("Metadata file was not created")
}
// Test adding a duplicate secret without force flag
err = vault.AddSecret(secretName, secretValue, false)
if err == nil {
t.Errorf("Expected error when adding duplicate secret without force flag")
}
// Test adding a duplicate secret with force flag
err = vault.AddSecret(secretName, []byte("new value"), true)
if err != nil {
t.Errorf("Failed to overwrite secret with force flag: %v", err)
}
// Test adding secret with slash in name (should be encoded)
err = vault.AddSecret("path/to/secret", []byte("value"), false)
if err != nil {
t.Fatalf("Failed to add secret with slash in name: %v", err)
}
// Check that the slash was encoded as percent
encodedSecretDir := filepath.Join(vaultDir, "secrets.d", "path%to%secret")
exists, err = afero.DirExists(fs, encodedSecretDir)
if err != nil {
t.Fatalf("Error checking encoded secret directory: %v", err)
}
if !exists {
t.Errorf("Encoded secret directory was not created")
}
}
func TestGetSecret(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create vault and set up long-term key
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
err = setupVaultWithLongTermKey(fs, vault)
if err != nil {
t.Fatalf("Failed to setup vault with long-term key: %v", err)
}
// Add a secret
secretName := "test-secret"
secretValue := []byte("super secret value")
err = vault.AddSecret(secretName, secretValue, false)
if err != nil {
t.Fatalf("Failed to add secret: %v", err)
}
// Test getting the secret (using mnemonic environment variable)
retrievedValue, err := vault.GetSecret(secretName)
if err != nil {
t.Fatalf("Failed to get secret: %v", err)
}
if string(retrievedValue) != string(secretValue) {
t.Errorf("Expected secret value '%s', got '%s'", string(secretValue), string(retrievedValue))
}
// Test getting a nonexistent secret
_, err = vault.GetSecret("nonexistent-secret")
if err == nil {
t.Errorf("Expected error when getting nonexistent secret")
}
// Test getting secret with encoded name
encodedSecretName := "path/to/secret"
encodedSecretValue := []byte("encoded secret value")
err = vault.AddSecret(encodedSecretName, encodedSecretValue, false)
if err != nil {
t.Fatalf("Failed to add encoded secret: %v", err)
}
retrievedEncodedValue, err := vault.GetSecret(encodedSecretName)
if err != nil {
t.Fatalf("Failed to get encoded secret: %v", err)
}
if string(retrievedEncodedValue) != string(encodedSecretValue) {
t.Errorf("Expected encoded secret value '%s', got '%s'", string(encodedSecretValue), string(retrievedEncodedValue))
}
}
func TestListSecrets(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create vault and set up long-term key
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
err = setupVaultWithLongTermKey(fs, vault)
if err != nil {
t.Fatalf("Failed to setup vault with long-term key: %v", err)
}
// Initially no secrets
secrets, err := vault.ListSecrets()
if err != nil {
t.Fatalf("Failed to list secrets: %v", err)
}
if len(secrets) != 0 {
t.Errorf("Expected no secrets initially, got %d", len(secrets))
}
// Add multiple secrets
secretNames := []string{"secret1", "secret2", "path/to/secret3"}
for _, name := range secretNames {
err := vault.AddSecret(name, []byte("value for "+name), false)
if err != nil {
t.Fatalf("Failed to add secret %s: %v", name, err)
}
}
// List secrets
secrets, err = vault.ListSecrets()
if err != nil {
t.Fatalf("Failed to list secrets: %v", err)
}
if len(secrets) != len(secretNames) {
t.Errorf("Expected %d secrets, got %d", len(secretNames), len(secrets))
}
// Check that all added secrets are in the list (names should be decoded)
secretMap := make(map[string]bool)
for _, secret := range secrets {
secretMap[secret] = true
}
for _, name := range secretNames {
if !secretMap[name] {
t.Errorf("Expected secret '%s' in list", name)
}
}
}
func TestGetSecretMetadata(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create vault and set up long-term key
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
err = setupVaultWithLongTermKey(fs, vault)
if err != nil {
t.Fatalf("Failed to setup vault with long-term key: %v", err)
}
// Add a secret
secretName := "test-secret"
secretValue := []byte("super secret value")
beforeAdd := time.Now()
err = vault.AddSecret(secretName, secretValue, false)
if err != nil {
t.Fatalf("Failed to add secret: %v", err)
}
afterAdd := time.Now()
// Get secret object and its metadata
secretObj, err := vault.GetSecretObject(secretName)
if err != nil {
t.Fatalf("Failed to get secret object: %v", err)
}
metadata := secretObj.GetMetadata()
if metadata.Name != secretName {
t.Errorf("Expected metadata name '%s', got '%s'", secretName, metadata.Name)
}
// Check that timestamps are reasonable
if metadata.CreatedAt.Before(beforeAdd) || metadata.CreatedAt.After(afterAdd) {
t.Errorf("CreatedAt timestamp is out of expected range")
}
if metadata.UpdatedAt.Before(beforeAdd) || metadata.UpdatedAt.After(afterAdd) {
t.Errorf("UpdatedAt timestamp is out of expected range")
}
// Test getting metadata for nonexistent secret
_, err = vault.GetSecretObject("nonexistent-secret")
if err == nil {
t.Errorf("Expected error when getting secret object for nonexistent secret")
}
}
func TestListUnlockKeys(t *testing.T) {
fs, cleanup := setupTestEnvironment(t)
defer cleanup()
stateDir := "/test-secret-state"
// Create vault
vault, err := CreateVault(fs, stateDir, "test-vault")
if err != nil {
t.Fatalf("Failed to create vault: %v", err)
}
// Initially no unlock keys
keys, err := vault.ListUnlockKeys()
if err != nil {
t.Fatalf("Failed to list unlock keys: %v", err)
}
if len(keys) != 0 {
t.Errorf("Expected no unlock keys initially, got %d", len(keys))
}
}
// setupVaultWithLongTermKey sets up a vault with a long-term public key for testing
func setupVaultWithLongTermKey(fs afero.Fs, vault *Vault) error {
// This simulates what happens during vault initialization
// We derive a long-term keypair from the test mnemonic
ltIdentity, err := vault.deriveLongTermIdentity()
if err != nil {
return err
}
// Store the long-term public key in the vault
vaultDir, err := vault.GetDirectory()
if err != nil {
return err
}
ltPubKey := ltIdentity.Recipient().String()
return afero.WriteFile(fs, filepath.Join(vaultDir, "pub.age"), []byte(ltPubKey), 0600)
}
// deriveLongTermIdentity is a helper method to derive the long-term identity for testing
func (v *Vault) deriveLongTermIdentity() (*age.X25519Identity, error) {
// Use agehd.DeriveIdentity with the test mnemonic
return agehd.DeriveIdentity(testMnemonic, 0)
}