timmy-talking-turd/tests/image-concurrency.test.js
Timmy 517c8dbac3
Some checks failed
Quality gates / quality (pull_request) Failing after 2m39s
Harden image ingress: pinned runtime, header-bomb rejection, concurrency ceiling, bounded rate limiter, canonical data-URL/polyglot contract, 503-on-unavailable, body-read timeout
Closes the PR 63 hostile-review blockers:
1. Immutable pinned Python/Pillow runtime (TIMMY_PYTHON absolute, --verify-pin),
   deployment/runtime re-encode smoke gate in build_release + deploy_staging.
2. Header-only width/height/total-pixel/bomb rejection before full decode; proves
   6000x6000 and 12000x12000 stay resource bounded (RSS + address-space caps).
3. Fail-fast decoder concurrency ceiling; tests count actual spawned children.
4. Rate-limiter key cardinality hard-bounded under 20k+ unexpired identities,
   trusted-loopback-proxy identity, no spoofable forwarded headers, fixed-window
   boundary burst smoothed by two-window sliding count.
5. build_release explicitly syntax/gates every new JS module + Python re-encoder +
   production-runtime smoke; CI runs reencode-image test and the runtime pin smoke.
6. Robust polyglot contract via canonical container parsing; rejects uppercase/mixed
   script, appended HTML, ZIP local/EOCD and archive tails, data after canonical
   JPEG/PNG/WebP end; no naive compressed-byte scans (false-positive controls pass).
7. Canonical base64 data-URL grammar with byte-exact round trip; rejects missing/excess
   padding, whitespace/CRLF, malformed and noncanonical encodings; exact MIME policy.
8. Missing Pillow / runtime-unavailable maps to sanitized 503 + manual fallback.
9. Inbound body-read timeout and stop-on-oversize; preserves sanitized 413, base path,
   provider suppression, and temp cleanup; socket torn down on rejection.

Audited prior partial edits: reused the sound source modules, re-wired new tests into
the unit/syntax gates, fixed a non-canonical readJson that destroyed the socket before
delivering 413/408, and hardened test flakiness (port reuse, EPIPE, startup races).
2026-08-22 23:22:42 +00:00

147 lines
6.5 KiB
JavaScript

