mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth. Original-topic-commit:7903f1cddbOriginal-topic-commit:588117b5cfOriginal-topic-commit:dedd4f8c86Original-topic-commit:6645dae104Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276 Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276 Restacked-onto:b032867021Co-authored-by: kespineira <kespineira@users.noreply.github.com> Co-authored-by: kevimux <kevimux@users.noreply.github.com> Reported-by: pullfrog Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f Native-helper-source:80dbe23237Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c
218 lines
8.5 KiB
TypeScript
218 lines
8.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { ProviderRateLimits } from '../../shared/rate-limit-types'
|
|
import type { OpenCodeGoUsageWindows } from './opencode-go-status-parsing'
|
|
import type * as usageFetcher from './opencode-go-usage-fetcher'
|
|
|
|
const resolveApiKeyMock = vi.hoisted(() => vi.fn())
|
|
const fetchWithApiKeyMock = vi.hoisted(() => vi.fn())
|
|
const fetchWithCookieMock = vi.hoisted(() => vi.fn())
|
|
|
|
vi.mock('./opencode-go-api-key-source', () => ({ resolveOpenCodeGoApiKey: resolveApiKeyMock }))
|
|
vi.mock('./opencode-go-usage-api', () => ({
|
|
fetchOpenCodeGoUsageWithApiKey: fetchWithApiKeyMock
|
|
}))
|
|
vi.mock('./opencode-go-usage-fetcher', async (importOriginal) => {
|
|
const actual: typeof usageFetcher = await importOriginal()
|
|
return {
|
|
normalizeCookieInput: actual.normalizeCookieInput,
|
|
fetchOpenCodeGoRateLimits: fetchWithCookieMock
|
|
}
|
|
})
|
|
|
|
import { fetchOpenCodeGoUsage } from './opencode-go-usage-source-selection'
|
|
|
|
// Placeholder only — a real key must never reach a fixture.
|
|
const API_KEY = 'placeholder-go-key'
|
|
const COOKIE = 'auth=placeholder; __Host-console_session=placeholder'
|
|
|
|
const WINDOWS: OpenCodeGoUsageWindows = {
|
|
session: { usedPercent: 12, windowMinutes: 300, resetsAt: null, resetDescription: null },
|
|
weekly: { usedPercent: 34, windowMinutes: 10080, resetsAt: null, resetDescription: null },
|
|
monthly: null
|
|
}
|
|
|
|
function cookieResult(status: ProviderRateLimits['status']): ProviderRateLimits {
|
|
return {
|
|
provider: 'opencode-go',
|
|
session: status === 'ok' ? WINDOWS.session : null,
|
|
weekly: status === 'ok' ? WINDOWS.weekly : null,
|
|
monthly: null,
|
|
updatedAt: Date.now(),
|
|
error: status === 'ok' ? null : 'Usage fetch failed (401)',
|
|
status
|
|
}
|
|
}
|
|
|
|
describe('fetchOpenCodeGoUsage', () => {
|
|
beforeEach(() => {
|
|
resolveApiKeyMock.mockReset()
|
|
fetchWithApiKeyMock.mockReset()
|
|
fetchWithCookieMock.mockReset()
|
|
})
|
|
|
|
it('uses the API key ahead of a configured cookie and records its tier', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'found', key: API_KEY, tier: 'environment' })
|
|
fetchWithApiKeyMock.mockResolvedValue({ kind: 'ok', windows: WINDOWS })
|
|
const onApiKeyResolved = vi.fn()
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: COOKIE, onApiKeyResolved })
|
|
|
|
expect(fetchWithCookieMock).not.toHaveBeenCalled()
|
|
expect(onApiKeyResolved).toHaveBeenCalledWith({
|
|
status: 'found',
|
|
key: API_KEY,
|
|
tier: 'environment'
|
|
})
|
|
expect(result.status).toBe('ok')
|
|
expect(result.session).toEqual(WINDOWS.session)
|
|
expect(result.usageMetadata?.credentialSource).toBe('environment')
|
|
})
|
|
|
|
it('passes the settings override down as the highest-precedence tier', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'missing' })
|
|
|
|
await fetchOpenCodeGoUsage({ cookie: '', settingsApiKey: API_KEY })
|
|
|
|
expect(resolveApiKeyMock).toHaveBeenCalledWith({ settingsOverride: API_KEY })
|
|
})
|
|
|
|
it('passes trusted execution context and selected roots through to credential lookup', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'missing' })
|
|
const environment = { XDG_DATA_HOME: '/task/selected', OPENCODE_DB: ':memory:' }
|
|
|
|
await fetchOpenCodeGoUsage({ cookie: '', backend: 'v1', environment, cwd: '/task/workspace' })
|
|
|
|
expect(resolveApiKeyMock).toHaveBeenCalledExactlyOnceWith({
|
|
settingsOverride: undefined,
|
|
backend: 'v1',
|
|
environment,
|
|
cwd: '/task/workspace'
|
|
})
|
|
})
|
|
|
|
it('passes a manual override without evaluating selected execution context', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'found', key: API_KEY, tier: 'settings' })
|
|
fetchWithApiKeyMock.mockResolvedValue({ kind: 'ok', windows: WINDOWS })
|
|
const input = { cookie: '', settingsApiKey: API_KEY }
|
|
Object.defineProperty(input, 'environment', {
|
|
get: () => {
|
|
throw new Error('Selected profile metadata is unreadable')
|
|
}
|
|
})
|
|
|
|
expect((await fetchOpenCodeGoUsage(input)).status).toBe('ok')
|
|
expect(resolveApiKeyMock).toHaveBeenCalledExactlyOnceWith({ settingsOverride: API_KEY })
|
|
})
|
|
|
|
it('propagates a rejected selected-account lookup without trying host or cookie credentials', async () => {
|
|
resolveApiKeyMock.mockRejectedValue(new Error('Selected profile metadata is unreadable'))
|
|
|
|
await expect(fetchOpenCodeGoUsage({ cookie: COOKIE })).rejects.toThrow(
|
|
'Selected profile metadata is unreadable'
|
|
)
|
|
expect(fetchWithApiKeyMock).not.toHaveBeenCalled()
|
|
expect(fetchWithCookieMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('names the missing subscription instead of a generic refresh failure', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({
|
|
status: 'found',
|
|
key: API_KEY,
|
|
tier: 'opencode-auth-file'
|
|
})
|
|
fetchWithApiKeyMock.mockResolvedValue({ kind: 'no-subscription' })
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: '' })
|
|
|
|
expect(result.status).toBe('error')
|
|
expect(result.usageMetadata?.failureKind).toBe('no-subscription')
|
|
expect(result.error).toContain('no OpenCode Go subscription')
|
|
expect(result.error).not.toContain(API_KEY)
|
|
})
|
|
|
|
it('keeps a working cookie account alive when the key has no Go entitlement', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'found', key: API_KEY, tier: 'settings' })
|
|
fetchWithApiKeyMock.mockResolvedValue({ kind: 'no-subscription' })
|
|
fetchWithCookieMock.mockResolvedValue(cookieResult('ok'))
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: COOKIE })
|
|
|
|
expect(fetchWithCookieMock).toHaveBeenCalledWith(COOKIE, undefined, undefined)
|
|
expect(result.status).toBe('ok')
|
|
})
|
|
|
|
it('reports the key verdict when the cookie fallback also fails', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'found', key: API_KEY, tier: 'settings' })
|
|
fetchWithApiKeyMock.mockResolvedValue({ kind: 'unauthorized' })
|
|
fetchWithCookieMock.mockResolvedValue(cookieResult('error'))
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: COOKIE })
|
|
|
|
expect(result.status).toBe('error')
|
|
expect(result.usageMetadata?.failureKind).toBe('stale-token')
|
|
expect(result.error).toContain('/connect')
|
|
})
|
|
|
|
it('surfaces a transport failure without exposing the key', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'found', key: API_KEY, tier: 'settings' })
|
|
fetchWithApiKeyMock.mockResolvedValue({
|
|
kind: 'failed',
|
|
message: 'OpenCode Go usage request failed (503)'
|
|
})
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: '' })
|
|
|
|
expect(result.status).toBe('error')
|
|
expect(result.usageMetadata?.failureKind).toBe('server')
|
|
expect(result.error).toBe('OpenCode Go usage request failed (503)')
|
|
})
|
|
|
|
it('falls back to the cookie path when no key exists anywhere', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'missing' })
|
|
fetchWithCookieMock.mockResolvedValue(cookieResult('ok'))
|
|
|
|
const proxy = { httpProxyUrl: 'http://proxy.example:8080', httpProxyBypassRules: '' }
|
|
const result = await fetchOpenCodeGoUsage({
|
|
cookie: COOKIE,
|
|
workspaceIdOverride: 'wrk_abc',
|
|
networkProxySettings: proxy
|
|
})
|
|
|
|
expect(fetchWithApiKeyMock).not.toHaveBeenCalled()
|
|
expect(fetchWithCookieMock).toHaveBeenCalledWith(COOKIE, 'wrk_abc', proxy)
|
|
expect(result.status).toBe('ok')
|
|
})
|
|
|
|
it('reports an unreadable credential database instead of using an env key', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'credential-database-unreadable' })
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: ' ' })
|
|
|
|
expect(fetchWithApiKeyMock).not.toHaveBeenCalled()
|
|
expect(fetchWithCookieMock).not.toHaveBeenCalled()
|
|
expect(result.status).toBe('error')
|
|
expect(result.usageMetadata?.failureKind).toBe('usage-unavailable')
|
|
expect(result.error).toContain("Could not read OpenCode's credential database")
|
|
})
|
|
|
|
it('uses the configured cookie when the credential database is unreadable', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'credential-database-unreadable' })
|
|
fetchWithCookieMock.mockResolvedValue(cookieResult('ok'))
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: COOKIE })
|
|
|
|
expect(fetchWithApiKeyMock).not.toHaveBeenCalled()
|
|
expect(result.status).toBe('ok')
|
|
})
|
|
|
|
it('stays unavailable when neither a key nor a cookie is configured', async () => {
|
|
resolveApiKeyMock.mockResolvedValue({ status: 'missing' })
|
|
|
|
const result = await fetchOpenCodeGoUsage({ cookie: ' ' })
|
|
|
|
expect(fetchWithCookieMock).not.toHaveBeenCalled()
|
|
expect(result.status).toBe('unavailable')
|
|
expect(result.usageMetadata?.failureKind).toBe('missing-credentials')
|
|
})
|
|
})
|