codeburn/app/electron/quota/index.test.ts
iamtoruk 916aa6430f harden(app): Electron security + spawn efficiency batch
- will-navigate/window-open denied; openExternal restricted to http(s);
  production CSP drops 'unsafe-inline' scripts (dev-only Vite plugin
  re-adds it for the Fast Refresh preamble)
- single-instance lock; before-quit reaps in-flight CLI children; 16MB
  output cap rejects with CliError 'too-large'
- read-only CLI spawns coalesce: concurrent identical calls share one
  child, a 5s result cache absorbs same-cadence pollers (six sections
  polled getOverview independently); actions bypass and flush the cache
  on completion so post-action refetches are fresh
- every renderer-supplied argv string validated (period/provider/range/
  currency allowlists, no flag-shaped tokens, absolute export path) with
  new CliError kind 'bad-args'; envelope + action stderr routed through
  sanitizeError
- quota force-refresh: manual refresh (refreshToken) passes force which
  invalidates QuotaService's cache; steady polls do not
- POSIX credential mode check gated off on win32

App suite 190/190, build green (production index.html verified strict).
2026-07-16 01:57:44 -07:00

58 lines
2.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { QuotaService } from './index'
import type { QuotaProvider } from './types'
const quota = (provider: 'claude' | 'codex'): QuotaProvider => ({
provider, connection: 'connected', primary: null, details: [], planLabel: null, footerLines: [],
})
describe('QuotaService', () => {
it('persists provider 429 blocked-until and gates the next forced fetch', async () => {
const writes: string[] = []
const claude = vi.fn(async () => ({ quota: quota('claude'), retryAfterSeconds: 60 }))
const codex = vi.fn(async () => ({ quota: quota('codex') }))
const service = new QuotaService({
claude, codex, now: () => Date.parse('2026-07-12T00:00:00Z'),
readFile: vi.fn(async () => writes.at(-1) ?? null),
writeFile: vi.fn(async (_path, value) => { writes.push(value) }),
statePath: '/mock/backoff.json',
})
await service.getQuota({ force: true })
const saved = JSON.parse(writes[0]!)
expect(saved.claude).toBe('2026-07-12T00:01:00.000Z')
await service.getQuota({ force: true })
expect(claude).toHaveBeenCalledTimes(1)
expect(codex).toHaveBeenCalledTimes(2)
})
it('force re-fetches within the cache window by invalidating first', async () => {
const claude = vi.fn(async () => ({ quota: quota('claude') }))
const codex = vi.fn(async () => ({ quota: quota('codex') }))
const service = new QuotaService({
claude, codex, now: () => 1000, refreshMs: 120_000,
readFile: vi.fn(async () => null), writeFile: vi.fn(async () => undefined),
})
await service.getQuota()
await service.getQuota() // fresh cache, no re-fetch
expect(claude).toHaveBeenCalledTimes(1)
await service.getQuota({ force: true }) // force clears the still-fresh cache
expect(claude).toHaveBeenCalledTimes(2)
})
it('single-flights simultaneous callers', async () => {
let release!: () => void
const pending = new Promise<void>(resolve => { release = resolve })
const claude = vi.fn(async () => { await pending; return { quota: quota('claude') } })
const service = new QuotaService({
claude, codex: vi.fn(async () => ({ quota: quota('codex') })),
readFile: vi.fn(async () => null), writeFile: vi.fn(async () => undefined),
})
const first = service.getQuota({ force: true })
const second = service.getQuota({ force: true })
release()
expect(await first).toEqual(await second)
expect(claude).toHaveBeenCalledTimes(1)
})
})