timmy-talking-turd/tests/staging-health.test.js
Timmy 7e11154302
All checks were successful
Quality gates / quality (pull_request) Successful in 1m43s
feat: add private subpage staging slice
2026-08-21 14:12:17 +00:00

168 lines
6.7 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { chmod, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = fileURLToPath(new URL('..', import.meta.url));
const hermesFixture = fileURLToPath(new URL('./fixtures/fake-hermes.mjs', import.meta.url));
let nextPort = 43100;
async function startServer(t, env = {}) {
const port = nextPort++;
const origin = `http://127.0.0.1:${port}`;
const child = spawn(process.execPath, ['server.mjs'], {
cwd: root,
env: { ...process.env, PORT: String(port), ...env, ...(env.TIMMY_PUBLIC_ORIGIN === '__ORIGIN__' ? { TIMMY_PUBLIC_ORIGIN: origin } : {}) },
stdio: ['ignore', 'pipe', 'pipe'],
});
let stderr = '';
let stdout = '';
child.stdout.on('data', chunk => { stdout += chunk; });
child.stderr.on('data', chunk => { stderr += chunk; });
t.after(() => child.kill('SIGTERM'));
const deadline = Date.now() + 10_000;
const basePath = (env.TIMMY_BASE_PATH || '').replace(/\/$/, '');
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`server exited ${child.exitCode}: ${stderr}`);
try {
const response = await fetch(`${origin}${basePath}/api/healthz`);
if (response.status) return { origin, child, getStdout: () => stdout };
} catch {}
await new Promise(resolve => setTimeout(resolve, 40));
}
throw new Error(`server did not become ready: ${stderr}`);
}
test('staging host can bind loopback instead of every network interface', async t => {
const { getStdout } = await startServer(t, { HOST: '127.0.0.1', TIMMY_VISION_ENABLED: '0' });
assert.match(getStdout(), /http:\/\/127\.0\.0\.1:/);
assert.doesNotMatch(getStdout(), /http:\/\/0\.0\.0\.0:/);
});
test('health endpoint exposes only bounded staging identity and feature flags', async t => {
const { origin } = await startServer(t, {
TIMMY_RELEASE_TAG: 'daily-2026-08-20.3',
TIMMY_RELEASE_COMMIT: 'ca31e6d38bec649407f63880504554c59f2878ae',
TIMMY_VISION_ENABLED: '0',
TIMMY_AGENT_ENABLED: 'false',
SECRET_TOKEN: 'must-not-leak',
});
const response = await fetch(`${origin}/api/healthz`);
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), {
ok: true,
release: 'daily-2026-08-20.3',
commit: 'ca31e6d38bec649407f63880504554c59f2878ae',
visionEnabled: false,
agentEnabled: false,
});
assert.deepEqual([...response.headers.keys()].filter(name => /token|cookie|session|path|environment|credential/i.test(name)), []);
});
test('base path contains static files and APIs without capturing sibling routes', async t => {
const { origin } = await startServer(t, { TIMMY_BASE_PATH: '/timmy-staging', TIMMY_VISION_ENABLED: '0' });
const health = await fetch(`${origin}/timmy-staging/api/healthz`);
assert.equal(health.status, 200);
const page = await fetch(`${origin}/timmy-staging/`);
assert.equal(page.status, 200);
assert.match(await page.text(), /<div id="app"/);
assert.equal((await fetch(`${origin}/timmy-staging/app.js`)).status, 200);
assert.equal((await fetch(`${origin}/api/healthz`)).status, 404);
assert.equal((await fetch(`${origin}/git`)).status, 404);
});
test('prefixed document, manifest, and service worker stay inside the app scope', async t => {
const { origin } = await startServer(t, {
TIMMY_BASE_PATH: '/timmy-staging',
TIMMY_RELEASE_TAG: 'daily-2026-08-20.3',
TIMMY_RELEASE_COMMIT: 'ca31e6d38bec649407f63880504554c59f2878ae',
TIMMY_STAGING_LABEL: 'true',
TIMMY_VISION_ENABLED: '0',
});
const redirect = await fetch(`${origin}/timmy-staging`, { redirect: 'manual' });
assert.equal(redirect.status, 308);
assert.equal(redirect.headers.get('location'), '/timmy-staging/');
const html = await (await fetch(`${origin}/timmy-staging/`)).text();
assert.match(html, /<base href="\/timmy-staging\/">/);
assert.match(html, /window\.__TIMMY_CONFIG__=/);
assert.match(html, /"basePath":"\/timmy-staging"/);
assert.match(html, /"stagingLabel":"Staging · daily-2026-08-20\.3 · ca31e6d38bec"/);
const manifest = await (await fetch(`${origin}/timmy-staging/manifest.webmanifest`)).json();
assert.equal(manifest.start_url, '/timmy-staging/');
assert.equal(manifest.scope, '/timmy-staging/');
assert.ok(manifest.icons.every(icon => icon.src.startsWith('/timmy-staging/')));
assert.equal((await fetch(`${origin}/timmy-staging/service-worker.js`)).status, 200);
});
test('agent cookie is constrained to the normalized base path', async t => {
const workdir = await mkdtemp(join(tmpdir(), 'timmy-staging-cookie-'));
await chmod(hermesFixture, 0o700);
t.after(() => rm(workdir, { recursive: true, force: true }));
const { origin } = await startServer(t, {
TIMMY_BASE_PATH: '/timmy-staging/',
TIMMY_AGENT_ENABLED: 'true',
TIMMY_AGENT_ACCESS_TOKEN: 'integration-access-code-2026',
TIMMY_PUBLIC_ORIGIN: '__ORIGIN__',
TIMMY_AGENT_WORKDIR: workdir,
TIMMY_HERMES_COMMAND: hermesFixture,
TIMMY_VISION_ENABLED: '0',
});
const response = await fetch(`${origin}/timmy-staging/api/agent/unlock`, {
method: 'POST',
headers: { 'content-type': 'application/json', origin, 'sec-fetch-site': 'same-origin' },
body: JSON.stringify({ accessCode: 'integration-access-code-2026' }),
});
assert.equal(response.status, 200);
assert.match(response.headers.get('set-cookie'), /; Path=\/timmy-staging\//);
});
test('malformed and escaping base paths are rejected at startup', async () => {
const invalid = [
'timmy-staging',
'/../git',
'/timmy/./nested',
'/%2e%2e/git',
'/%252e%252e/git',
'/timmy%2Fgit',
'/timmy%5cgit',
'/timmy%2dstaging',
'/timmy\\git',
'/timmy?debug=1',
'/timmy#fragment',
'/timmy%',
'/timmy//nested',
'/timmy-staging//',
'/timmy-staging;Secure',
'/timmy-staging"quoted',
"/timmy-staging'quoted",
'/timmy staging',
'/timmy\nstaging',
];
for (const value of invalid) {
const child = spawn(process.execPath, ['server.mjs'], {
cwd: root,
env: { ...process.env, PORT: '0', TIMMY_BASE_PATH: value },
stdio: ['ignore', 'ignore', 'pipe'],
});
let stderr = '';
child.stderr.on('data', chunk => { stderr += chunk; });
const exitCode = await Promise.race([
new Promise(resolve => child.once('exit', resolve)),
new Promise(resolve => setTimeout(() => { child.kill('SIGTERM'); resolve('timeout'); }, 800)),
]);
assert.notEqual(exitCode, 'timeout', `${JSON.stringify(value)} was accepted`);
assert.notEqual(exitCode, 0, `${JSON.stringify(value)} exited successfully`);
assert.match(stderr, /TIMMY_BASE_PATH/);
}
});