package auth import ( "errors" "os" "path/filepath" "testing" "github.com/zalando/go-keyring" ) type fakeKeyring struct { secrets map[string]string setErr error getErr error deleteErr error } func newFakeKeyring() *fakeKeyring { return &fakeKeyring{secrets: make(map[string]string)} } func (f *fakeKeyring) Set(service, account, secret string) error { if f.setErr != nil { return f.setErr } f.secrets[key(service, account)] = secret return nil } func (f *fakeKeyring) Get(service, account string) (string, error) { if f.getErr != nil { return "", f.getErr } if secret, ok := f.secrets[key(service, account)]; ok { return secret, nil } return "", keyring.ErrNotFound } func (f *fakeKeyring) Delete(service, account string) error { if f.deleteErr != nil { return f.deleteErr } delete(f.secrets, key(service, account)) return nil } func key(service, account string) string { return service + ":" + account } func newTestManager(t *testing.T, kr systemKeyring) (*KeychainManager, string) { t.Helper() baseDir := t.TempDir() return newKeychainManagerWithKeyring(kr, baseDir), baseDir } func TestKeychainStoreAndGetPrimary(t *testing.T) { kr := newFakeKeyring() km, baseDir := newTestManager(t, kr) if err := km.StoreAPIKey("fetch-ml", "alice", "super-secret"); err != nil { t.Fatalf("StoreAPIKey failed: %v", err) } got, err := km.GetAPIKey("fetch-ml", "alice") if err != nil { t.Fatalf("GetAPIKey failed: %v", err) } if got != "super-secret" { t.Fatalf("expected secret to be stored in primary keyring") } // Ensure fallback file was not created when primary succeeds path := filepath.Join(baseDir, filepath.Base(km.fallback.path("fetch-ml", "alice"))) if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { t.Fatalf("expected no fallback file, got err=%v", err) } } func TestKeychainFallbackWhenUnsupported(t *testing.T) { kr := newFakeKeyring() kr.setErr = keyring.ErrUnsupportedPlatform kr.getErr = keyring.ErrUnsupportedPlatform kr.deleteErr = keyring.ErrUnsupportedPlatform km, _ := newTestManager(t, kr) if err := km.StoreAPIKey("fetch-ml", "bob", "fallback-secret"); err != nil { t.Fatalf("StoreAPIKey should fallback: %v", err) } got, err := km.GetAPIKey("fetch-ml", "bob") if err != nil { t.Fatalf("GetAPIKey should use fallback: %v", err) } if got != "fallback-secret" { t.Fatalf("expected fallback secret, got %s", got) } } func TestKeychainDeleteRemovesFallback(t *testing.T) { kr := newFakeKeyring() kr.deleteErr = keyring.ErrNotFound km, _ := newTestManager(t, kr) if err := km.fallback.store("fetch-ml", "carol", "temp"); err != nil { t.Fatalf("failed to seed fallback store: %v", err) } if err := km.DeleteAPIKey("fetch-ml", "carol"); err != nil { t.Fatalf("DeleteAPIKey failed: %v", err) } if _, err := km.fallback.get("fetch-ml", "carol"); !errors.Is(err, os.ErrNotExist) { t.Fatalf("expected fallback secret removed, err=%v", err) } } func TestListAvailableMethodsIncludesFallback(t *testing.T) { kr := newFakeKeyring() kr.getErr = keyring.ErrUnsupportedPlatform km, _ := newTestManager(t, kr) methods := km.ListAvailableMethods() if len(methods) != 1 || methods[0] == "OS keyring" { t.Fatalf("expected only fallback method, got %v", methods) } }