mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
234 lines
7.3 KiB
TypeScript
234 lines
7.3 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest'
|
|
|
|
import { mobileI18n } from '../i18n/mobile-i18n'
|
|
import {
|
|
getInactiveProviderUsage,
|
|
getUsageBarState,
|
|
getWindowResetLabel,
|
|
hasActiveProviderUsage,
|
|
hasRenderableUsage,
|
|
type AccountsSnapshot,
|
|
type InactiveAccountUsage,
|
|
type ProviderRateLimits
|
|
} from './account-usage-state'
|
|
|
|
const INITIAL_LOCALE = mobileI18n.language
|
|
|
|
afterEach(async () => {
|
|
await mobileI18n.changeLanguage(INITIAL_LOCALE)
|
|
})
|
|
|
|
function makeLimits(overrides: Partial<ProviderRateLimits> = {}): ProviderRateLimits {
|
|
return {
|
|
provider: 'claude',
|
|
session: null,
|
|
weekly: null,
|
|
monthly: null,
|
|
updatedAt: 0,
|
|
error: null,
|
|
status: 'idle',
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function makeSnapshot(
|
|
overrides: {
|
|
claudeLimits?: ProviderRateLimits | null
|
|
codexLimits?: ProviderRateLimits | null
|
|
claudeAccounts?: AccountsSnapshot['claude']['accounts']
|
|
codexAccounts?: AccountsSnapshot['codex']['accounts']
|
|
inactiveClaudeAccounts?: InactiveAccountUsage[]
|
|
inactiveCodexAccounts?: InactiveAccountUsage[]
|
|
} = {}
|
|
): AccountsSnapshot {
|
|
return {
|
|
claude: {
|
|
accounts: overrides.claudeAccounts ?? [],
|
|
activeAccountId: null,
|
|
activeAccountIdsByRuntime: { host: null, wsl: {} }
|
|
},
|
|
codex: {
|
|
accounts: overrides.codexAccounts ?? [],
|
|
activeAccountId: null,
|
|
activeAccountIdsByRuntime: { host: null, wsl: {} }
|
|
},
|
|
rateLimits: {
|
|
claude: overrides.claudeLimits ?? null,
|
|
codex: overrides.codexLimits ?? null,
|
|
claudeTarget: { runtime: 'host', wslDistro: null },
|
|
codexTarget: { runtime: 'host', wslDistro: null },
|
|
inactiveClaudeAccounts: overrides.inactiveClaudeAccounts ?? [],
|
|
inactiveCodexAccounts: overrides.inactiveCodexAccounts ?? []
|
|
}
|
|
}
|
|
}
|
|
|
|
describe('hasActiveProviderUsage', () => {
|
|
it('is false when there are no rate limits at all', () => {
|
|
expect(hasActiveProviderUsage(null)).toBe(false)
|
|
})
|
|
|
|
it('is true when a session window has data', () => {
|
|
expect(
|
|
hasActiveProviderUsage(
|
|
makeLimits({
|
|
status: 'ok',
|
|
session: { usedPercent: 12, windowMinutes: 300, resetsAt: null, resetDescription: null }
|
|
})
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('is true when a successful fetch returned ok even with empty windows', () => {
|
|
expect(hasActiveProviderUsage(makeLimits({ status: 'ok' }))).toBe(true)
|
|
})
|
|
|
|
it('is false for an unavailable/error provider with no window data (no creds)', () => {
|
|
expect(hasActiveProviderUsage(makeLimits({ status: 'unavailable' }))).toBe(false)
|
|
expect(hasActiveProviderUsage(makeLimits({ status: 'error', error: 'nope' }))).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('hasRenderableUsage', () => {
|
|
it('is true when the provider has at least one managed account', () => {
|
|
const snapshot = makeSnapshot({
|
|
claudeAccounts: [{ id: 'a', email: 'x@y.z' }]
|
|
})
|
|
expect(hasRenderableUsage(snapshot, 'claude')).toBe(true)
|
|
})
|
|
|
|
// The bug: system-default auth has zero managed accounts but real usage data,
|
|
// and the home screen used to hide it entirely.
|
|
it('is true with zero managed accounts when active rate-limit data exists (system default)', () => {
|
|
const snapshot = makeSnapshot({
|
|
codexLimits: makeLimits({
|
|
provider: 'codex',
|
|
status: 'ok',
|
|
session: { usedPercent: 40, windowMinutes: 300, resetsAt: null, resetDescription: null }
|
|
})
|
|
})
|
|
expect(hasRenderableUsage(snapshot, 'codex')).toBe(true)
|
|
})
|
|
|
|
it('is false with zero accounts and no usable rate-limit data', () => {
|
|
const snapshot = makeSnapshot({
|
|
claudeLimits: makeLimits({ status: 'unavailable' })
|
|
})
|
|
expect(hasRenderableUsage(snapshot, 'claude')).toBe(false)
|
|
expect(hasRenderableUsage(makeSnapshot(), 'claude')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('getInactiveProviderUsage', () => {
|
|
it('returns inactive usage using the runtime rateLimits payload shape', () => {
|
|
const limits = makeLimits({
|
|
status: 'ok',
|
|
session: { usedPercent: 52, windowMinutes: 300, resetsAt: null, resetDescription: null }
|
|
})
|
|
const snapshot = makeSnapshot({
|
|
inactiveClaudeAccounts: [
|
|
{ accountId: 'account-1', rateLimits: limits, updatedAt: 123, isFetching: false }
|
|
]
|
|
})
|
|
|
|
expect(getInactiveProviderUsage(snapshot, 'claude', 'account-1')?.rateLimits).toBe(limits)
|
|
})
|
|
})
|
|
|
|
describe('getWindowResetLabel', () => {
|
|
const now = 1_700_000_000_000
|
|
const min = 60_000
|
|
const hour = 60 * min
|
|
const day = 24 * hour
|
|
|
|
function makeWindow(resetsAt: number | null): ProviderRateLimits['session'] {
|
|
return { usedPercent: 13, windowMinutes: 300, resetsAt, resetDescription: null }
|
|
}
|
|
|
|
it('is null when there are no limits or the window has no reset timestamp', () => {
|
|
expect(getWindowResetLabel(null, 'session', now)).toBe(null)
|
|
expect(getWindowResetLabel(makeLimits({ status: 'ok' }), 'session', now)).toBe(null)
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ status: 'ok', session: makeWindow(null) }), 'session', now)
|
|
).toBe(null)
|
|
})
|
|
|
|
it('formats minutes, hours+minutes, and days+hours like the desktop tooltip', () => {
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ session: makeWindow(now + 47 * min) }), 'session', now)
|
|
).toBe('Resets in 47m')
|
|
expect(
|
|
getWindowResetLabel(
|
|
makeLimits({ session: makeWindow(now + 3 * hour + 54 * min) }),
|
|
'session',
|
|
now
|
|
)
|
|
).toBe('Resets in 3h 54m')
|
|
expect(
|
|
getWindowResetLabel(
|
|
makeLimits({ weekly: makeWindow(now + 6 * day + 7 * hour) }),
|
|
'weekly',
|
|
now
|
|
)
|
|
).toBe('Resets in 6d 7h')
|
|
})
|
|
|
|
it('formats exact hours and exact days without a zero remainder', () => {
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ session: makeWindow(now + 2 * hour) }), 'session', now)
|
|
).toBe('Resets in 2h')
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ weekly: makeWindow(now + 7 * day) }), 'weekly', now)
|
|
).toBe('Resets in 7d')
|
|
})
|
|
|
|
it('reports "Resets now" for a reset timestamp in the past', () => {
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ session: makeWindow(now - min) }), 'session', now)
|
|
).toBe('Resets now')
|
|
})
|
|
|
|
it('localizes the complete reset countdown sentence', async () => {
|
|
await mobileI18n.changeLanguage('es')
|
|
|
|
expect(
|
|
getWindowResetLabel(
|
|
makeLimits({ session: makeWindow(now + 3 * hour + 54 * min) }),
|
|
'session',
|
|
now
|
|
)
|
|
).toBe('Se restablece en 3h 54m')
|
|
expect(
|
|
getWindowResetLabel(makeLimits({ session: makeWindow(now - min) }), 'session', now)
|
|
).toBe('Se restablece ahora')
|
|
})
|
|
|
|
it('reads the requested window only', () => {
|
|
const limits = makeLimits({ session: makeWindow(now + hour) })
|
|
expect(getWindowResetLabel(limits, 'weekly', now)).toBe(null)
|
|
})
|
|
})
|
|
|
|
describe('getUsageBarState', () => {
|
|
it('keeps stale window data visible during a transient error', () => {
|
|
const bar = getUsageBarState(
|
|
makeLimits({
|
|
status: 'error',
|
|
error: 'temporarily unavailable',
|
|
session: { usedPercent: 72, windowMinutes: 300, resetsAt: null, resetDescription: null }
|
|
}),
|
|
'session'
|
|
)
|
|
|
|
expect(bar).toEqual({ usedPercent: 72, unavailable: false, loading: false })
|
|
})
|
|
|
|
it('shows loading for a fetching provider without a window', () => {
|
|
expect(getUsageBarState(makeLimits({ status: 'fetching' }), 'weekly')).toEqual({
|
|
usedPercent: null,
|
|
unavailable: false,
|
|
loading: true
|
|
})
|
|
})
|
|
})
|