Files
orca/src/main/codex/codex-app-server-capability-cache.test.ts
T

175 lines
5.8 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import {
CODEX_APP_SERVER_CAPABILITY_RETRY_INTERVAL_MS,
CodexAppServerCapabilityCache,
getCodexAppServerHostKey
} from './codex-app-server-capability-cache'
const unsupportedError = new Error('unsupported')
const isUnsupported = (error: unknown): boolean => error === unsupportedError
describe('CodexAppServerCapabilityCache', () => {
it('retries a host after the compatibility interval', () => {
const cache = new CodexAppServerCapabilityCache()
cache.rememberUnsupported('native', 1_000)
expect(
cache.shouldTry('native', 1_000 + CODEX_APP_SERVER_CAPABILITY_RETRY_INTERVAL_MS - 1)
).toBe(false)
expect(cache.shouldTry('native', 1_000 + CODEX_APP_SERVER_CAPABILITY_RETRY_INTERVAL_MS)).toBe(
true
)
})
it('falls back on the first unsupported probe and skips the probe on later calls', () => {
const cache = new CodexAppServerCapabilityCache()
const firstPreferred = vi.fn(() => {
throw unsupportedError
})
expect(
cache.runWithFallbackSync('native', firstPreferred, () => 'first-fallback', isUnsupported, 5)
).toBe('first-fallback')
expect(firstPreferred).toHaveBeenCalledTimes(1)
// Why: probes are synchronous on the main thread, so they can never
// overlap — back-to-back calls inside the retry window are the
// "concurrent probe" equivalent and must share the first probe's result.
const laterPreferred = vi.fn(() => 'unexpected-preferred')
expect(
cache.runWithFallbackSync('native', laterPreferred, () => 'cached-fallback', isUnsupported, 6)
).toBe('cached-fallback')
expect(
cache.runWithFallbackSync('native', laterPreferred, () => 'cached-fallback', isUnsupported, 7)
).toBe('cached-fallback')
expect(laterPreferred).not.toHaveBeenCalled()
})
it('isolates capability state per execution host', () => {
const cache = new CodexAppServerCapabilityCache()
cache.rememberUnsupported('wsl:Ubuntu', 1_000)
expect(cache.shouldTry('wsl:Ubuntu', 1_001)).toBe(false)
expect(cache.shouldTry('native', 1_001)).toBe(true)
expect(cache.shouldTry('wsl:Debian', 1_001)).toBe(true)
const nativePreferred = vi.fn(() => 'native-result')
expect(
cache.runWithFallbackSync('native', nativePreferred, () => 'unexpected', isUnsupported, 1_001)
).toBe('native-result')
expect(nativePreferred).toHaveBeenCalledTimes(1)
})
it('drops known support when a later call reports the capability unsupported', () => {
const cache = new CodexAppServerCapabilityCache()
expect(
cache.runWithFallbackSync(
'native',
() => 'supported',
() => 'unexpected',
isUnsupported,
1
)
).toBe('supported')
expect(cache.isKnownSupported('native')).toBe(true)
expect(
cache.runWithFallbackSync(
'native',
() => {
throw unsupportedError
},
() => 'fallback',
isUnsupported,
2
)
).toBe('fallback')
expect(cache.isKnownSupported('native')).toBe(false)
const laterPreferred = vi.fn(() => 'unexpected-preferred')
expect(
cache.runWithFallbackSync('native', laterPreferred, () => 'cached-fallback', isUnsupported, 3)
).toBe('cached-fallback')
expect(laterPreferred).not.toHaveBeenCalled()
})
it('rethrows transient errors without marking the host unsupported', () => {
const cache = new CodexAppServerCapabilityCache()
const transient = new Error('spawn ETIMEDOUT')
expect(() =>
cache.runWithFallbackSync(
'native',
() => {
throw transient
},
() => 'unexpected-fallback',
isUnsupported,
1
)
).toThrow(transient)
expect(cache.shouldTry('native', 2)).toBe(true)
})
it('coalesces concurrent unsupported async probes per host', async () => {
const cache = new CodexAppServerCapabilityCache()
let rejectProbe!: (error: Error) => void
const preferred = vi.fn(
() =>
new Promise<string>((_resolve, reject) => {
rejectProbe = reject
})
)
const first = cache.runWithFallbackAsync(
'native',
preferred,
() => 'first-fallback',
isUnsupported,
5
)
const second = cache.runWithFallbackAsync(
'native',
preferred,
() => 'second-fallback',
isUnsupported,
5
)
await Promise.resolve()
expect(preferred).toHaveBeenCalledTimes(1)
rejectProbe(unsupportedError)
await expect(first).resolves.toBe('first-fallback')
await expect(second).resolves.toBe('second-fallback')
expect(preferred).toHaveBeenCalledTimes(1)
})
it('lets concurrent callers run their own work once the async probe succeeds', async () => {
const cache = new CodexAppServerCapabilityCache()
let resolveProbe!: (value: string) => void
const preferred = vi
.fn<() => Promise<string>>()
.mockImplementationOnce(
() =>
new Promise<string>((resolve) => {
resolveProbe = resolve
})
)
.mockResolvedValueOnce('second-result')
const first = cache.runWithFallbackAsync('native', preferred, () => 'fallback', isUnsupported)
const second = cache.runWithFallbackAsync('native', preferred, () => 'fallback', isUnsupported)
await Promise.resolve()
expect(preferred).toHaveBeenCalledTimes(1)
resolveProbe('first-result')
await expect(first).resolves.toBe('first-result')
await expect(second).resolves.toBe('second-result')
expect(preferred).toHaveBeenCalledTimes(2)
})
it('builds host keys that keep WSL distros apart', () => {
expect(getCodexAppServerHostKey({ kind: 'native' })).toBe('native')
expect(getCodexAppServerHostKey({ kind: 'wsl', distro: 'Ubuntu' })).toBe('wsl:Ubuntu')
expect(getCodexAppServerHostKey({ kind: 'wsl', distro: 'Debian' })).toBe('wsl:Debian')
})
})