tests/cli-usage.unit.test.ts
Rama main · réplica de lectura del repositorio original.
import type { Context } from 'hono'
import { readFileSync } from 'node:fs'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import pack from '../cli/package.json'
import {
buildCliRequestHeaders,
setCurrentCliCommand,
} from '../cli/src/analytics/cli-headers.ts'
import { hasCliUsageIngest, resolveCliUsageIdentity, trackCliUsage } from '../supabase/functions/_backend/utils/cli_usage.ts'
const {
cloudlogErrMock,
checkKeyMock,
} = vi.hoisted(() => ({
cloudlogErrMock: vi.fn(),
checkKeyMock: vi.fn(),
}))
vi.mock('../supabase/functions/_backend/utils/logging.ts', () => ({
cloudlogErr: cloudlogErrMock,
serializeError: (error: unknown) => ({ message: error instanceof Error ? error.message : String(error) }),
}))
vi.mock('../supabase/functions/_backend/utils/supabase.ts', () => ({
checkKey: checkKeyMock,
supabaseAdmin: vi.fn(() => ({})),
}))
function createContext(env: Record<string, unknown> = {}) {
return {
env,
get: vi.fn((key: string) => key === 'requestId' ? 'test-request' : undefined),
} as unknown as Context
}
describe('buildCliRequestHeaders', () => {
afterEach(() => {
setCurrentCliCommand('')
})
it('includes version, command, node, and os headers', () => {
setCurrentCliCommand('bundle upload')
const headers = buildCliRequestHeaders()
expect(headers.capgo_api).toBe('2025-10-01')
expect(headers['x-cli-version']).toBe(pack.version)
expect(headers['x-cli-command']).toBe('bundle upload')
expect(headers['x-cli-node']).toBeTruthy()
expect(headers['x-cli-os']).toBeTruthy()
expect(headers.capgkey).toBeUndefined()
})
it('merges extra headers without inventing apikey', () => {
const headers = buildCliRequestHeaders({ Authorization: 'Bearer x', capgkey: 'key-1' })
expect(headers.Authorization).toBe('Bearer x')
expect(headers.capgkey).toBe('key-1')
expect(headers['x-cli-version']).toBe(pack.version)
})
})
describe('trackCliUsage', () => {
const event = {
cli_version: '8.32.8',
command: 'bundle upload',
node_version: 'v22.0.0',
os_platform: 'darwin',
apikey_id: '00000000-0000-4000-8000-000000000001',
org_id: null,
source: 'config' as const,
api_version: '2025-10-01',
}
beforeEach(() => {
cloudlogErrMock.mockReset()
})
it('skips entirely when cli_version is empty', async () => {
const record = vi.fn()
await trackCliUsage(createContext({ ADMIN_CLI_USAGE: { record } }), { ...event, cli_version: '' })
expect(record).not.toHaveBeenCalled()
})
it('forwards the event to the admin dashboard RPC binding', async () => {
const record = vi.fn().mockResolvedValue({ recorded: true })
const c = createContext({ ADMIN_CLI_USAGE: { record } })
expect(hasCliUsageIngest(c)).toBe(true)
await trackCliUsage(c, event)
expect(record).toHaveBeenCalledWith(event)
})
it('records nothing without the binding (preprod, local, Supabase)', async () => {
const c = createContext({ CLI_USAGE: { writeDataPoint: vi.fn() } })
expect(hasCliUsageIngest(c)).toBe(false)
await expect(trackCliUsage(c, event)).resolves.toBeUndefined()
expect(cloudlogErrMock).not.toHaveBeenCalled()
})
it('logs and swallows RPC failures', async () => {
const record = vi.fn().mockRejectedValue(new Error('rpc down'))
await expect(trackCliUsage(createContext({ ADMIN_CLI_USAGE: { record } }), event)).resolves.toBeUndefined()
expect(cloudlogErrMock).toHaveBeenCalledTimes(1)
})
})
describe('resolveCliUsageIdentity', () => {
beforeEach(() => {
cloudlogErrMock.mockReset()
checkKeyMock.mockReset()
})
it('returns nulls when no capgkey is provided', async () => {
const result = await resolveCliUsageIdentity(createContext(), undefined)
expect(result).toEqual({ apikey_id: null, org_id: null })
expect(checkKeyMock).not.toHaveBeenCalled()
})
it('returns rbac_id for a valid key', async () => {
checkKeyMock.mockResolvedValue({ rbac_id: 'rbac-123' })
const result = await resolveCliUsageIdentity(createContext(), 'capgo_key')
expect(result).toEqual({ apikey_id: 'rbac-123', org_id: null })
expect(checkKeyMock).toHaveBeenCalled()
})
it('returns nulls without throwing when checkKey rejects', async () => {
checkKeyMock.mockRejectedValue(new Error('lookup failed'))
await expect(resolveCliUsageIdentity(createContext(), 'bad-key')).resolves.toEqual({
apikey_id: null,
org_id: null,
})
expect(cloudlogErrMock).toHaveBeenCalled()
})
})
describe('api worker CLI usage bindings', () => {
const config = JSON.parse(readFileSync(new URL('../cloudflare_workers/api/wrangler.jsonc', import.meta.url), 'utf8'))
it('binds only prod to the admin dashboard CliUsageIngest RPC entrypoint', () => {
expect(config.env.prod.services).toContainEqual({
binding: 'ADMIN_CLI_USAGE',
service: 'capgo-admin-dashboard-api',
entrypoint: 'CliUsageIngest',
})
for (const [name, environment] of Object.entries(config.env) as Array<[string, { services?: Array<{ binding: string }> }]>) {
if (name !== 'prod')
expect(environment.services?.some(service => service.binding === 'ADMIN_CLI_USAGE') ?? false).toBe(false)
}
})
it('no longer writes the CLI_USAGE Analytics Engine dataset directly', () => {
for (const environment of [config, ...Object.values(config.env)] as Array<{ analytics_engine_datasets?: Array<{ binding: string }> }>)
expect(environment.analytics_engine_datasets?.some(dataset => dataset.binding === 'CLI_USAGE') ?? false).toBe(false)
})
})