mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
fix(providers): retry initial empty quota before showing feedback
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { RefreshQuotaResult } from '@/api/endpoints/keys'
|
||||
import { refreshQuotaInBackground } from '../refreshQuotaInBackground'
|
||||
|
||||
function emptyQuota(status: 'error' | 'no_metadata' | 'forbidden' = 'no_metadata'): RefreshQuotaResult {
|
||||
return {
|
||||
success: 0,
|
||||
failed: 1,
|
||||
total: 1,
|
||||
results: [{ key_id: 'key-1', key_name: 'account', status }],
|
||||
}
|
||||
}
|
||||
|
||||
const readyQuota: RefreshQuotaResult = {
|
||||
success: 1,
|
||||
failed: 0,
|
||||
total: 1,
|
||||
results: [{ key_id: 'key-1', key_name: 'account', status: 'success' }],
|
||||
}
|
||||
|
||||
afterEach(() => vi.useRealTimers())
|
||||
|
||||
describe('initial background quota refresh', () => {
|
||||
it('waits and retries an initial empty response once', async () => {
|
||||
vi.useFakeTimers()
|
||||
const refresh = vi.fn().mockResolvedValueOnce(emptyQuota()).mockResolvedValueOnce(readyQuota)
|
||||
const pending = refreshQuotaInBackground({ refresh, isCurrent: () => true, retryInitialEmptyQuota: true })
|
||||
await vi.advanceTimersByTimeAsync(999)
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
expect(await pending).toBe(readyQuota)
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('returns persistent failures after at most one retry', async () => {
|
||||
vi.useFakeTimers()
|
||||
const result = emptyQuota('error')
|
||||
const refresh = vi.fn().mockResolvedValue(result)
|
||||
const pending = refreshQuotaInBackground({ refresh, isCurrent: () => true, retryInitialEmptyQuota: true })
|
||||
await vi.runAllTimersAsync()
|
||||
expect(await pending).toBe(result)
|
||||
expect(refresh).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it.each([readyQuota, emptyQuota('forbidden'), {
|
||||
...emptyQuota('error'),
|
||||
results: [{ ...emptyQuota('error').results[0]!, status_code: 401 }],
|
||||
}])('does not retry successful or rejected authorization results', async (result) => {
|
||||
const refresh = vi.fn().mockResolvedValue(result)
|
||||
expect(await refreshQuotaInBackground({ refresh, isCurrent: () => true, retryInitialEmptyQuota: true })).toBe(result)
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not retry when initial quota retry is disabled', async () => {
|
||||
const refresh = vi.fn().mockResolvedValue(emptyQuota())
|
||||
await refreshQuotaInBackground({ refresh, isCurrent: () => true, retryInitialEmptyQuota: false })
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('cancels the retry when the drawer closes or switches providers', async () => {
|
||||
vi.useFakeTimers()
|
||||
let current = true
|
||||
const refresh = vi.fn().mockResolvedValue(emptyQuota())
|
||||
const pending = refreshQuotaInBackground({ refresh, isCurrent: () => current, retryInitialEmptyQuota: true })
|
||||
await vi.advanceTimersByTimeAsync(1)
|
||||
current = false
|
||||
await vi.runAllTimersAsync()
|
||||
expect(await pending).toBeNull()
|
||||
expect(refresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('discards an in-flight response from a closed drawer', async () => {
|
||||
let current = true
|
||||
const refresh = vi.fn(async () => {
|
||||
current = false
|
||||
return readyQuota
|
||||
})
|
||||
expect(await refreshQuotaInBackground({ refresh, isCurrent: () => current, retryInitialEmptyQuota: true })).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { RefreshQuotaResult } from '@/api/endpoints/keys'
|
||||
|
||||
interface BackgroundQuotaRefreshOptions {
|
||||
refresh: () => Promise<RefreshQuotaResult>
|
||||
isCurrent: () => boolean
|
||||
retryInitialEmptyQuota: boolean
|
||||
}
|
||||
|
||||
function shouldRetryEmptyQuota(result: RefreshQuotaResult): boolean {
|
||||
return result.success === 0
|
||||
&& result.results.length > 0
|
||||
&& result.results.every(item =>
|
||||
(item.status === 'no_metadata' || item.status === 'error')
|
||||
&& item.status_code !== 401
|
||||
&& item.status_code !== 403
|
||||
&& !item.metadata
|
||||
&& !item.quota_snapshot,
|
||||
)
|
||||
}
|
||||
|
||||
export async function refreshQuotaInBackground({
|
||||
refresh,
|
||||
isCurrent,
|
||||
retryInitialEmptyQuota,
|
||||
}: BackgroundQuotaRefreshOptions): Promise<RefreshQuotaResult | null> {
|
||||
if (!isCurrent()) return null
|
||||
const result = await refresh()
|
||||
if (!isCurrent()) return null
|
||||
if (!retryInitialEmptyQuota || !shouldRetryEmptyQuota(result)) return result
|
||||
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 1000))
|
||||
if (!isCurrent()) return null
|
||||
const retried = await refresh()
|
||||
return isCurrent() ? retried : null
|
||||
}
|
||||
Reference in New Issue
Block a user