diff --git a/frontend/src/composables/__tests__/antigravityMappings.retry.spec.ts b/frontend/src/composables/__tests__/antigravityMappings.retry.spec.ts new file mode 100644 index 0000000000..4f19e685b8 --- /dev/null +++ b/frontend/src/composables/__tests__/antigravityMappings.retry.spec.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const getMapping = vi.hoisted(() => vi.fn()) +vi.mock('@/api/admin/accounts', () => ({ getAntigravityDefaultModelMapping: getMapping })) + +beforeEach(() => { + vi.resetModules() + getMapping.mockReset() + vi.spyOn(console, 'warn').mockImplementation(() => {}) +}) +afterEach(() => vi.restoreAllMocks()) + +describe('Antigravity default mapping cache', () => { + it('retries after a failed fetch rather than caching the empty fallback', async () => { + getMapping.mockRejectedValueOnce(new Error('offline')).mockResolvedValueOnce({ alias: 'upstream-model' }) + const { fetchAntigravityDefaultMappings: fetch } = await import('../useModelWhitelist') + expect(await fetch()).toEqual([]) + expect(await fetch()).toEqual([{ from: 'alias', to: 'upstream-model' }]) + expect(getMapping).toHaveBeenCalledTimes(2) + }) + + it('caches a successful mapping', async () => { + getMapping.mockResolvedValue({ alias: 'upstream-model' }) + const { fetchAntigravityDefaultMappings: fetch } = await import('../useModelWhitelist') + expect(await fetch()).toEqual([{ from: 'alias', to: 'upstream-model' }]) + expect(await fetch()).toEqual([{ from: 'alias', to: 'upstream-model' }]) + expect(getMapping).toHaveBeenCalledTimes(1) + }) + + it('still caches a successful empty mapping', async () => { + getMapping.mockResolvedValue({}) + const { fetchAntigravityDefaultMappings: fetch } = await import('../useModelWhitelist') + expect(await fetch()).toEqual([]) + expect(await fetch()).toEqual([]) + expect(getMapping).toHaveBeenCalledTimes(1) + }) + + it('does not erase a successful concurrent result when an older request fails', async () => { + let reject!: (error: Error) => void + getMapping.mockImplementationOnce(() => new Promise((_, fail) => { reject = fail })) + .mockResolvedValueOnce({ alias: 'upstream-model' }) + const { fetchAntigravityDefaultMappings: fetch } = await import('../useModelWhitelist') + const older = fetch() + await fetch() + reject(new Error('offline')) + expect(await older).toEqual([]) + expect(await fetch()).toEqual([{ from: 'alias', to: 'upstream-model' }]) + expect(getMapping).toHaveBeenCalledTimes(2) + }) +}) diff --git a/frontend/src/composables/useModelWhitelist.ts b/frontend/src/composables/useModelWhitelist.ts index 80696b28f8..0b312d21bf 100644 --- a/frontend/src/composables/useModelWhitelist.ts +++ b/frontend/src/composables/useModelWhitelist.ts @@ -413,7 +413,7 @@ export async function fetchAntigravityDefaultMappings(): Promise<{ from: string; _antigravityDefaultMappingsCache = Object.entries(mapping).map(([from, to]) => ({ from, to })) } catch (e) { console.warn('[fetchAntigravityDefaultMappings] API failed, using empty fallback', e) - _antigravityDefaultMappingsCache = [] + return [] } return _antigravityDefaultMappingsCache }