mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
91 lines
4.2 KiB
TypeScript
91 lines
4.2 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
import { serveExport } from './export'
|
|
import { bare } from './heard'
|
|
import { asked, issuer, type Seen } from './issuer'
|
|
|
|
/**
|
|
* A `<slug>.hanzo.app` ALIAS SIGNS IN ON HANZO.AI.
|
|
*
|
|
* The Sites plane serves this export at cloud-hanzo-ai.hanzo.app and
|
|
* hanzo-bot.hanzo.app as well as at its own hosts. No OAuth client lists a
|
|
* callback under hanzo.app, so an authorize from there is refused. The alias is
|
|
* answered from the export on this machine (.app is HSTS-preloaded, so it is
|
|
* routed rather than resolved); hanzo.ai and IAM are answered by stubs, so
|
|
* what is measured is where the browser was sent.
|
|
*/
|
|
|
|
const ALIAS = 'cloud-hanzo-ai.hanzo.app'
|
|
|
|
let port: string
|
|
let stop: () => Promise<void>
|
|
|
|
test.beforeAll(async () => {
|
|
const served = await serveExport()
|
|
port = new URL(served.url).port
|
|
stop = served.close
|
|
})
|
|
test.afterAll(async () => stop())
|
|
|
|
/** Every address outside the export is stubbed or refused; returns where the browser went. */
|
|
async function hermetic(page: Page): Promise<{ home: string[]; seen: Seen }> {
|
|
await page.route('**/*', (route) => route.abort())
|
|
await page.route(`https://${ALIAS}/**`, async (route) => {
|
|
const u = new URL(route.request().url())
|
|
// The page may leave (the ask goes to hanzo.ai) while a file of the one it
|
|
// leaves is still being fetched; that request is gone, and so is its answer.
|
|
const res = await route.fetch({ url: `http://127.0.0.1:${port}${u.pathname}${u.search}` }).catch(() => null)
|
|
return res ? route.fulfill({ response: res }).catch(() => {}) : route.abort().catch(() => {})
|
|
})
|
|
const home: string[] = []
|
|
await page.route('https://hanzo.ai/**', (route) => {
|
|
home.push(route.request().url())
|
|
return route.fulfill({ status: 200, contentType: 'text/html', body: '<!doctype html><title>Hanzo</title>' })
|
|
})
|
|
return { home, seen: await issuer(page) }
|
|
}
|
|
|
|
// The Sites plane roots the cloud-hanzo-ai and hanzo-bot aliases at cloud.html and
|
|
// bot.html (deploy.yml), so each landing is asked the same question as the home.
|
|
for (const landing of ['/', '/cloud', '/bot']) test(`an ask typed on the alias at ${landing} is asked on hanzo.ai, and no authorize leaves the alias`, async ({ page }) => {
|
|
const sent = await hermetic(page)
|
|
await page.goto(`https://${ALIAS}${landing}`)
|
|
// Typed once React owns the composer: text filled into the exported field before
|
|
// hydration is the markup's, and hydration puts the empty one back.
|
|
await page.locator('html[data-hydrated]').waitFor({ state: 'attached' })
|
|
// The ask: the composer, wherever the page draws it (on the home, below the
|
|
// hero's sign-in, which must not leave the alias before anything is typed).
|
|
const form = page.locator('form[data-slot="prism"]:visible').first()
|
|
await form.locator('input:visible, textarea:visible').first().fill('hello there')
|
|
await form.locator('button[type=submit], button:not([type])').first().click()
|
|
await page.waitForURL(/^https:\/\/hanzo\.ai\/chat\?q=hello/)
|
|
expect(sent.home).toHaveLength(1)
|
|
expect(asked(sent.seen), 'an authorize from the alias is refused by IAM').toEqual([])
|
|
})
|
|
|
|
test('a room asked for on the alias is the same room on hanzo.ai', async ({ page }) => {
|
|
const sent = await hermetic(page)
|
|
await page.goto(`https://${ALIAS}/chat?q=hi`)
|
|
await page.waitForURL((u) => bare(u.href) === 'https://hanzo.ai/chat?q=hi')
|
|
expect(asked(sent.seen)).toEqual([])
|
|
})
|
|
|
|
test('/login on the alias is /login on hanzo.ai', async ({ page }) => {
|
|
const sent = await hermetic(page)
|
|
await page.goto(`https://${ALIAS}/login`)
|
|
await page.waitForURL((u) => bare(u.href) === 'https://hanzo.ai/login')
|
|
expect(asked(sent.seen)).toEqual([])
|
|
})
|
|
|
|
test('a product page reads its star count from the build, not from api.github.com', async ({ page }) => {
|
|
const github: string[] = []
|
|
await hermetic(page)
|
|
page.on('request', (r) => {
|
|
if (new URL(r.url()).hostname === 'api.github.com') github.push(r.url())
|
|
})
|
|
// /bot is the short explainer; the product page with its footer is /bot/details.
|
|
await page.goto(`https://${ALIAS}/bot/details`)
|
|
await page.getByTestId('oss-attribution').first().scrollIntoViewIfNeeded()
|
|
await page.waitForTimeout(1500)
|
|
expect(github, 'every reader would spend one of 60 unauthenticated requests an hour').toEqual([])
|
|
})
|