Files
stack/packages/processing-control/tests/effects.test.mjs
T
rocko c13c68c813 test: strengthen processing cancellation and refusal guards
Bound the halt-abort expectation, check Discord refusal before malformed journal reads, and isolate HTTP origin and valid oversized-body guards. These test changes are committed for the row 191 gate; execution awaits the coordinated slot.
2026-10-21 22:42:12 -05:00

75 lines
4.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { initializeControl, transition } from '../src/state.mjs';
const api = await import('../src/effects.mjs').catch(e => { if (e.code === 'ERR_MODULE_NOT_FOUND') return {}; throw e; });
function fixture(t) { const root = mkdtempSync(join(tmpdir(), 'processing-effect-')); initializeControl(root); t.after(() => rmSync(root, { recursive: true, force: true })); return root; }
const actor = { business: 'demo', human: 'jason', halted: true };
test('halt aborts an active local request and prevents a subsequent effect after halt/resume', { timeout: 4000 }, async t => {
assert.equal(typeof api.withProcessing, 'function');
const root = fixture(t); let started;
const ready = new Promise(resolve => { started = resolve; });
const work = api.withProcessing(root, async scope => {
await new Promise(resolve => { scope.signal.addEventListener('abort', resolve, { once: true }); started(); });
scope.guard();
});
await ready; const halted = transition(root, actor);
transition(root, { ...actor, halted: false, generation: halted.generation });
await assert.rejects(work, e => e.code === 'processing-generation-changed');
});
test('processing scopes isolate roots and cancel only the registered child group', async t => {
assert.equal(typeof api.withProcessing, 'function');
const a = fixture(t), b = fixture(t);
let admitted;
const ready = new Promise(resolve => { admitted = resolve; });
const task = api.withProcessing(a, async scope => {
const child = spawn(process.execPath, ['-e', "process.on('SIGTERM',()=>process.exit(0));console.log('ready');setInterval(()=>{},1000)"], { detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
const exit = once(child, 'exit'); t.after(() => { try { process.kill(-child.pid, 'SIGKILL'); } catch {} });
scope.track(child); await once(child.stdout, 'data'); admitted();
const outcome = await exit; assert.deepEqual(outcome, [0, null]); scope.guard();
});
await ready; transition(b, actor);
await api.withProcessing(a, async scope => scope.guard());
transition(a, actor);
await assert.rejects(task, e => e.code === 'processing-halted');
});
test('halted scopes never invoke their body and unscoped human work has no processing context', async t => {
assert.equal(typeof api.withProcessing, 'function');
const root = fixture(t); transition(root, actor); let called = false;
await assert.rejects(api.withProcessing(root, async () => { called = true; }), e => e.code === 'processing-halted');
assert.equal(called, false); assert.equal(api.currentProcessing(), undefined);
});
test('host git is stopped promptly by the active scope rather than waiting for its long deadline', async t => {
const { mkdirSync, writeFileSync } = await import('node:fs');
const { runGit, gitEnv } = await import('../../seat/src/checkout.mjs');
const root = fixture(t), bin = join(root, 'bin'); mkdirSync(bin);
writeFileSync(join(bin, 'git'), `#!${process.execPath}\nprocess.on('SIGTERM',()=>process.exit(0));setInterval(()=>{},1000);\n`, { mode: 0o755 });
const begin = Date.now();
const task = api.withProcessing(root, async () => runGit(['status'], { ...gitEnv(), PATH: bin + ':' + process.env.PATH }, { timeoutMs: 10000, graceMs: 100 }));
await new Promise(resolve => setTimeout(resolve, 100)); transition(root, actor);
await assert.rejects(task, e => e.code === 'processing-halted');
assert.ok(Date.now() - begin < 2000, 'the halt cancels the actual git process');
});
test('a child leader exiting does not erase an unverified surviving group receipt', async t => {
const { listRuntimes } = await import('../src/runtimes.mjs');
const root = fixture(t);
let child;
await api.withProcessing(root, async scope => {
const script = `const {spawn}=require('node:child_process');spawn(process.execPath,['-e','setInterval(()=>{},1000)'],{stdio:'ignore'});console.log('ready');setTimeout(()=>process.exit(0),80);`;
child = spawn(process.execPath, ['-e', script], { detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
t.after(() => { try { process.kill(-child.pid, 'SIGKILL'); } catch {} });
const closed = once(child, 'close');
scope.track(child); await once(child.stdout, 'data'); await closed;
});
const records = listRuntimes(root);
assert.equal(records.length, 1, 'unknown group remains discoverable to the independent halt sweep');
assert.equal(records[0].pid, child.pid);
assert.equal(records[0].group, true);
});