import type { TokenStore } from './token-store' import { describe, expect, it, vi } from 'vitest' import { detectTokenStore, getTokenStore } from './manager' function memStore(label: string): TokenStore & { _label: string } { const map = new Map() const k = (h: string, e: string): string => `${h} ${e}` return { _label: label, async read(host: string, email: string): Promise { return map.get(k(host, email)) ?? '' }, async write(host: string, email: string, bearer: string): Promise { map.set(k(host, email), bearer) }, async remove(host: string, email: string): Promise { map.delete(k(host, email)) }, } } describe('detectTokenStore', () => { it('returns keychain store when probe succeeds', async () => { const k = memStore('keyring') const f = memStore('file') const result = await detectTokenStore({ factory: { keyring: () => k, file: () => f }, }) expect(result.mode).toBe('keychain') expect(result.store).toBe(k) }) it('falls back to file when keyring set throws', async () => { const k = memStore('keyring') const f = memStore('file') k.write = vi.fn(() => { throw new Error('locked') }) as TokenStore['write'] const result = await detectTokenStore({ factory: { keyring: () => k, file: () => f }, }) expect(result.mode).toBe('file') expect(result.store).toBe(f) }) it('falls back to file when probe round-trip mismatches', async () => { const k = memStore('keyring') const f = memStore('file') k.read = vi.fn(async () => 'something-else') as TokenStore['read'] const result = await detectTokenStore({ factory: { keyring: () => k, file: () => f }, }) expect(result.mode).toBe('file') expect(result.store).toBe(f) }) it('falls back to file when keyring constructor throws', async () => { const f = memStore('file') const result = await detectTokenStore({ factory: { keyring: () => { throw new Error('no backend') }, file: () => f, }, }) expect(result.mode).toBe('file') expect(result.store).toBe(f) }) it('cleans up probe entry after successful probe', async () => { const k = memStore('keyring') const f = memStore('file') await detectTokenStore({ factory: { keyring: () => k, file: () => f }, }) expect(await k.read('__difyctl_probe__', '__difyctl_probe__')).toBe('') }) it('removes the probe entry even when the probe read throws', async () => { const k = memStore('keyring') const f = memStore('file') const removeSpy = vi.spyOn(k, 'remove') k.read = vi.fn(() => { throw new Error('read boom') }) as TokenStore['read'] const result = await detectTokenStore({ factory: { keyring: () => k, file: () => f }, }) expect(removeSpy).toHaveBeenCalledWith('__difyctl_probe__', '__difyctl_probe__') expect(result.mode).toBe('file') expect(result.store).toBe(f) }) }) describe('getTokenStore', () => { it('constructs the keychain backend without probing when mode is keychain', () => { const k = memStore('keyring') const f = memStore('file') k.write = vi.fn(() => { throw new Error('probe must never run on the read path') }) as TokenStore['write'] const store = getTokenStore('keychain', { factory: { keyring: () => k, file: () => f }, }) expect(store).toBe(k) }) it('constructs the file backend when mode is file, never touching the keyring', () => { const keyringFactory = vi.fn(() => memStore('keyring')) const f = memStore('file') const store = getTokenStore('file', { factory: { keyring: keyringFactory, file: () => f }, }) expect(store).toBe(f) expect(keyringFactory).not.toHaveBeenCalled() }) })