mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
Testing: production export, typecheck, mobile/desktop composer and paid-Settings browser gates, historical activity/count unit tests and design lint pass. Risk: organization-scoped read-only usage, labelled estimates, public repository metadata only. How to revert: revert this commit and consume app 0.2.46.
63 lines
4.8 KiB
TypeScript
63 lines
4.8 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { arrive, AUTO } from './chat'
|
|
import { answer, signIn } from '../session'
|
|
import { PLANS } from './api'
|
|
import { serveExport } from './export'
|
|
|
|
const plan = { plan: 'max-5x', period_start: '2026-10-01T00:00:00Z', period_end: '2026-11-01T00:00:00Z', state: 'ok', classes: { premium: { percent: 35, state: 'ok', paying: 'plan', resets_at: '2026-11-01T00:00:00Z' }, ours: { percent: 20, state: 'ok', paying: 'plan', resets_at: '2026-11-01T00:00:00Z' } }, actions: [] }
|
|
const totals = { tokens: 12345, promptTokens: 10000, completionTokens: 2345, requests: 67, spendCents: 12, models: 1, providers: 1 }
|
|
test.describe('composer usage', () => {
|
|
let base: string, stop: () => Promise<void>
|
|
test.beforeAll(async () => { const site = await serveExport(); base = site.url; stop = site.close })
|
|
test.afterAll(async () => stop())
|
|
for (const width of [1440, 390]) test(`context, reported tokens, plan and account usage at ${width}px`, async ({ page }, info) => {
|
|
await page.setViewportSize({ width, height: 900 })
|
|
await answer(page, '/v1/ai/limits', plan)
|
|
const reads: string[] = []
|
|
await page.route(url => url.pathname === '/v1/ai/usages/cloud', route => {
|
|
reads.push(route.request().headers()['x-org-id'])
|
|
return route.fulfill({ json: { scope: { org: 'bob', allOrgs: false }, totals, series: [], byModel: { items: [], totalCents: 12, other: null }, activity: { items: [], total: 0, limit: 20, offset: 0 } } })
|
|
})
|
|
await page.route(url => url.pathname === '/v1/chat/completions', route => route.fulfill({ contentType: 'text/event-stream', headers: { 'x-hanzo-served': AUTO.id, 'access-control-expose-headers': 'x-hanzo-served' }, body: `data: ${JSON.stringify({ choices: [{ index: 0, delta: { content: 'Done.' } }] })}\n\ndata: ${JSON.stringify({ usage: { prompt_tokens: 122, completion_tokens: 21, total_tokens: 143 } })}\n\ndata: [DONE]\n\n` }))
|
|
await arrive(page, base, 'pro', AUTO.id)
|
|
const field = page.locator('textarea').filter({ visible: true }).first()
|
|
await field.fill('Explain the usage control'); await field.press('Enter')
|
|
await expect(page.getByText('Done.', { exact: true }).first()).toBeVisible()
|
|
await page.locator('[data-slot="composer-usage"]').filter({ visible: true }).first().click()
|
|
const panel = page.locator('[data-slot="composer-usage-panel"]').filter({ visible: true }).first()
|
|
await expect(panel).toBeVisible()
|
|
await expect(panel.locator('[data-slot="composer-context"]')).toContainText('estimated')
|
|
await expect(panel.locator('[data-slot="composer-reported-tokens"]')).toContainText('143')
|
|
await expect(panel.locator('[data-slot="composer-account-usage"]')).toContainText('12,345')
|
|
await expect(panel.locator('[data-slot="composer-plan-usage"]')).toContainText('35%')
|
|
expect(reads).toContain('bob')
|
|
for (const name of ['Upgrade plan', 'Buy credits']) await expect(panel.getByRole('link', { name, exact: true })).toHaveAttribute('href', /org=bob/)
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1)).toBe(false)
|
|
await page.screenshot({ path: info.outputPath(`composer-usage-${width}.png`) })
|
|
await panel.locator('[data-slot="composer-see-usage"]').click()
|
|
await expect(page.locator('[data-slot="plan-usage"]').filter({ visible: true }).first()).toBeVisible()
|
|
})
|
|
test('paid Settings exposes actual tokens, requests and spend for the acting organization', async ({ page }) => {
|
|
await page.route('**/v1/**', route => route.fulfill({ json: {} }))
|
|
await signIn(page, { sub: 'u-sam', name: 'sam', email: '[email protected]', orgs: [{ org: 'sam', role: 'owner' }] })
|
|
await answer(page, '/v1/iam/organizations/admin/sam', { owner: 'admin', name: 'sam', displayName: 'Sam', isPersonal: true })
|
|
await answer(page, '/v1/billing/tier', { plan: 'max-5x', tier: { name: 'pro', displayName: 'Pro', maxAgents: 10, allowedModels: ['*'] }, balance: {} })
|
|
await answer(page, '/v1/billing/plans', PLANS)
|
|
await answer(page, '/v1/ai/limits', plan)
|
|
const orgs: string[] = []
|
|
await page.route(url => url.pathname === '/v1/ai/usages/cloud', route => {
|
|
orgs.push(route.request().headers()['x-org-id'])
|
|
return route.fulfill({ json: { scope: { org: 'sam', allOrgs: false }, totals, series: [], byModel: { items: [], totalCents: 12 }, activity: { items: [], total: 0 } } })
|
|
})
|
|
await page.goto(`${base}/settings/organization/usage`)
|
|
const section = page.getByRole('region', { name: 'Tokens, requests and spend', exact: true })
|
|
await expect(section).toBeVisible()
|
|
await expect(section).toContainText('12.3K')
|
|
await expect(section).toContainText('67')
|
|
await expect(section).toContainText('$0.12')
|
|
expect(orgs).toContain('sam')
|
|
await expect(page.getByRole('region', { name: 'Plan usage', exact: true })).toBeVisible()
|
|
})
|
|
|
|
})
|