secret/internal/secret/keychainunlocker_test.go
sneak 7264026d66 Fix unlocker rm to succeed when keychain item is missing
When removing a keychain unlocker, if the keychain item doesn't exist
(e.g., already manually deleted or vault synced from another machine),
the removal should still succeed since the goal is to remove the
unlocker and the keychain item being gone already satisfies that goal.
2025-12-23 14:14:14 +07:00

185 lines
5.6 KiB
Go

//go:build darwin
// +build darwin
package secret
import (
"encoding/hex"
"runtime"
"testing"
"github.com/awnumar/memguard"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestKeychainStoreRetrieveDelete(t *testing.T) {
// Skip test if not on macOS
if runtime.GOOS != "darwin" {
t.Skip("Keychain tests only run on macOS")
}
// Test data
testItemName := "test-secret-keychain-item"
testData := "test-secret-data-12345"
testBuffer := memguard.NewBufferFromBytes([]byte(testData))
defer testBuffer.Destroy()
// Clean up any existing item first
_ = deleteFromKeychain(testItemName)
// Test 1: Store data in keychain
err := storeInKeychain(testItemName, testBuffer)
require.NoError(t, err, "Failed to store data in keychain")
// Test 2: Retrieve data from keychain
retrievedData, err := retrieveFromKeychain(testItemName)
require.NoError(t, err, "Failed to retrieve data from keychain")
assert.Equal(t, testData, string(retrievedData), "Retrieved data doesn't match stored data")
// Test 3: Update existing item (store again with different data)
newTestData := "updated-test-data-67890"
newTestBuffer := memguard.NewBufferFromBytes([]byte(newTestData))
defer newTestBuffer.Destroy()
err = storeInKeychain(testItemName, newTestBuffer)
require.NoError(t, err, "Failed to update data in keychain")
// Verify updated data
retrievedData, err = retrieveFromKeychain(testItemName)
require.NoError(t, err, "Failed to retrieve updated data from keychain")
assert.Equal(t, newTestData, string(retrievedData), "Retrieved data doesn't match updated data")
// Test 4: Delete from keychain
err = deleteFromKeychain(testItemName)
require.NoError(t, err, "Failed to delete data from keychain")
// Test 5: Verify item is deleted (should fail to retrieve)
_, err = retrieveFromKeychain(testItemName)
assert.Error(t, err, "Expected error when retrieving deleted item")
}
func TestKeychainInvalidItemName(t *testing.T) {
// Skip test if not on macOS
if runtime.GOOS != "darwin" {
t.Skip("Keychain tests only run on macOS")
}
testData := memguard.NewBufferFromBytes([]byte("test"))
defer testData.Destroy()
// Test invalid item names
invalidNames := []string{
"", // Empty name
"test space", // Contains space
"test/slash", // Contains slash
"test\\backslash", // Contains backslash
"test:colon", // Contains colon
"test;semicolon", // Contains semicolon
"test|pipe", // Contains pipe
"test@at", // Contains @
"test#hash", // Contains #
"test$dollar", // Contains $
"test&ampersand", // Contains &
"test*asterisk", // Contains *
"test?question", // Contains ?
"test!exclamation", // Contains !
"test'quote", // Contains single quote
"test\"doublequote", // Contains double quote
"test(paren", // Contains parenthesis
"test[bracket", // Contains bracket
}
for _, name := range invalidNames {
err := storeInKeychain(name, testData)
assert.Error(t, err, "Expected error for invalid name: %s", name)
assert.Contains(t, err.Error(), "invalid keychain item name", "Error should mention invalid name for: %s", name)
}
// Test valid names (should not error on validation)
validNames := []string{
"test-name",
"test_name",
"test.name",
"TestName123",
"TEST_NAME_123",
"com.example.test",
"secret-vault-hostname-2024-01-01",
}
for _, name := range validNames {
err := validateKeychainItemName(name)
assert.NoError(t, err, "Expected no error for valid name: %s", name)
// Clean up
_ = deleteFromKeychain(name)
}
}
func TestKeychainNilData(t *testing.T) {
// Skip test if not on macOS
if runtime.GOOS != "darwin" {
t.Skip("Keychain tests only run on macOS")
}
// Test storing nil data
err := storeInKeychain("test-item", nil)
assert.Error(t, err, "Expected error when storing nil data")
assert.Contains(t, err.Error(), "data buffer is nil")
}
func TestKeychainLargeData(t *testing.T) {
// Skip test if not on macOS
if runtime.GOOS != "darwin" {
t.Skip("Keychain tests only run on macOS")
}
// Test with larger hex-encoded data (512 bytes of binary data = 1KB hex)
largeData := make([]byte, 512)
for i := range largeData {
largeData[i] = byte(i % 256)
}
// Convert to hex string for storage
hexData := hex.EncodeToString(largeData)
testItemName := "test-large-data"
testBuffer := memguard.NewBufferFromBytes([]byte(hexData))
defer testBuffer.Destroy()
// Clean up first
_ = deleteFromKeychain(testItemName)
// Store hex data
err := storeInKeychain(testItemName, testBuffer)
require.NoError(t, err, "Failed to store large data")
// Retrieve and verify
retrievedData, err := retrieveFromKeychain(testItemName)
require.NoError(t, err, "Failed to retrieve large data")
// Decode hex and compare
decodedData, err := hex.DecodeString(string(retrievedData))
require.NoError(t, err, "Failed to decode hex data")
assert.Equal(t, largeData, decodedData, "Large data mismatch")
// Clean up
_ = deleteFromKeychain(testItemName)
}
func TestDeleteNonExistentKeychainItem(t *testing.T) {
// Skip test if not on macOS
if runtime.GOOS != "darwin" {
t.Skip("Keychain tests only run on macOS")
}
// Ensure item doesn't exist
testItemName := "test-nonexistent-keychain-item-12345"
_ = deleteFromKeychain(testItemName)
// Deleting a non-existent item should NOT return an error
// This is important for cleaning up unlocker directories when the keychain item
// has already been removed (e.g., manually by user, or on a different machine)
err := deleteFromKeychain(testItemName)
assert.NoError(t, err, "Deleting non-existent keychain item should not return an error")
}