Files
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

139 lines
6.4 KiB
TypeScript

import { SELF } from 'cloudflare:test'
import { describe, expect, it } from 'vitest'
// HTTP-layer integration tests for the Cloudflare Workers entry point.
//
// The Worker is a stateless edge router: it terminates OAuth, validates tokens,
// and proxies `/mcp` to the Hono runtime. This suite covers that entry-point
// HTTP behavior (OAuth metadata, auth challenges, redirects) — the full MCP
// protocol loop is exercised against the Hono runtime in
// `tests/hono/mcp-protocol.test.ts`.
describe('MCP HTTP entry point (Cloudflare Workers)', () => {
describe('OAuth Protected Resource Metadata (RFC 9728)', () => {
it('returns metadata advertising scopes_supported for /mcp', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/.well-known/oauth-protected-resource/mcp')
expect(response.status).toBe(200)
expect(response.headers.get('content-type')).toContain('application/json')
const body = (await response.json()) as Record<string, unknown>
expect(body.resource).toBe('https://mcp.hanzo.ai/mcp')
expect(Array.isArray(body.authorization_servers)).toBe(true)
expect(Array.isArray(body.scopes_supported)).toBe(true)
expect((body.scopes_supported as string[]).length).toBeGreaterThan(0)
expect(body.bearer_methods_supported).toEqual(['header'])
})
it('returns metadata for /sse with the same shape', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/.well-known/oauth-protected-resource/sse')
expect(response.status).toBe(200)
const body = (await response.json()) as Record<string, unknown>
expect(body.resource).toBe('https://mcp.hanzo.ai/sse')
})
it('allows CORS from Insights app origins for consent-page metadata fetch', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/.well-known/oauth-protected-resource/mcp', {
headers: { Origin: 'https://us.hanzo.ai' },
})
expect(response.status).toBe(200)
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://us.hanzo.ai')
expect(response.headers.get('Vary')).toBe('Origin')
})
it('answers OPTIONS preflight for Insights app origins', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/.well-known/oauth-protected-resource/mcp', {
method: 'OPTIONS',
headers: {
Origin: 'https://us.hanzo.ai',
'Access-Control-Request-Method': 'GET',
},
})
expect(response.status).toBe(204)
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://us.hanzo.ai')
})
})
describe('Authentication challenges', () => {
it('returns 401 with WWW-Authenticate Bearer + resource_metadata when no token', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/mcp', { method: 'POST' })
expect(response.status).toBe(401)
const challenge = response.headers.get('WWW-Authenticate') || ''
expect(challenge).toContain('Bearer')
expect(challenge).toContain('resource_metadata=')
expect(challenge).toContain('/.well-known/oauth-protected-resource/mcp')
})
it('returns 401 for malformed bearer tokens (wrong prefix)', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/mcp', {
method: 'POST',
headers: { Authorization: 'Bearer not_a_insights_token' },
})
expect(response.status).toBe(401)
expect(await response.text()).toContain('Invalid token')
})
it('returns 401 for JWTs without typ: at+jwt (not an ID-JAG token)', async () => {
const headerB64 = btoa('{"typ":"JWT","alg":"HS256"}')
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
const response = await SELF.fetch('https://mcp.hanzo.ai/mcp', {
method: 'POST',
headers: { Authorization: `Bearer ${headerB64}.eyJzdWIiOiJ4In0.sig` },
})
expect(response.status).toBe(401)
expect(await response.text()).toContain('Invalid token')
})
it('passes the gate for ID-JAG access tokens (typ: at+jwt)', async () => {
// The MCP gate only checks the header. Signature verification happens
// downstream against the Insights API (`IDJagAccessTokenAuthentication`).
// Here we assert the response is NOT the gate's "Invalid token" 401 —
// anything else (init failure against a missing Insights API, etc.) is
// acceptable since this test exercises only the gate.
const headerB64 = btoa('{"typ":"at+jwt","alg":"RS256"}')
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
const payloadB64 = btoa('{"sub":"example.com:[email protected]"}')
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
const response = await SELF.fetch('https://mcp.hanzo.ai/mcp', {
method: 'POST',
headers: { Authorization: `Bearer ${headerB64}.${payloadB64}.sig` },
})
expect(await response.text()).not.toContain('Invalid token')
})
})
describe('Authorization server redirects', () => {
it('redirects /.well-known/oauth-authorization-server to the auth server', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/.well-known/oauth-authorization-server', {
redirect: 'manual',
})
expect(response.status).toBe(302)
const location = response.headers.get('location') || ''
expect(location).toContain('/.well-known/oauth-authorization-server')
})
it('rewrites /register to /oauth/register with a 307 (preserves POST)', async () => {
const response = await SELF.fetch('https://mcp.hanzo.ai/register', {
method: 'POST',
redirect: 'manual',
})
expect(response.status).toBe(307)
const location = response.headers.get('location') || ''
expect(location).toContain('/oauth/register')
})
})
})