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.
136 lines
5.2 KiB
TypeScript
136 lines
5.2 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
||
import { api, iam } from '@hanzo/app/lib/api'
|
||
import { configure, where } from '@hanzo/app/where'
|
||
|
||
/**
|
||
* WHERE THE PLATFORM IS, ASKED AT THE MOMENT OF ASKING.
|
||
*
|
||
* `e2e/gates/studio-host.spec.ts` proves the studio's turn lands on
|
||
* api.hanzo.ai by serving the export as the apex and reading the wire. That is
|
||
* the important half and it can only measure ONE host: the gate's page is
|
||
* hanzo.ai, so the branch a developer runs — and the branch that made eight
|
||
* modules write their own copy of this derivation before it had one home — is
|
||
* never exercised there.
|
||
*
|
||
* So this spec measures the derivation itself, and every case is one of
|
||
* `lib/hanzo/api.ts`'s own claims:
|
||
*
|
||
* the apex an absolute https://api.hanzo.ai, because the gateway
|
||
* already admits that origin
|
||
* a dev host the page's own origin, because api.hanzo.ai admits an
|
||
* origin by allowlist and by a portless https proof, and
|
||
* localhost satisfies neither — `next.config`'s `/v1` rewrite
|
||
* is what answers there, and only a same-origin caller
|
||
* reaches a rewrite
|
||
* the prerender no `window` at all, which is the export being written
|
||
* an override whatever it names, with no trailing slash, so
|
||
* `${api()}/v1/…` cannot come out doubled
|
||
*
|
||
* A FUNCTION IS THE SUBJECT, NOT A CONSTANT. Every module that got this wrong
|
||
* got it wrong the same way: a module-scope `const` evaluated while the static
|
||
* export prerenders, where there is no `window`, freezing one answer into the
|
||
* bundle for every host it is later served from. Each case below calls `api()`
|
||
* after the world it describes exists, which a `const` cannot survive — and
|
||
* that is the regression this file exists to fail on.
|
||
*/
|
||
|
||
/** The world a page is open in, for the length of one call. */
|
||
function at(hostname: string, origin: string): () => void {
|
||
const global = globalThis as { window?: unknown }
|
||
const had = 'window' in global
|
||
const was = global.window
|
||
global.window = { location: { hostname, origin } }
|
||
return () => {
|
||
if (had) global.window = was
|
||
else delete global.window
|
||
}
|
||
}
|
||
|
||
/** The override the host states with `configure({ api })`: set, or genuinely absent. */
|
||
function override(value: string | undefined): () => void {
|
||
const was = where().api
|
||
configure({ api: value })
|
||
return () => configure({ api: was })
|
||
}
|
||
|
||
test.describe('the API host is derived where the page is open', () => {
|
||
test('the apex, its subdomains and hanzo.team address api.hanzo.ai, never themselves', async () => {
|
||
const unset = override(undefined)
|
||
try {
|
||
for (const host of ['hanzo.ai', 'cloud.hanzo.ai', 'dev.hanzo.ai', 'hanzo.team', 'hanzo.bot', 'hanzo-bot.hanzo.app']) {
|
||
const restore = at(host, `https://${host}`)
|
||
try {
|
||
expect(api(), `${host} asks the gateway`).toBe('https://api.hanzo.ai')
|
||
// The defect this replaced, stated as an absence: a bare `/v1/…` path
|
||
// on the published site is a read of a static host with no /v1 on it.
|
||
expect(api()).not.toBe(`https://${host}`)
|
||
} finally {
|
||
restore()
|
||
}
|
||
}
|
||
} finally {
|
||
unset()
|
||
}
|
||
})
|
||
|
||
test('a dev host stays on its own origin, so the /v1 rewrite is what answers', async () => {
|
||
const unset = override(undefined)
|
||
try {
|
||
for (const [host, origin] of [
|
||
['localhost', 'http://localhost:8084'],
|
||
['127.0.0.1', 'http://127.0.0.1:3000'],
|
||
// A preview host is not hanzo.ai either: the gateway pins
|
||
// `access-control-allow-origin` to the apex exactly, so an absolute
|
||
// address from anywhere else fails its preflight.
|
||
['hanzo-ai.pages.dev', 'https://hanzo-ai.pages.dev'],
|
||
]) {
|
||
const restore = at(host, origin)
|
||
try {
|
||
expect(api(), `${host} keeps its own origin`).toBe(origin)
|
||
} finally {
|
||
restore()
|
||
}
|
||
}
|
||
} finally {
|
||
unset()
|
||
}
|
||
})
|
||
|
||
test('the prerender has no window, and answers the published address', async () => {
|
||
const unset = override(undefined)
|
||
try {
|
||
expect('window' in (globalThis as { window?: unknown }), 'the runner is not a browser').toBe(false)
|
||
expect(api()).toBe('https://api.hanzo.ai')
|
||
} finally {
|
||
unset()
|
||
}
|
||
})
|
||
|
||
test('an override wins everywhere, and brings no trailing slash with it', async () => {
|
||
const set = override('https://api.example.test/')
|
||
try {
|
||
expect(api(), 'the slash is the caller’s to add').toBe('https://api.example.test')
|
||
const restore = at('hanzo.ai', 'https://hanzo.ai')
|
||
try {
|
||
expect(api(), 'an override outranks the host').toBe('https://api.example.test')
|
||
} finally {
|
||
restore()
|
||
}
|
||
} finally {
|
||
set()
|
||
}
|
||
})
|
||
|
||
test('identity is a second address, and it is not the gateway', async () => {
|
||
const was = where().iam
|
||
configure({ iam: undefined })
|
||
try {
|
||
expect(iam()).toBe('https://hanzo.id')
|
||
configure({ iam: 'https://lux.id/' })
|
||
expect(iam(), 'one setting, one spelling, no trailing slash').toBe('https://lux.id')
|
||
} finally {
|
||
configure({ iam: was })
|
||
}
|
||
})
|
||
})
|