package auth import ( "os/exec" "strings" "testing" "github.com/jfraeys/fetch_ml/internal/auth" ) // isKeyringAvailable checks if dbus is available for keyring operations func isKeyringAvailable() bool { // Check if dbus-launch is available (Linux keyring requirement) if _, err := exec.LookPath("dbus-launch"); err != nil { return false } return true } func TestNewKeychainManager(t *testing.T) { t.Parallel() km := auth.NewKeychainManager() if km == nil { t.Fatal("NewKeychainManager returned nil") } methods := km.ListAvailableMethods() if len(methods) == 0 { t.Error("Expected at least one available method") } } func TestKeychainIsAvailable(t *testing.T) { t.Parallel() // Enable parallel execution km := auth.NewKeychainManager() // IsAvailable should return a boolean without error available := km.IsAvailable() // We can't predict the result since it depends on the test environment, // but it should not panic t.Logf("Keychain availability: %v", available) } func TestKeychainBasicOperations(t *testing.T) { t.Parallel() if !isKeyringAvailable() { t.Skip("Skipping: dbus-launch not available for keyring operations") } km := auth.NewKeychainManager() service := "test-service" account := "test-account" secret := "test-secret" // Test storing API key if err := km.StoreAPIKey(service, account, secret); err != nil { t.Fatalf("StoreAPIKey failed: %v", err) } // Test retrieving API key retrieved, err := km.GetAPIKey(service, account) if err != nil { t.Fatalf("GetAPIKey failed: %v", err) } if retrieved != secret { t.Errorf("Expected secret %s, got %s", secret, retrieved) } // Test deleting API key if err := km.DeleteAPIKey(service, account); err != nil { t.Fatalf("DeleteAPIKey failed: %v", err) } // Verify deletion - should fail to retrieve _, err = km.GetAPIKey(service, account) if err == nil { t.Error("Expected error when retrieving deleted key") } } func TestKeychainListMethods(t *testing.T) { t.Parallel() // Enable parallel execution km := auth.NewKeychainManager() methods := km.ListAvailableMethods() if len(methods) == 0 { t.Error("Expected at least one available method") } // Check that fallback method is always included hasFallback := false for _, method := range methods { if method == "OS keyring" { // OS keyring might be available continue } if len(method) > 0 { hasFallback = true break } } if !hasFallback { t.Error("Expected fallback method to be available") } t.Logf("Available methods: %v", methods) } func TestKeychainErrorHandling(t *testing.T) { t.Parallel() // Enable parallel execution km := auth.NewKeychainManager() // Test getting non-existent key _, err := km.GetAPIKey("non-existent", "non-existent") if err == nil { t.Error("Expected error when getting non-existent key") } // Test deleting non-existent key (should not error) if err := km.DeleteAPIKey("non-existent", "non-existent"); err != nil { t.Errorf("DeleteAPIKey should not error for non-existent key: %v", err) } } func TestKeychainMultipleKeys(t *testing.T) { t.Parallel() if !isKeyringAvailable() { t.Skip("Skipping: dbus-launch not available for keyring operations") } km := auth.NewKeychainManager() keys := map[string]string{ "service1:account1": "secret1", "service1:account2": "secret2", "service2:account1": "secret3", } // Store multiple keys for serviceAccount, secret := range keys { parts := strings.SplitN(serviceAccount, ":", 2) service, account := parts[0], parts[1] if err := km.StoreAPIKey(service, account, secret); err != nil { t.Fatalf("StoreAPIKey failed for %s: %v", serviceAccount, err) } } // Retrieve and verify all keys for serviceAccount, expectedSecret := range keys { parts := strings.SplitN(serviceAccount, ":", 2) service, account := parts[0], parts[1] retrieved, err := km.GetAPIKey(service, account) if err != nil { t.Fatalf("GetAPIKey failed for %s: %v", serviceAccount, err) } if retrieved != expectedSecret { t.Errorf("Expected secret %s for %s, got %s", expectedSecret, serviceAccount, retrieved) } // Clean up each key if err := km.DeleteAPIKey(service, account); err != nil { t.Fatalf("DeleteAPIKey failed for %s: %v", serviceAccount, err) } } }