1
0
forked from sneak/secret
secret/internal/secret/passphrase_test.go
sneak c450e1c13d fix: replace remaining os.Setenv with t.Setenv in tests
Replace all os.Setenv calls with t.Setenv in test functions to ensure
proper test environment cleanup and better test isolation. This leaves
only legitimate application code and helper functions using os.Setenv.
2025-06-20 09:22:01 -07:00

183 lines
5.6 KiB
Go

package secret_test
import (
"os"
"path/filepath"
"testing"
"time"
"filippo.io/age"
"git.eeqj.de/sneak/secret/internal/secret"
"git.eeqj.de/sneak/secret/pkg/agehd"
"github.com/spf13/afero"
)
func TestPassphraseUnlockerWithRealFS(t *testing.T) {
// This test uses real filesystem
if os.Getenv("CI") == "true" {
t.Log("Running in CI environment with real filesystem")
}
// Create a temporary directory for our tests
tempDir, err := os.MkdirTemp("", "secret-passphrase-test-")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir) // Clean up after test
// Use the real filesystem
fs := afero.NewOsFs()
// Test data
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
testPassphrase := "test-passphrase-123"
// Create the directory structure
unlockerDir := filepath.Join(tempDir, "unlocker")
if err := os.MkdirAll(unlockerDir, secret.DirPerms); err != nil {
t.Fatalf("Failed to create unlocker directory: %v", err)
}
// Set up test metadata
metadata := secret.UnlockerMetadata{
Type: "passphrase",
CreatedAt: time.Now(),
Flags: []string{},
}
// Create passphrase unlocker
unlocker := secret.NewPassphraseUnlocker(fs, unlockerDir, metadata)
// Generate a test age identity
ageIdentity, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("Failed to generate age identity: %v", err)
}
agePrivateKey := ageIdentity.String()
agePublicKey := ageIdentity.Recipient().String()
// Test writing public key
t.Run("WritePublicKey", func(t *testing.T) {
pubKeyPath := filepath.Join(unlockerDir, "pub.age")
if err := afero.WriteFile(fs, pubKeyPath, []byte(agePublicKey), secret.FilePerms); err != nil {
t.Fatalf("Failed to write public key: %v", err)
}
// Verify the file exists
exists, err := afero.Exists(fs, pubKeyPath)
if err != nil {
t.Fatalf("Failed to check if public key exists: %v", err)
}
if !exists {
t.Errorf("Public key file should exist at %s", pubKeyPath)
}
})
// Test encrypting private key with passphrase
t.Run("EncryptPrivateKey", func(t *testing.T) {
privKeyData := []byte(agePrivateKey)
encryptedPrivKey, err := secret.EncryptWithPassphrase(privKeyData, testPassphrase)
if err != nil {
t.Fatalf("Failed to encrypt private key: %v", err)
}
privKeyPath := filepath.Join(unlockerDir, "priv.age")
if err := afero.WriteFile(fs, privKeyPath, encryptedPrivKey, secret.FilePerms); err != nil {
t.Fatalf("Failed to write encrypted private key: %v", err)
}
// Verify the file exists
exists, err := afero.Exists(fs, privKeyPath)
if err != nil {
t.Fatalf("Failed to check if private key exists: %v", err)
}
if !exists {
t.Errorf("Encrypted private key file should exist at %s", privKeyPath)
}
})
// Test writing long-term key
t.Run("WriteLongTermKey", func(t *testing.T) {
// Derive a long-term identity from the test mnemonic
ltIdentity, err := agehd.DeriveIdentity(testMnemonic, 0)
if err != nil {
t.Fatalf("Failed to derive long-term identity: %v", err)
}
// Encrypt long-term private key to the unlocker's recipient
recipient, err := age.ParseX25519Recipient(agePublicKey)
if err != nil {
t.Fatalf("Failed to parse recipient: %v", err)
}
ltPrivKeyData := []byte(ltIdentity.String())
encryptedLtPrivKey, err := secret.EncryptToRecipient(ltPrivKeyData, recipient)
if err != nil {
t.Fatalf("Failed to encrypt long-term private key: %v", err)
}
ltPrivKeyPath := filepath.Join(unlockerDir, "longterm.age")
if err := afero.WriteFile(fs, ltPrivKeyPath, encryptedLtPrivKey, secret.FilePerms); err != nil {
t.Fatalf("Failed to write encrypted long-term private key: %v", err)
}
// Verify the file exists
exists, err := afero.Exists(fs, ltPrivKeyPath)
if err != nil {
t.Fatalf("Failed to check if long-term key exists: %v", err)
}
if !exists {
t.Errorf("Encrypted long-term key file should exist at %s", ltPrivKeyPath)
}
})
// Set test environment variable (cleaned up automatically)
t.Setenv(secret.EnvUnlockPassphrase, testPassphrase)
// Test getting identity from environment variable
t.Run("GetIdentityFromEnv", func(t *testing.T) {
identity, err := unlocker.GetIdentity()
if err != nil {
t.Fatalf("Failed to get identity from env: %v", err)
}
// Verify the identity matches what we expect
expectedPubKey := ageIdentity.Recipient().String()
actualPubKey := identity.Recipient().String()
if actualPubKey != expectedPubKey {
t.Errorf("Public key mismatch. Expected %s, got %s", expectedPubKey, actualPubKey)
}
})
// Unset the environment variable to test interactive prompt
os.Unsetenv(secret.EnvUnlockPassphrase)
// Test getting identity from prompt (this would require mocking the prompt)
// For real integration tests, we'd need to provide a way to mock the passphrase input
// Here we'll just verify the error is what we expect when no passphrase is available
t.Run("GetIdentityWithoutEnv", func(t *testing.T) {
// This should fail since we're not in an interactive terminal
_, err := unlocker.GetIdentity()
if err == nil {
t.Errorf("Should have failed to get identity without passphrase env var")
}
})
// Test removing the unlocker
t.Run("RemoveUnlocker", func(t *testing.T) {
err := unlocker.Remove()
if err != nil {
t.Fatalf("Failed to remove unlocker: %v", err)
}
// Verify the directory is gone
exists, err := afero.DirExists(fs, unlockerDir)
if err != nil {
t.Fatalf("Failed to check if unlocker directory exists: %v", err)
}
if exists {
t.Errorf("Unlocker directory should not exist after removal")
}
})
}