//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&ersand", // 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") }