← Files CastReaderARCHIVED FILE
assets/voice-app/tests/app.test.mjs
22.1 KB · Oct 3, 2026 · 06:35 UTC
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, mkdir, readdir, readFile, writeFile, rm, lstat, symlink } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { createHash, randomUUID } from 'node:crypto';
import { request as httpRequest } from 'node:http';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
// The same suite runs in the monorepo and at tests/ in the extracted starter.
const source = new URL(existsSync(new URL('../public/examples/flashcards/generate.mjs', import.meta.url)) ? '../public/examples/flashcards/' : '../', import.meta.url);
const { runCards, validateCards } = await import(new URL('generate.mjs', source));
const { startServer } = await import(new URL('server.mjs', source));
const voiceId = 'voice_' + 'a'.repeat(32);
const cards = [{ id: 'one-en', language: 'en', text: 'Hello.' }];
function fake() {
const requests = [];
const jobs = new Map();
const client = {
async createJob(body, options) {
requests.push(options.idempotencyKey);
if (!jobs.has(options.idempotencyKey)) jobs.set(options.idempotencyKey, { id: 'job-' + jobs.size, status: 'succeeded' });
return jobs.get(options.idempotencyKey);
},
async getJob(id) { return [...jobs.values()].find(job => job.id === id); },
async getJobAudio() { return { audio: Buffer.from('fixture-audio-not-for-publication'), chargedUSD: '0.000048', billableCharacters: 6, trialCharacters: 0 }; },
};
return { client, requests, jobs };
}
async function directory(fn) {
const dir = await mkdtemp(join(tmpdir(), 'voice-cards-test-'));
try { await fn(dir); } finally { await rm(dir, { recursive: true, force: true }); }
}
test('validate whole input before work; preserve exact language and NFC metering', () => {
assert.equal(validateCards([{ id: 'nfc-en', language: 'en', text: ' e\u0301\r\nHi ' }])[0].text, 'é\nHi');
for (const value of [[], [...cards, ...cards], [{ ...cards[0], id: '../escape' }], [{ ...cards[0], language: 'xx' }], [{ ...cards[0], text: '\ud800' }], [{ ...cards[0], text: 'a'.repeat(501) }]]) assert.throws(() => validateCards(value));
});
test('completed rerun uses local checksum and makes no further API requests', () => directory(async output => {
const { client, requests } = fake();
const args = { client, cards, output, voiceId, log() {} };
const result = await runCards(args);
assert.equal(result.rows.length, 1);
const original = await readFile(join(output, 'one-en.mp3'));
client.getJob = client.getJobAudio = async () => { throw new Error('No API access permitted'); };
await runCards(args);
assert.equal(requests.length, 1);
assert.deepEqual(await readFile(join(output, 'one-en.mp3')), original);
}));
test('unknown POST result persists and retries the same key, not a replacement job', () => directory(async output => {
const { client, requests, jobs } = fake();
const original = client.createJob;
let first = true;
client.createJob = async (...args) => { const result = await original(...args); if (first) { first = false; throw new Error('network lost after acceptance'); } return result; };
const args = { client, cards, output, voiceId, log() {} };
await assert.rejects(runCards(args), /network lost/);
await runCards(args);
assert.equal(requests.length, 2);
assert.equal(requests[0], requests[1]);
assert.equal(jobs.size, 1);
}));
test('download interruption and corrupt local files recover through the original job', () => directory(async output => {
const { client, requests } = fake();
const download = client.getJobAudio;
client.getJobAudio = async () => { throw new Error('download interrupted'); };
const args = { client, cards, output, voiceId, log() {} };
await assert.rejects(runCards(args), /download interrupted/);
client.getJobAudio = download;
await runCards(args);
await writeFile(join(output, 'one-en.mp3'), 'corrupt');
await runCards(args);
assert.equal(requests.length, 1);
}));
test('changed input fails closed; existing manifest and output are preserved', () => directory(async output => {
const { client, requests } = fake();
const args = { client, cards, output, voiceId, log() {} };
await runCards(args);
const original = await readFile(join(output, 'manifest.json'));
await assert.rejects(runCards({ ...args, cards: [{ ...cards[0], text: 'Different text.' }] }), /Inputs or voice changed/);
assert.deepEqual(await readFile(join(output, 'manifest.json')), original);
assert.equal(requests.length, 1);
}));
test('failed job is never automatically replaced', () => directory(async output => {
const { client, requests } = fake();
const create = client.createJob;
client.createJob = async (...args) => Object.assign(await create(...args), { status: 'failed', errorCode: 'worker_unavailable' });
const args = { client, cards, output, voiceId, log() {} };
await assert.rejects(runCards(args), /worker_unavailable/);
await assert.rejects(runCards(args), /worker_unavailable/);
assert.equal(requests.length, 1);
}));
test('concurrent invocation refuses a locked folder', () => directory(async output => {
await writeFile(join(output, '.running'), 'existing process');
await assert.rejects(runCards({ client: fake().client, cards, output, voiceId }), /locked/);
assert.equal(await readFile(join(output, '.running'), 'utf8'), 'existing process');
}));
function webFake() {
const fixture = fake();
const calls = [];
for (const key of ['createJob', 'getJob', 'getJobAudio']) {
const original = fixture.client[key];
fixture.client[key] = (...args) => { calls.push(key); return original(...args); };
}
Object.assign(fixture.client, {
async models() { calls.push('models'); return { data: [{ id: 'clone-v1', languages: ['en', 'zh'], max_characters: 500 }] }; },
async voices() { calls.push('voices'); return { data: [{ id: voiceId, name: 'Authorized fixture', status: 'ready', default_voice_key: 'narrator', secret: 'do-not-return' }, { id: 'voice_' + 'b'.repeat(32), status: 'pending' }] }; },
async request(path, options) {
calls.push(path);
assert.equal(path, '/usage/estimate');
assert.equal(options.method, 'POST'); assert.equal(options.retry, false);
const n = Array.from(options.body.text).length;
return { response: { async json() { return { billable_characters: n, trial_characters: n, maximum_charge_usd: (n * 8 / 1000000).toFixed(6), estimated_charge_usd: '0.000000', reservation_created: false, balance_sufficient: true }; } } };
},
});
return { ...fixture, calls };
}
async function browser(app) {
const response = await fetch(app.origin + '/api/session', { headers: { Origin: app.origin } });
assert.equal(response.status, 200);
const session = await response.json();
return { session, async request(path, { body, headers = {}, raw, method = body || raw ? 'POST' : 'GET' } = {}) {
return fetch(app.origin + path, { method, headers: { Origin: app.origin, 'X-CSRF-Token': session.csrfToken, ...(body || raw ? { 'Content-Type': 'application/json' } : {}), ...headers }, ...(body || raw ? { body: raw || JSON.stringify(body) } : {}) });
} };
}
async function local(fn, fixture = webFake()) {
return directory(async dataDir => {
const app = await startServer({ port: 0, dataDir, client: fixture.client, waitMs: 1, timeoutMs: 10 });
try { await fn({ app, dataDir, fixture, ...await browser(app) }); }
finally { await app.close(); }
});
}
async function plan(request, input = {}) {
const response = await request('/api/batches', { body: { cards, voiceId, title: 'Offline batch', ...input } });
assert.equal(response.status, 201, await response.clone().text());
return response.json();
}
async function settled(request, id) {
for (let i = 0; i < 200; i++) {
const response = await request(`/api/batches/${id}`);
assert.equal(response.status, 200);
const value = await response.json();
if (value.status !== 'running') return value;
await new Promise(done => setTimeout(done, 5));
}
assert.fail('Local fake batch did not settle.');
}
async function start(request, id) {
const response = await request(`/api/batches/${id}/start`, { body: { confirm: true } });
assert.equal(response.status, 202, await response.clone().text());
}
test('local app binds loopback; exact Host, Origin and CSRF guard every API mutation', () => local(async ({ app, request, session, fixture }) => {
assert.equal(app.server.address().address, '127.0.0.1');
const page = await fetch(app.origin);
assert.equal(page.status, 200);
assert.match(page.headers.get('content-security-policy'), /frame-ancestors 'none'/);
assert.equal(page.headers.get('cache-control'), 'no-store');
assert.equal((await fetch(app.origin + '/api/session')).status, 403);
for (const headers of [{ Host: 'evil.example' }, { Host: `localhost:${app.server.address().port}` }, { Origin: 'null' }, { Origin: 'https://evil.example' }, { 'X-CSRF-Token': '' }, { 'Sec-Fetch-Site': 'same-site' }, { 'X-Forwarded-Host': 'evil.example' }]) {
const status = await new Promise((done, reject) => {
const req = httpRequest(app.origin + '/api/batches', { method: 'POST', headers: { Origin: app.origin, 'X-CSRF-Token': session.csrfToken, 'Content-Type': 'application/json', ...headers } }, res => { res.resume(); done(res.statusCode); });
req.on('error', reject); req.end(JSON.stringify({ cards, voiceId, title: 'Blocked' }));
});
assert.equal(status, 403, JSON.stringify(headers));
}
const noOrigin = await fetch(app.origin + '/api/batches', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': (await browser(app)).session.csrfToken }, body: '{}' });
assert.equal(noOrigin.status, 403);
assert.equal((await request('/api/batches', { method: 'OPTIONS' })).status, 404);
assert.deepEqual(fixture.calls, []);
}));
test('only allowlisted UI and batch resources can be read; no paths or URLs are accepted', () => local(async ({ app, request, session, fixture }) => {
for (const path of ['/server.mjs', '/.env', '/state.json', '/api/batches/../../state.json', '/api/batches?path=/etc/passwd', '/api/proxy?url=https://evil.example']) {
// Raw HTTP avoids fetch's dot-segment normalization.
const status = await new Promise((done, reject) => {
const req = httpRequest(app.origin, { path, headers: { Origin: app.origin, 'X-CSRF-Token': session.csrfToken } }, res => { res.resume(); done(res.statusCode); });
req.on('error', reject); req.end();
});
assert.equal(status, 404);
}
for (const extra of [{ apiKey: 'secret' }, { output: '/tmp/escape' }, { baseURL: 'https://evil.example' }, { path: '../escape' }]) assert.equal((await request('/api/batches', { body: { cards, voiceId, title: 'Unsafe', ...extra } })).status, 400);
assert.deepEqual(fixture.calls, []);
}));
test('validate complete input and request size before API access, cap 500 code points', () => local(async ({ request, fixture }) => {
for (const invalid of [[], Array.from({ length: 201 }, (_, n) => ({ ...cards[0], id: `card-${n}` })), [...cards, ...cards], [{ ...cards[0], id: 'constructor' }], [{ ...cards[0], id: '../escape' }], [{ ...cards[0], language: 'xx' }], [{ ...cards[0], text: '\ud800' }], [{ ...cards[0], text: 'a'.repeat(501) }], [{ ...cards[0], voiceId }]]) {
assert.equal((await request('/api/batches', { body: { cards: invalid, voiceId, title: 'Invalid' } })).status, 400);
}
assert.equal((await request('/api/batches', { raw: '{broken' })).status, 400);
assert.equal((await request('/api/batches', { raw: JSON.stringify({ text: 'x'.repeat(530000) }) })).status, 413);
assert.equal((await request('/api/batches', { body: {}, headers: { 'Content-Type': 'text/plain' } })).status, 415);
assert.deepEqual(fixture.calls, []);
const value = await plan(request, { cards: [{ ...cards[0], text: '😀'.repeat(500) }] });
assert.equal(value.estimate.characters, 500);
}));
test('backend-only ready voice discovery; plans aggregate maximum cost without double counting trial', () => local(async ({ request, fixture }) => {
const catalog = await (await request('/api/catalog')).json();
assert.equal(catalog.voices.length, 1);
assert.equal(JSON.stringify(catalog).includes('do-not-return'), false);
assert.equal((await request('/api/batches', { body: { cards, voiceId: 'voice_' + 'b'.repeat(32), title: 'Not ready' } })).status, 400);
const value = await plan(request, { cards: [...cards, { id: 'two-zh', language: 'zh', text: '你好。' }] });
assert.equal(value.estimate.maximumChargeUSD, '0.000072');
assert.equal(value.status, 'planned'); assert.equal(fixture.requests.length, 0);
assert.equal(fixture.calls.filter(call => call === '/usage/estimate').length, 2);
assert.equal((await request(`/api/batches/${value.id}/start`, { body: {} })).status, 400);
assert.equal((await request(`/api/batches/${value.id}/start`, { body: { confirm: true, cards } })).status, 400);
assert.equal(fixture.requests.length, 0);
}));
test('generate, progress, replay and exports; completed resume never submits or downloads again', () => local(async ({ request, fixture, dataDir }) => {
const value = await plan(request);
await start(request, value.id);
const result = await settled(request, value.id);
assert.equal(result.status, 'complete'); assert.equal(result.rows[0].saved, true);
const before = fixture.calls.slice();
const response = await request(`/api/batches/${value.id}/audio/one-en`);
const audio = Buffer.from(await response.arrayBuffer());
assert.equal(createHash('sha256').update(audio).digest('hex'), result.rows[0].sha256);
const manifest = await (await request(`/api/batches/${value.id}/manifest`)).json();
assert.equal(manifest.rows[0].text, cards[0].text);
for (const privateValue of ['jobId', 'idempotencyKey', voiceId]) assert.equal(JSON.stringify(manifest).includes(privateValue), false);
const archive = await request(`/api/batches/${value.id}/archive`);
const zipBytes = Buffer.from(await archive.arrayBuffer()), entries = new Map();
let offset = 0;
while (zipBytes.readUInt32LE(offset) === 0x04034b50) {
assert.equal(zipBytes.readUInt16LE(offset + 8), 0, 'portable ZIP uses store format');
const size = zipBytes.readUInt32LE(offset + 18), nameLength = zipBytes.readUInt16LE(offset + 26), extraLength = zipBytes.readUInt16LE(offset + 28);
const name = zipBytes.subarray(offset + 30, offset + 30 + nameLength).toString();
offset += 30 + nameLength + extraLength;
entries.set(name, zipBytes.subarray(offset, offset + size)); offset += size;
}
assert.equal(zipBytes.readUInt32LE(offset), 0x02014b50);
assert.deepEqual([...entries.keys()], ['manifest.json', 'one-en.mp3']);
assert.deepEqual(entries.get('one-en.mp3'), audio);
assert.deepEqual(JSON.parse(entries.get('manifest.json')), manifest);
assert.deepEqual(fixture.calls, before);
await start(request, value.id); await settled(request, value.id);
assert.deepEqual(fixture.calls, before);
assert.equal((await lstat(join(dataDir, 'batches', value.id, 'batch.json'))).mode & 0o777, 0o600);
}));
test('unknown submission resumes the same key after server restart; errors never reveal secrets', () => directory(async dataDir => {
const fixture = webFake(), create = fixture.client.createJob;
fixture.client.createJob = async (...args) => { await create(...args); throw new Error('secret-key-value PRIVATE upstream payload'); };
let app = await startServer({ port: 0, dataDir, client: fixture.client, waitMs: 1 });
let api = await browser(app), value;
try {
value = await plan(api.request); await start(api.request, value.id);
const paused = await settled(api.request, value.id);
assert.equal(paused.status, 'paused'); assert.equal(paused.rows[0].status, 'submission_unknown');
assert.equal(JSON.stringify(paused).includes('secret-key-value'), false);
assert.equal((await readFile(join(dataDir, 'batches', value.id, 'execution.json'), 'utf8')).includes('secret-key-value'), false);
} finally { await app.close(); }
fixture.client.createJob = create;
app = await startServer({ port: 0, dataDir, client: fixture.client, waitMs: 1 });
try {
const next = await browser(app);
assert.notEqual(next.session.csrfToken, api.session.csrfToken);
assert.equal((await (await next.request('/api/batches')).json()).batches[0].id, value.id);
await start(next.request, value.id); assert.equal((await settled(next.request, value.id)).status, 'complete');
assert.equal(fixture.requests[0], fixture.requests[1]); assert.equal(fixture.jobs.size, 1);
} finally { await app.close(); }
}));
test('progress and duplicate starts are serialized, timeout resumes the original queued job', () => local(async ({ request, fixture }) => {
const create = fixture.client.createJob;
fixture.client.createJob = async (...args) => Object.assign(await create(...args), { status: 'queued' });
const first = await plan(request), second = await plan(request);
const responses = await Promise.all([first.id, first.id, second.id].map(id => request(`/api/batches/${id}/start`, { body: { confirm: true } })));
assert.deepEqual(responses.map(res => res.status).sort(), [202, 409, 409]);
const paused = await settled(request, first.id);
assert.equal(paused.status, 'paused'); assert.equal(fixture.requests.length, 1);
for (const job of fixture.jobs.values()) job.status = 'succeeded';
await start(request, first.id); assert.equal((await settled(request, first.id)).status, 'complete');
assert.equal(fixture.requests.length, 1);
}));
test('download-only routes fail closed on corruption or symlinks; resume recovers original audio', () => local(async ({ request, fixture, dataDir }) => {
const value = await plan(request); await start(request, value.id); await settled(request, value.id);
const output = join(dataDir, 'batches', value.id, 'one-en.mp3');
await writeFile(output, 'corrupt');
const before = fixture.calls.slice();
assert.equal((await request(`/api/batches/${value.id}/audio/one-en`)).status, 409);
assert.equal((await request(`/api/batches/${value.id}/archive`)).status, 409);
assert.deepEqual(fixture.calls, before);
await start(request, value.id); assert.equal((await settled(request, value.id)).status, 'complete');
assert.equal(fixture.requests.length, 1);
await rm(output); await symlink(join(dataDir, 'batches', value.id, 'state.json'), output);
assert.equal((await request(`/api/batches/${value.id}/audio/one-en`)).status, 409);
}));
test('terminal failed jobs, batch locks, missing checkpoints and app locks are preserved', () => local(async ({ request, fixture, dataDir }) => {
await assert.rejects(startServer({ port: 0, dataDir, client: fixture.client }), /Another app/);
const create = fixture.client.createJob;
fixture.client.createJob = async (...args) => Object.assign(await create(...args), { status: 'failed', errorCode: 'worker_unavailable' });
const value = await plan(request); await start(request, value.id); await settled(request, value.id);
await start(request, value.id); const result = await settled(request, value.id);
assert.equal(result.rows[0].status, 'failed'); assert.equal(fixture.requests.length, 1);
const dir = join(dataDir, 'batches', value.id);
await writeFile(join(dir, '.running'), 'do-not-delete');
assert.equal((await request(`/api/batches/${value.id}/start`, { body: { confirm: true } })).status, 409);
assert.equal(await readFile(join(dir, '.running'), 'utf8'), 'do-not-delete');
await rm(join(dir, '.running')); await rm(join(dir, 'state.json'));
assert.equal((await request(`/api/batches/${value.id}/start`, { body: { confirm: true } })).status, 409);
assert.equal(fixture.requests.length, 1);
}));
test('history isolates incomplete, malformed and linked batches, preserves them and reveals only a count', () => local(async ({ request, dataDir, fixture }) => {
const good = await plan(request);
await start(request, good.id); await settled(request, good.id);
const root = join(dataDir, 'batches');
const [emptyId, malformedId, metadataId, linkedId] = Array.from({ length: 4 }, () => randomUUID());
for (const id of [emptyId, malformedId, metadataId]) await mkdir(join(root, id));
const secret = 'PRIVATE-RECOVERY-DATA-MUST-NOT-LEAK';
await writeFile(join(root, emptyId, 'state.json'), secret);
const malformed = `{invalid-json ${secret}`;
await writeFile(join(root, malformedId, 'batch.json'), malformed);
const metadata = JSON.stringify({ id: metadataId, title: secret, createdAt: null });
await writeFile(join(root, metadataId, 'batch.json'), metadata);
await symlink(join(root, good.id), join(root, linkedId));
const calls = fixture.calls.slice();
const response = await request('/api/batches');
assert.equal(response.status, 200);
const listing = await response.json();
assert.deepEqual(Object.keys(listing).sort(), ['batches', 'skippedBatchCount']);
assert.equal(listing.skippedBatchCount, 4);
assert.deepEqual(listing.batches, [{ id: good.id, title: good.title, createdAt: good.createdAt }]);
const serialized = JSON.stringify(listing);
for (const privateValue of [secret, dataDir, emptyId, malformedId, metadataId, linkedId]) assert.equal(serialized.includes(privateValue), false);
assert.equal((await (await request(`/api/batches/${good.id}`)).json()).status, 'complete');
assert.equal((await request(`/api/batches/${good.id}/audio/one-en`)).status, 200);
assert.deepEqual(fixture.calls, calls, 'history and existing audio stay local');
assert.deepEqual((await readdir(root)).sort(), [good.id, emptyId, malformedId, metadataId, linkedId].sort());
assert.equal(await readFile(join(root, emptyId, 'state.json'), 'utf8'), secret);
assert.equal(await readFile(join(root, malformedId, 'batch.json'), 'utf8'), malformed);
assert.equal(await readFile(join(root, metadataId, 'batch.json'), 'utf8'), metadata);
assert.equal((await lstat(join(root, linkedId))).isSymbolicLink(), true);
}));
test('starter source hashes match its exact files when running from the extracted ZIP', async () => {
const path = new URL('source-checksums.json', source);
if (!existsSync(path)) return;
const hashes = JSON.parse(await readFile(path, 'utf8'));
for (const [name, expected] of Object.entries(hashes)) assert.equal(createHash('sha256').update(await readFile(new URL(name, source))).digest('hex'), expected, name);
for (const required of ['server.mjs', 'ui/index.html', 'ui/app.js', 'ui/style.css', 'tests/flashcards-example.test.mjs']) assert.ok(hashes[required]);
});
SHA-256: ce1aa72aaf1b4cde305a5001f3dbc296eccfdd12a36fb97456c3af366417ec3b