// Concurrency ceiling contract.
//
// The decoder runs as a subprocess and each one costs real memory. The service
// runs under MemoryMax=512M, so a burst must be bounded by a small fail-fast
// ceiling rather than queued indefinitely: excess requests are refused
// immediately with a sanitized retry message, and the number of decoder
// children alive at once never exceeds the ceiling.
import test from 'node:test';
import assert from 'node:assert/strict';
import { chmod, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
INGRESS_MAX_CONCURRENCY,
IngressUnavailableError,
ingressConcurrencyState,
validateImageIngress,
} from '../src/image-ingress.js';
const fixture = (name) => fileURLToPath(new URL(`../tests/fixtures/${name}`, import.meta.url));
const clean = await readFile(fixture('ingress-clean.jpg'));
const cleanDataUrl = `data:image/jpeg;base64,${clean.toString('base64')}`;
// An instrumented stand-in interpreter: it records one marker file per spawn
// while it is alive, so the test can observe the true peak child count instead
// of trusting an internal counter.
async function trackingInterpreter(name, holdSeconds = '0.6') {
const dir = join(tmpdir(), `timmy-ingress-conc-${name}`);
await rm(dir, { recursive: true, force: true });
await mkdir(join(dir, 'live'), { recursive: true });
await mkdir(join(dir, 'seen'), { recursive: true });
const path = join(dir, 'python3');
await writeFile(path, [
'#!/bin/sh',
`marker="${dir}/live/$$"`,
`touch "$marker" "${dir}/seen/$$"`,
`sleep ${holdSeconds}`,
'rm -f "$marker"',
// Emit a valid success verdict and write the expected output file.
'out=""',
'while [ $# -gt 0 ]; do',
' if [ "$1" = "--out" ]; then out="$2"; fi',
' shift',
'done',
`cp "${fixture('ingress-clean.jpg')}" "$out"`,
'echo \'{"ok": true, "format": "jpeg", "width": 64, "height": 64, "bytes": 639, "metadataStripped": true}\'',
].join('\n'));
await chmod(path, 0o755);
return { path, dir };
}
const withPython = async (path, run) => {
const previous = process.env.TIMMY_PYTHON;
process.env.TIMMY_PYTHON = path;
try {
return await run();
} finally {
if (previous === undefined) delete process.env.TIMMY_PYTHON;
else process.env.TIMMY_PYTHON = previous;
}
};
test('the concurrency ceiling is small enough to stay inside the service memory budget', () => {
assert.equal(typeof INGRESS_MAX_CONCURRENCY, 'number');
assert.ok(INGRESS_MAX_CONCURRENCY >= 1, 'at least one decode must be permitted');
assert.ok(INGRESS_MAX_CONCURRENCY <= 4,
`ceiling ${INGRESS_MAX_CONCURRENCY} is too high for a 512 MiB service`);
// Worst observed single-decoder peak is ~24 MiB; the whole burst must leave
// ample headroom under MemoryMax=512M alongside the Node process itself.
assert.ok(INGRESS_MAX_CONCURRENCY * 64 * 1024 * 1024 < 512 * 1024 * 1024,
'the ceiling must bound peak decoder memory well under 512 MiB');
});
test('a burst beyond the ceiling never runs more decoder children than the ceiling', async () => {
const { path, dir } = await trackingInterpreter('peak');
await withPython(path, async () => {
let peakLive = 0;
const sampler = setInterval(async () => {
try {
const live = await readdir(join(dir, 'live'));
peakLive = Math.max(peakLive, live.length);
} catch { /* directory races are fine */ }
}, 15);
const burst = await Promise.allSettled(
Array.from({ length: 24 }, () => validateImageIngress({ consent: true, imageDataUrl: cleanDataUrl })),
);
clearInterval(sampler);
const spawned = (await readdir(join(dir, 'seen'))).length;
const rejected = burst.filter((r) => r.status === 'rejected');
assert.ok(peakLive <= INGRESS_MAX_CONCURRENCY,
`observed ${peakLive} concurrent decoder children, ceiling is ${INGRESS_MAX_CONCURRENCY}`);
assert.ok(spawned <= INGRESS_MAX_CONCURRENCY,
`a 24-request burst spawned ${spawned} decoders; only ${INGRESS_MAX_CONCURRENCY} may run and the rest must be refused`);
assert.ok(rejected.length >= 24 - INGRESS_MAX_CONCURRENCY,
'excess requests must be refused rather than queued');
});
});
test('over-ceiling requests fail fast with a sanitized unavailable message', async () => {
const { path } = await trackingInterpreter('failfast');
await withPython(path, async () => {
const started = Date.now();
const burst = await Promise.allSettled(
Array.from({ length: 12 }, () => validateImageIngress({ consent: true, imageDataUrl: cleanDataUrl })),
);
const rejected = burst.filter((r) => r.status === 'rejected').map((r) => r.reason);
assert.ok(rejected.length > 0, 'a 12-request burst must refuse some requests');
for (const error of rejected) {
assert.ok(error instanceof IngressUnavailableError,
'an over-capacity refusal is a server-capacity condition, not corrupt client input');
assert.match(error.message, /temporarily unavailable|slow down|try again/i);
assert.doesNotMatch(error.message, /corrupt|malformed/i);
assert.doesNotMatch(error.message, /[A-Za-z0-9+/]{40,}/, 'no payload data in refusals');
assert.ok(error.message.length < 200);
}
// Fail-fast: refusals must not wait for the in-flight decoders to finish.
assert.ok(Date.now() - started < 5000, 'refusals must be immediate, not queued behind decodes');
});
});
test('capacity is released so later requests succeed after a burst', async () => {
const { path } = await trackingInterpreter('release', '0.05');
await withPython(path, async () => {
await Promise.allSettled(
Array.from({ length: 12 }, () => validateImageIngress({ consent: true, imageDataUrl: cleanDataUrl })),
);
const state = ingressConcurrencyState();
assert.equal(state.active, 0, 'every slot must be released after the burst settles');
const after = await validateImageIngress({ consent: true, imageDataUrl: cleanDataUrl });
assert.equal(after.format, 'jpeg');
});
});
test('rejected input releases its slot too', async () => {
const garbage = await readFile(fixture('ingress-garbage.jpg'));
for (let i = 0; i < INGRESS_MAX_CONCURRENCY + 3; i += 1) {
await assert.rejects(() => validateImageIngress({
consent: true, imageDataUrl: `data:image/jpeg;base64,${garbage.toString('base64')}`,
}));
}
assert.equal(ingressConcurrencyState().active, 0,
'a rejected request must not leak a concurrency slot');
});