import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { createHash } from 'node:crypto'; import { copyFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; const ROOT = resolve(process.cwd()); function makeWorkspace({ includeHuggingFaceModel = false } = {}) { const workspace = mkdtempSync(join(tmpdir(), 'wa-gen-docs-')); const srcDir = join(workspace, 'src'); const docsDir = join(workspace, 'docs'); const scriptsDir = join(workspace, 'scripts'); const dashboardDir = join(srcDir, 'dashboard'); mkdirSync(srcDir, { recursive: true }); mkdirSync(docsDir, { recursive: true }); mkdirSync(scriptsDir, { recursive: true }); mkdirSync(dashboardDir, { recursive: true }); copyFileSync(join(ROOT, 'package.json'), join(workspace, 'package.json')); copyFileSync(join(ROOT, 'src/models.js'), join(srcDir, 'models.js')); copyFileSync(join(ROOT, 'scripts/gen-docs-models.js'), join(scriptsDir, 'gen-docs-models.js')); copyFileSync(join(ROOT, 'docs/index.html'), join(docsDir, 'index.html')); if (!includeHuggingFaceModel) { return workspace; } const modelsPath = join(srcDir, 'models.js'); let source = readFileSync(modelsPath, 'utf8'); const marker = source.indexOf('// Build reverse lookup'); if (marker < 0) { throw new Error('unexpected models.js structure: missing // Build reverse lookup marker'); } const objectEndMatch = source.match(/\r?\n\};\r?\n\r?\n\/\/ Build reverse lookup/); const objectEnd = objectEndMatch ? objectEndMatch.index : -1; if (objectEnd < 0) { throw new Error('unexpected models.js structure: missing end of MODELS object'); } const patch = "\n 'model-huggingface-custom': { name: 'model-huggingface-custom', provider: 'huggingface', enumValue: 999999, modelUid: 'model-huggingface-custom', credit: 1 },\n"; source = `${source.slice(0, objectEnd + 1)}${patch}${source.slice(objectEnd + 1)}`; writeFileSync(modelsPath, source, 'utf8'); return workspace; } function runGenerator(workspace) { const result = spawnSync(process.execPath, ['scripts/gen-docs-models.js'], { cwd: workspace, encoding: 'utf8', timeout: 20000, }); return result; } function parseModels(htmlText) { const start = htmlText.indexOf('const MODELS = ['); assert.ok(start >= 0, 'MODELS array anchor not found'); const open = htmlText.indexOf('[', start); const close = htmlText.indexOf(' ];', start); assert.ok(close > open, 'MODELS array end marker not found'); const literal = htmlText.slice(open, close + 3); const models = new Function(`return ${literal}`)(); assert.ok(Array.isArray(models), 'MODELS should be an array'); return models; } function readDocsHtml(workspace) { return readFileSync(join(workspace, 'docs', 'index.html'), 'utf8'); } function sha256(content) { return createHash('sha256').update(content).digest('hex'); } describe('scripts/gen-docs-models.js', () => { it('regenerates a parseable MODELS block and keeps HTML shape', () => { const workspace = makeWorkspace(); try { const result = runGenerator(workspace); assert.equal(result.status, 0, `generator exit: ${result.status}, stderr=${result.stderr}`); const html = readDocsHtml(workspace); const models = parseModels(html); assert.ok(html.includes(''), 'generated docs should include html close tag'); assert.equal(typeof html, 'string'); assert.ok(models.length >= 100, `expected at least 100 models, got ${models.length}`); assert.ok(models.every(model => model?.k && model?.p && typeof model.c === 'number')); // Pick a stable, non-deprecated marker. `adaptive` was removed from // /v1/models in v2.0.51 (#109) — upstream rejects its UID — so it // can't be used as a presence anchor. claude-sonnet-4.6 is the // current default and is unlikely to be deprecated near-term. assert.ok(models.some(model => model.k === 'claude-sonnet-4.6')); } finally { rmSync(workspace, { recursive: true, force: true }); } }); it('is idempotent when run repeatedly on the same input', () => { const workspace = makeWorkspace(); try { const first = runGenerator(workspace); assert.equal(first.status, 0, `first run failed: ${first.stderr}`); const firstHtml = readDocsHtml(workspace); const second = runGenerator(workspace); assert.equal(second.status, 0, `second run failed: ${second.stderr}`); const secondHtml = readDocsHtml(workspace); assert.equal(sha256(firstHtml), sha256(secondHtml), 'generator output changed between runs'); assert.deepEqual(parseModels(firstHtml), parseModels(secondHtml)); } finally { rmSync(workspace, { recursive: true, force: true }); } }); it('supports unknown provider values when models are merged in', () => { const workspace = makeWorkspace({ includeHuggingFaceModel: true }); try { const result = runGenerator(workspace); assert.equal(result.status, 0, `generator failed on unknown provider: ${result.stderr}`); const html = readDocsHtml(workspace); const models = parseModels(html); assert.ok(models.some(model => model.k === 'model-huggingface-custom' && model.p === 'huggingface')); } finally { rmSync(workspace, { recursive: true, force: true }); } }); });