mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
fix(frontend): synchronize pool account state
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { QuotaWindowSnapshot } from '@/api/endpoints/types'
|
||||
import { resetCodexCycleUsageWindows } from '@/features/pool/utils/poolCycleStats'
|
||||
|
||||
describe('resetCodexCycleUsageWindows', () => {
|
||||
it('mirrors the backend reset scope and creates zero usage for resettable windows', () => {
|
||||
const windows: QuotaWindowSnapshot[] = [
|
||||
{
|
||||
code: '5h',
|
||||
scope: 'account',
|
||||
window_minutes: 300,
|
||||
usage: null,
|
||||
},
|
||||
{
|
||||
code: 'spark_weekly',
|
||||
scope: 'account',
|
||||
window_minutes: 10_080,
|
||||
usage: { request_count: 3, total_tokens: 30, total_cost_usd: '0.03' },
|
||||
},
|
||||
{
|
||||
code: 'model_daily',
|
||||
scope: 'model',
|
||||
window_minutes: 1_440,
|
||||
usage: { request_count: 4, total_tokens: 40, total_cost_usd: '0.04' },
|
||||
},
|
||||
{
|
||||
code: 'lifetime',
|
||||
scope: 'account',
|
||||
window_minutes: 0,
|
||||
usage: { request_count: 5, total_tokens: 50, total_cost_usd: '0.05' },
|
||||
},
|
||||
]
|
||||
|
||||
const result = resetCodexCycleUsageWindows(windows, 123)
|
||||
|
||||
expect(result[0]).toMatchObject({
|
||||
usage_reset_at: 123,
|
||||
usage: { request_count: 0, total_tokens: 0, total_cost_usd: '0.00000000' },
|
||||
})
|
||||
expect(result[1]).toBe(windows[1])
|
||||
expect(result[2]).toBe(windows[2])
|
||||
expect(result[3]).toBe(windows[3])
|
||||
})
|
||||
|
||||
it('treats a missing scope as account and ignores empty codes', () => {
|
||||
const windows: QuotaWindowSnapshot[] = [
|
||||
{ code: 'weekly', usage: { request_count: 7 } },
|
||||
{ code: ' ', scope: 'account', usage: { request_count: 8 } },
|
||||
]
|
||||
|
||||
const result = resetCodexCycleUsageWindows(windows, 456)
|
||||
|
||||
expect(result[0].usage?.request_count).toBe(0)
|
||||
expect(result[0].usage_reset_at).toBe(456)
|
||||
expect(result[1]).toBe(windows[1])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { PoolKeyDetail } from '@/api/endpoints/pool'
|
||||
import type { RefreshQuotaResult } from '@/api/endpoints/keys'
|
||||
import { mergePoolKeyQuotaSnapshots } from '@/features/pool/utils/poolQuotaRefresh'
|
||||
|
||||
function createKey(keyId: string): PoolKeyDetail {
|
||||
return {
|
||||
key_id: keyId,
|
||||
key_name: keyId,
|
||||
is_active: true,
|
||||
auth_type: 'oauth',
|
||||
api_formats: ['openai:responses'],
|
||||
internal_priority: 0,
|
||||
account_quota: null,
|
||||
cooldown_reason: null,
|
||||
cooldown_ttl_seconds: null,
|
||||
cost_window_usage: 0,
|
||||
cost_limit: null,
|
||||
request_count: 0,
|
||||
total_tokens: 0,
|
||||
total_cost_usd: '0',
|
||||
sticky_sessions: 0,
|
||||
lru_score: null,
|
||||
created_at: null,
|
||||
last_used_at: null,
|
||||
}
|
||||
}
|
||||
|
||||
describe('mergePoolKeyQuotaSnapshots', () => {
|
||||
it('merges snapshots from non-success quota results', () => {
|
||||
const keys = [createKey('exhausted'), createKey('invalid'), createKey('unchanged')]
|
||||
const results: RefreshQuotaResult['results'] = [
|
||||
{
|
||||
key_id: 'exhausted',
|
||||
key_name: 'exhausted',
|
||||
status: 'quota_exhausted',
|
||||
quota_snapshot: {
|
||||
code: 'exhausted',
|
||||
exhausted: true,
|
||||
updated_at: 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
key_id: 'invalid',
|
||||
key_name: 'invalid',
|
||||
status: 'auth_invalid',
|
||||
quota_snapshot: {
|
||||
code: 'unknown',
|
||||
exhausted: false,
|
||||
observed_at: 456,
|
||||
},
|
||||
},
|
||||
{
|
||||
key_id: 'unchanged',
|
||||
key_name: 'unchanged',
|
||||
status: 'error',
|
||||
},
|
||||
]
|
||||
|
||||
const merged = mergePoolKeyQuotaSnapshots(keys, results)
|
||||
|
||||
expect(merged[0].status_snapshot?.quota.code).toBe('exhausted')
|
||||
expect(merged[0].quota_updated_at).toBe(123)
|
||||
expect(merged[1].status_snapshot?.quota.code).toBe('unknown')
|
||||
expect(merged[1].quota_updated_at).toBe(456)
|
||||
expect(merged[2]).toBe(keys[2])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { QuotaWindowSnapshot } from '@/api/endpoints/types'
|
||||
|
||||
export function resetCodexCycleUsageWindows(
|
||||
windows: QuotaWindowSnapshot[],
|
||||
resetAt: number,
|
||||
): QuotaWindowSnapshot[] {
|
||||
return windows.map((window) => {
|
||||
const code = String(window.code || '').trim()
|
||||
const scope = String(window.scope || 'account').trim().toLowerCase()
|
||||
const shouldReset = Boolean(code)
|
||||
&& scope === 'account'
|
||||
&& !code.toLowerCase().startsWith('spark_')
|
||||
&& window.window_minutes !== 0
|
||||
|
||||
if (!shouldReset) return window
|
||||
|
||||
return {
|
||||
...window,
|
||||
usage_reset_at: Number.isFinite(resetAt) ? resetAt : window.usage_reset_at,
|
||||
usage: {
|
||||
request_count: 0,
|
||||
total_tokens: 0,
|
||||
total_cost_usd: '0.00000000',
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { PoolKeyDetail } from '@/api/endpoints/pool'
|
||||
import type { RefreshQuotaResult } from '@/api/endpoints/keys'
|
||||
|
||||
export function mergePoolKeyQuotaSnapshots(
|
||||
keys: PoolKeyDetail[],
|
||||
results: RefreshQuotaResult['results'],
|
||||
): PoolKeyDetail[] {
|
||||
const quotaByKeyId = new Map<string, NonNullable<RefreshQuotaResult['results'][number]['quota_snapshot']>>()
|
||||
for (const result of results) {
|
||||
if (result.quota_snapshot) {
|
||||
quotaByKeyId.set(result.key_id, result.quota_snapshot)
|
||||
}
|
||||
}
|
||||
if (quotaByKeyId.size === 0) return keys
|
||||
|
||||
return keys.map((key) => {
|
||||
const quotaSnapshot = quotaByKeyId.get(key.key_id)
|
||||
if (!quotaSnapshot) return key
|
||||
return {
|
||||
...key,
|
||||
quota_updated_at: quotaSnapshot.updated_at ?? quotaSnapshot.observed_at ?? key.quota_updated_at ?? null,
|
||||
status_snapshot: {
|
||||
...(key.status_snapshot ?? {}),
|
||||
quota: quotaSnapshot,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user