Files
agency/.pnpm-store/v3/files/6f/b4d3dab1b9ce334beeba682b7dbdb3d35f9f1cb3330a73df34b140f06a01b87be5e5e4b29b5a47259ac6eb3280202321d163431cd9d52a79fc446722dfcb45
hanzo-agent 5af6960b87 run project
Run: sess_f37640604ae254cb4d11dc6217f7b721
2026-09-30 00:04:56 +00:00

208 lines
7.6 KiB
Plaintext

// @vitest-environment jsdom
//
// One page, one stream. The shape under test is the one hanzo.ai serves: the
// component library mounts its telemetry, the app mounts its own provider, and
// each asks for a client for the same (host, product). Measured on production,
// that page put two batches on api.hanzo.ai/v1/event in the same frame — one
// carrying the publishable key (200, accepted 2) and one carrying no
// Authorization at all (401, ingest_key_required) — because the library asked
// first and had no key. Both batches held a $pageview for the same page.
import { describe, it, expect, beforeEach } from 'vitest'
import { Analytics, createAnalytics, resetClients } from './core'
import { PAGEVIEW } from './events'
import type { Transport, WireEvent } from './types'
const HOST = 'https://api.hanzo.ai'
const KEY = 'pk-CmfLA2K6kvsP'
interface Sent {
token?: string
ingestKey?: string
batch: WireEvent[]
}
class FakeTransport implements Transport {
sent: Sent[] = []
send(_url: string, body: string, opts: { token?: string; ingestKey?: string }) {
this.sent.push({
token: opts.token,
ingestKey: opts.ingestKey,
batch: (JSON.parse(body) as { batch: WireEvent[] }).batch ?? [],
})
}
get all(): WireEvent[] {
return this.sent.flatMap((s) => s.batch)
}
get pageviews(): WireEvent[] {
return this.all.filter((e) => e.event === PAGEVIEW)
}
}
let tx: FakeTransport
/** The library's ask: product from the hostname, no key — the env that carries
* one is not set on this build. It asks FIRST, as it does in the tree. */
const library = () =>
createAnalytics({ product: 'site', host: HOST, transport: tx, flushIntervalMs: 999999 })
/** The app's ask: the same stream, with the credential. */
const app = () =>
createAnalytics({
product: 'site',
host: HOST,
getToken: () => KEY,
transport: new FakeTransport(),
flushIntervalMs: 999999,
})
beforeEach(() => {
tx = new FakeTransport()
resetClients() // each `it` is its own page load
window.history.replaceState(null, '', '/pricing')
})
describe('one stream, one client', () => {
it('hands both callers the same client', () => {
expect(app()).toBe(library())
})
it('sends one batch, not two', () => {
library().pageview('/pricing')
app().capture('pricing_viewed')
app().flush()
expect(tx.sent).toHaveLength(1)
expect(tx.all).toHaveLength(2)
})
it('carries the credential even though the caller without one asked first', () => {
library().pageview('/pricing')
app().flush()
expect(tx.sent).toHaveLength(1)
expect(tx.sent[0].token).toBe(KEY)
})
it('brings up the error plane on the key it adopts', () => {
const a = createAnalytics({ product: 'console', host: HOST, transport: tx })
expect(a.errorPlaneEnabled).toBe(false)
createAnalytics({ product: 'console', host: HOST, ingestKey: KEY })
expect(a.errorPlaneEnabled).toBe(true)
})
it('keeps the first credential rather than overwriting it', () => {
createAnalytics({ product: 'site', host: HOST, ingestKey: KEY, transport: tx })
createAnalytics({ product: 'site', host: HOST, ingestKey: 'pk-second' }).capture('x')
createAnalytics({ product: 'site', host: HOST }).flush()
expect(tx.sent[0].ingestKey).toBe(KEY)
})
// A provider whose ownership resolves in an effect renders once saying
// `enabled: false` and then again saying true. Measured on hanzo.ai: the
// library's first render built a disabled client, and a registry that kept it
// handed that one to the app too — the page went silent, which is worse than
// the double it replaced.
it('does not let a disabled client take the stream', () => {
const off = createAnalytics({ product: 'site', host: HOST, enabled: false })
const on = createAnalytics({ product: 'site', host: HOST, transport: tx, flushIntervalMs: 999999 })
expect(on).not.toBe(off)
on.pageview('/pricing')
on.flush()
expect(tx.pageviews).toHaveLength(1)
expect(createAnalytics({ product: 'site', host: HOST, getToken: () => KEY })).toBe(on)
})
it('gives every disabled caller its own inert client', () => {
const a = createAnalytics({ product: 'site', host: HOST, enabled: false, transport: tx })
const b = createAnalytics({ product: 'site', host: HOST, enabled: false, transport: tx })
expect(a).not.toBe(b)
a.pageview('/pricing')
b.pageview('/pricing')
a.flush()
b.flush()
expect(tx.sent).toHaveLength(0)
})
it('keeps separate streams separate', () => {
expect(createAnalytics({ product: 'chat', host: HOST })).not.toBe(
createAnalytics({ product: 'site', host: HOST }),
)
expect(createAnalytics({ product: 'site', host: '' })).not.toBe(
createAnalytics({ product: 'site', host: HOST }),
)
})
it('leaves new Analytics outside the registry', () => {
const own = new Analytics({ product: 'site', host: HOST })
expect(createAnalytics({ product: 'site', host: HOST })).not.toBe(own)
})
// The registry is page state, and only a new page may drop it. A consumer that
// dropped it from its own code — @hanzogui/telemetry did this on every provider
// unmount — gives the next caller a client that has never seen this page, and
// the view is counted a second time. So the reset is named here, where a test
// runner can say "new page" and nothing else can say it by accident.
it('drops the page clients on reset, and only then', () => {
const before = createAnalytics({ product: 'site', host: HOST })
expect(createAnalytics({ product: 'site', host: HOST })).toBe(before)
resetClients()
expect(createAnalytics({ product: 'site', host: HOST })).not.toBe(before)
})
// The package publishes two entries and the react one carries its own copy of
// this module, so on a page that imports both there are two module scopes. A
// registry held in module scope is per-copy and would not dedupe the only case
// that matters. Two distinct instances of this module, one client.
it('holds one registry across separate copies of this module', async () => {
const one = (await import('./core?copy=1')) as typeof import('./core')
const two = (await import('./core?copy=2')) as typeof import('./core')
expect(one).not.toBe(two)
expect(one.createAnalytics({ product: 'site', host: HOST })).toBe(
two.createAnalytics({ product: 'site', host: HOST, ingestKey: KEY }),
)
})
})
describe('one pageview per view', () => {
it('counts a page once when two emitters both count the first view', () => {
library().pageview('/pricing') // the library's route hook, on mount
app().pageview() // the app provider's autoPageview, same frame
app().flush()
expect(tx.pageviews).toHaveLength(1)
})
it('counts a page once even through one client', () => {
const a = library()
a.pageview('/pricing')
a.pageview('/pricing')
a.flush()
expect(tx.pageviews).toHaveLength(1)
})
it('counts a route change', () => {
const a = library()
a.pageview('/pricing')
window.history.pushState(null, '', '/docs')
a.pageview('/docs')
a.flush()
expect(tx.pageviews.map((e) => e.path)).toEqual(['/pricing', '/docs'])
})
it('counts a return to a page already seen', () => {
const a = library()
a.pageview('/pricing')
a.pageview('/docs')
a.pageview('/pricing')
a.flush()
expect(tx.pageviews).toHaveLength(3)
})
it('counts a query change on one path', () => {
const a = library()
window.history.replaceState(null, '', '/search?q=a')
a.pageview('/search')
window.history.replaceState(null, '', '/search?q=b')
a.pageview('/search')
a.flush()
expect(tx.pageviews).toHaveLength(2)
})
})