mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
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.
193 lines
7.4 KiB
TypeScript
193 lines
7.4 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
||
import { paying } from '@hanzo/app/lib/tier'
|
||
import { chooseFree, choseFree, DEFAULT_DESTINATION, picker, safe } from '@hanzo/app/lib/destination'
|
||
|
||
/**
|
||
* EVERY SIGN-IN MEETS THE PLANS, AND A PAID ACCOUNT PASSES STRAIGHT THROUGH.
|
||
*
|
||
* The callback sends each sign-in to the picker by `picker`, except a shared
|
||
* chat, which opens. The picker asks `paying` first: a paid plan in the
|
||
* organization the reader works in, or before one is picked in any of theirs,
|
||
* sends them on. A read that fails is no plan, so the plans are offered.
|
||
*/
|
||
|
||
function bearer(claims: Record<string, unknown>): string {
|
||
return `header.${Buffer.from(JSON.stringify(claims)).toString('base64url')}.signature`
|
||
}
|
||
|
||
const tierOf = (name: string, plan = '') => ({
|
||
user: 'gate',
|
||
plan,
|
||
tier: { name, displayName: name, dailyCreditsCents: 0, maxAgents: 1, unlimitedAgents: false },
|
||
balance: { currency: 'usd', prepaidAvailable: 0, creditsRemaining: 0, dailyRemaining: 0, effectiveAvailable: 0 },
|
||
})
|
||
|
||
/**
|
||
* A browser signed in with `orgs`, working in `current`, answering each org's
|
||
* tier from `tiers`: a tier name (a paid one names the plan it is on), `trial`
|
||
* (a tier with no plan), `hang` (no answer), or a status for a failure.
|
||
*/
|
||
function world(orgs: string[], tiers: Record<string, string | number>, current?: string, signedIn = true) {
|
||
const global = globalThis as { window?: unknown; fetch: typeof fetch }
|
||
const hadWindow = 'window' in global
|
||
const wasWindow = global.window
|
||
const wasFetch = global.fetch
|
||
const store = new Map<string, string>()
|
||
if (signedIn) store.set('hanzo_iam_access_token', bearer({ owner: 'hanzo', orgs: orgs.map((org) => ({ org, role: 'owner' })) }))
|
||
if (current) store.set('hanzo_iam_current_org', current)
|
||
global.window = {
|
||
location: { hostname: 'hanzo.ai', origin: 'https://hanzo.ai' },
|
||
localStorage: { getItem: (k: string) => store.get(k) ?? null },
|
||
}
|
||
const asked: string[] = []
|
||
global.fetch = (async (input: string | URL, init?: RequestInit) => {
|
||
const url = new URL(String(input))
|
||
const org = new Headers(init?.headers).get('X-Org-Id') ?? '-'
|
||
asked.push(`${url.pathname} ${org}`)
|
||
const t = tiers[org]
|
||
if (t === undefined) throw new TypeError('network')
|
||
if (typeof t === 'number') return new Response('{}', { status: t })
|
||
if (t === 'hang')
|
||
return new Promise<Response>((_, reject) => init?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError'))))
|
||
if (t === 'trial') return new Response(JSON.stringify(tierOf('starter', '')), { status: 200 })
|
||
if (t === 'comp') return new Response(JSON.stringify(tierOf('pro', '')), { status: 200 })
|
||
if (t === 'enterprise') return new Response(JSON.stringify(tierOf('enterprise', '')), { status: 200 })
|
||
return new Response(JSON.stringify(tierOf(t, t === 'free' ? '' : 'max-20x')), { status: 200 })
|
||
}) as typeof fetch
|
||
return {
|
||
asked,
|
||
done: () => {
|
||
global.fetch = wasFetch
|
||
if (hadWindow) global.window = wasWindow
|
||
else delete global.window
|
||
},
|
||
}
|
||
}
|
||
|
||
test('a free account in the organization it works in is not paying, and only that organization is asked', async () => {
|
||
const w = world(['acme', 'side'], { acme: 'free', side: 'pro' }, 'acme')
|
||
try {
|
||
expect(await paying()).toBe(false)
|
||
expect(w.asked).toEqual(['/v1/billing/tier acme'])
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('a paid plan in the organization it works in is paying', async () => {
|
||
const w = world(['acme'], { acme: 'pro' })
|
||
try {
|
||
expect(await paying()).toBe(true)
|
||
expect(w.asked).toEqual(['/v1/billing/tier acme'])
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('before an organization is picked, a paid plan in any of them is paying', async () => {
|
||
const w = world(['home', 'webby', 'guard'], { home: 'free', webby: 'pro', guard: 500 })
|
||
try {
|
||
expect(await paying()).toBe(true)
|
||
expect(w.asked.sort()).toEqual(['/v1/billing/tier guard', '/v1/billing/tier home', '/v1/billing/tier webby'])
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('the platform org’s enterprise tier is paying', async () => {
|
||
const w = world(['admin'], { admin: 'enterprise' })
|
||
try {
|
||
expect(await paying()).toBe(true)
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('a plan recorded outside checkout, served the pro tier with no plan named, is paying', async () => {
|
||
const w = world(['acme'], { acme: 'comp' })
|
||
try {
|
||
expect(await paying()).toBe(true)
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('a tier with no plan behind it, as a trial nobody paid for, is not paying', async () => {
|
||
const w = world(['acme'], { acme: 'trial' })
|
||
try {
|
||
expect(await paying()).toBe(false)
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('billing that does not answer is no plan within five seconds', async () => {
|
||
const w = world(['acme'], { acme: 'hang' })
|
||
try {
|
||
const t0 = Date.now()
|
||
expect(await paying()).toBe(false)
|
||
expect(Date.now() - t0).toBeLessThan(7000)
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('a tier that cannot be read is no plan, so the plans are offered', async () => {
|
||
const w = world(['acme', 'other'], { acme: 502 })
|
||
try {
|
||
expect(await paying()).toBe(false)
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('nobody signed in is not paying and nothing is asked', async () => {
|
||
const w = world([], {}, undefined, false)
|
||
try {
|
||
expect(await paying()).toBe(false)
|
||
expect(w.asked).toEqual([])
|
||
} finally {
|
||
w.done()
|
||
}
|
||
})
|
||
|
||
test('every sign-in goes to the picker on its way, and a shared chat opens', () => {
|
||
expect(picker('/chat')).toBe('/onboarding?next=%2Fchat')
|
||
expect(picker('/')).toBe('/onboarding?next=%2F')
|
||
expect(picker('/pay/confirm/card?plan=dev')).toBe(`/onboarding?next=${encodeURIComponent('/pay/confirm/card?plan=dev')}`)
|
||
expect(picker('/chat/shared')).toBe('/chat/shared')
|
||
expect(picker('/chat/shared?s=abc')).toBe('/chat/shared?s=abc')
|
||
expect(picker('/chat/shared/')).toBe('/chat/shared/')
|
||
expect(picker('/onboarding?next=%2Fdev')).toBe('/onboarding?next=%2Fdev')
|
||
})
|
||
|
||
test('an address the browser would read as another site is not where a sign-in goes', () => {
|
||
// The parser drops a tab or a newline and reads a backslash as a slash, so each
|
||
// of these is `//evil.example` to a browser.
|
||
for (const bad of ['/\t/evil.example', '/\n/evil.example', '/\t\\evil.example', '/\r\n/evil.example', '/%09/evil.example'.replace('%09', '\t')])
|
||
expect(safe(bad), JSON.stringify(bad)).toBe(DEFAULT_DESTINATION)
|
||
expect(picker(safe('/\t/evil.example'))).toBe(`/onboarding?next=${encodeURIComponent(DEFAULT_DESTINATION)}`)
|
||
// What a person wrote into the address survives.
|
||
expect(safe('/chat?q=build a shift board')).toBe('/chat?q=build a shift board')
|
||
expect(safe('/chat/shared?s=abc#t')).toBe('/chat/shared?s=abc#t')
|
||
})
|
||
|
||
test('Free chosen before signing in skips the picker for that one sign-in only', () => {
|
||
const global = globalThis as { window?: unknown }
|
||
const had = 'window' in global
|
||
const was = global.window
|
||
const kept = new Map<string, string>()
|
||
global.window = {
|
||
sessionStorage: { getItem: (k: string) => kept.get(k) ?? null, setItem: (k: string, v: string) => void kept.set(k, v), removeItem: (k: string) => void kept.delete(k) },
|
||
}
|
||
try {
|
||
expect(choseFree(), 'nothing chosen').toBe(false)
|
||
chooseFree()
|
||
expect(choseFree(), 'Free was chosen').toBe(true)
|
||
expect(choseFree(), 'read once: the next sign-in asks again').toBe(false)
|
||
} finally {
|
||
if (had) global.window = was
|
||
else delete global.window
|
||
}
|
||
})
|