Files
zeekay ee42369c45 Render worker for Hanzo Studio
A Node 24 process that claims render jobs from Studio's render lane
(/v1/studio/render), renders one output, asks the lane where it goes,
PUTs it there, and completes the job with the measured result.

Five ops: render.html (exact PNG/JPEG/WebP or one-page PDF),
render.document (PDF, DOCX, Markdown), render.slides (PPTX with notes,
or a 1920x1080 PDF per slide), render.composition (@hanzo/frames), and
video.edit (ffmpeg: trim, fit, join, libass captions, overlays, mixed
audio).

Every page runs in a fresh browser context behind request interception
and a per-job egress proxy, which frames' Chrome and Node go through
too; ffmpeg opens only local files through a demuxer whitelist.

The image is node:24-bookworm-slim with Debian's chromium-headless-shell
(amd64 and arm64), ffmpeg and fonts, built for both platforms by
hanzoai/ci.
2026-10-16 23:29:04 -07:00

31 lines
1.3 KiB
TypeScript

import assert from 'node:assert/strict'
import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { setTimeout as sleep } from 'node:timers/promises'
import { test } from 'node:test'
import { config } from '../src/engine.ts'
import { start } from '../src/worker.ts'
import { fake } from './fake.ts'
test('the ServiceAccount token is exchanged at IAM, and again after a 401', async () => {
const f = await fake()
const sa = join(await mkdtemp(join(tmpdir(), 'sa-')), 'token')
await writeFile(sa, 'sa-jwt\n')
const w = start(config({ HANZO_API: f.url, HANZO_IAM_URL: f.url, HANZO_SA_TOKEN: sa, RENDER_CONCURRENCY: '1' }))
try {
while (f.hits.filter((h) => h.path.endsWith('/claim')).length < 2) await sleep(50)
f.deny = 1
while (f.calls('token').length < 2) await sleep(50)
while (f.hits.filter((h) => h.path.endsWith('/claim')).length < 5) await sleep(50)
} finally {
await w.stop()
await f.close()
}
const exchange = f.calls('token')[0]
assert.equal(exchange.headers['content-type'], 'application/x-www-form-urlencoded')
const lane = f.hits.filter((h) => h.path.startsWith('/v1/studio/render/'))
assert.equal(lane[0].headers.authorization, 'Bearer iam-0')
assert.equal(lane.at(-1)!.headers.authorization, 'Bearer iam-1', 'a 401 re-exchanges the token')
})