Files
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

65 lines
3.5 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { billed, planRequired } from '@hanzo/app/failure'
/**
* A REFUSAL ASKS FOR A PLAN BY ITS CODE.
*
* The gateway refuses a model the plan does not include with 402 and
* `{"error":{"code":"plan_required","upgrade_url":…}}`. The code decides, never
* the sentence, and the address it names is followed only when it is an https
* page on hanzo.ai or one of its hosts.
*/
const refused = (status: number, error: unknown) => ({ status, body: { error } })
test('402 plan_required asks, with the address it names', () => {
const href = 'https://pay.hanzo.ai/?plan=dev'
expect(planRequired(refused(402, { code: 'plan_required', upgrade_url: href }))).toEqual({ href })
expect(planRequired(refused(402, { code: 'plan_required' }))).toEqual({ href: undefined })
})
test('the Free plan\'s limits ask, each by its own status', () => {
const href = 'https://hanzo.ai/pay'
expect(planRequired(refused(402, { code: 'allowance_spent', upgrade_url: href }))).toEqual({ href })
expect(planRequired(refused(429, { code: 'pool_exhausted', upgrade_url: href }))).toEqual({ href })
expect(planRequired(refused(429, { code: 'pool_busy', upgrade_url: href }))).toEqual({ href })
// A spent Free day is 429 allowance_spent since the gateway's free notice (hanzoai/ai object/free_notice.go; @hanzo/app
// a2f7fc0): it asks for the plan, with no address when the gateway names none. The pool's codes are 429 alone.
expect(planRequired(refused(429, { code: 'allowance_spent' }))).toEqual({ href: undefined })
expect(planRequired(refused(402, { code: 'pool_exhausted' }))).toBeNull()
})
test('any other refusal does not ask, whatever its sentence says', () => {
expect(planRequired(refused(402, { code: 'insufficient_balance', message: 'plan_required' }))).toBeNull()
expect(planRequired(refused(403, { code: 'plan_required' }))).toBeNull()
expect(planRequired(new Error('plan_required'))).toBeNull()
expect(planRequired(null)).toBeNull()
})
test('an address off hanzo.ai, or not https, is not followed', () => {
for (const href of ['javascript:alert(1)', 'http://pay.hanzo.ai/', 'https://pay.hanzo.ai.example.com/', 'https://evil.test/?h=hanzo.ai', 'not a url']) {
expect(planRequired(refused(402, { code: 'plan_required', upgrade_url: href }))).toEqual({ href: undefined })
}
expect(planRequired(refused(402, { code: 'plan_required', upgrade_url: 'https://hanzo.ai/pricing' }))).toEqual({
href: 'https://hanzo.ai/pricing',
})
})
/**
* A BILLING REFUSAL IS READ FROM ITS ENVELOPE.
*
* 402 plan_allowance_used / paid_plan_required / insufficient_balance and 429
* free_plan_cap carry `{"error":{"type":"billing_error",…}}`; any other
* refusal is not one.
*/
test('a billing refusal is read from its envelope, and nothing else is', () => {
const at = '2026-10-30T00:00:00Z'
const bill = (status: number, error: Record<string, unknown>) => billed(refused(status, { type: 'billing_error', ...error }))
expect(bill(402, { code: 'plan_allowance_used', message: 'm', class: 'premium', resets_at: at })).toMatchObject({ status: 402, code: 'plan_allowance_used', class: 'premium', resets_at: at })
expect(bill(429, { code: 'free_plan_cap', message: 'm' })).toMatchObject({ status: 429, code: 'free_plan_cap' })
expect(billed(refused(429, { code: 'pool_busy', type: 'rate_limit_error' }))).toBeNull()
expect(billed(refused(500, { code: 'plan_allowance_used', type: 'billing_error' }))).toBeNull()
expect(billed(new Error('billing_error'))).toBeNull()
expect(billed(null)).toBeNull()
})