fix(e2e): bound secret configure pty output

This commit is contained in:
Vincent Koc
2026-06-07 04:37:41 +02:00
parent 7f7614276b
commit e32707458d
2 changed files with 77 additions and 9 deletions

View File

@@ -149,6 +149,12 @@ function createOutputCapture(label, options = {}) {
text() {
return output;
},
reset() {
output = "";
bytes = 0;
truncated = false;
scanTail = "";
},
leakedForbiddenValue() {
return leakedForbiddenValue;
},
@@ -1606,7 +1612,7 @@ async function runPtySecretsConfigurePreset(envCtx) {
cwd: command.options.cwd ?? process.cwd(),
env: command.options.env ?? envCtx.env,
});
let output = "";
const output = createOutputCapture("secrets configure stdout");
let phase = "providers-menu";
const sendKeys = (keys) => {
keys.forEach((key, index) => {
@@ -1616,22 +1622,23 @@ async function runPtySecretsConfigurePreset(envCtx) {
return await new Promise((resolve, reject) => {
const timer = setTimeout(() => {
child.kill();
reject(new Error(`secrets configure preset timed out: ${scrub(output)}`));
reject(new Error(`secrets configure preset timed out: ${scrub(output.text())}`));
}, 60000);
child.onData((data) => {
output += data;
if (phase === "providers-menu" && output.includes("Configure secret providers")) {
output.append(data);
const outputText = output.text();
if (phase === "providers-menu" && outputText.includes("Configure secret providers")) {
phase = "selecting-preset";
sendKeys(["\x1b[B", "\r"]);
return;
}
if (phase === "selecting-preset" && output.includes("Select plugin preset")) {
if (phase === "selecting-preset" && outputText.includes("Select plugin preset")) {
phase = "preset-selected";
sendKeys(["\r"]);
output = "";
output.reset();
return;
}
if (phase === "preset-selected" && output.includes("Configure secret providers")) {
if (phase === "preset-selected" && outputText.includes("Configure secret providers")) {
phase = "continue-selected";
sendKeys(["\x1b[A", "\r"]);
}
@@ -1639,10 +1646,10 @@ async function runPtySecretsConfigurePreset(envCtx) {
child.onExit(({ exitCode }) => {
clearTimeout(timer);
if (exitCode !== 0) {
reject(new Error(`secrets configure preset failed (${exitCode}): ${scrub(output)}`));
reject(new Error(`secrets configure preset failed (${exitCode}): ${scrub(output.text())}`));
return;
}
resolve(output);
resolve(output.text());
});
});
}
@@ -1858,6 +1865,7 @@ export {
cleanupEnv,
expectGatewayStartupFails,
gatewayCall,
runPtySecretsConfigurePreset,
runCommand,
startGateway,
waitForManagedGatewayStatus,

View File

@@ -114,6 +114,25 @@ function writeSignaledStartupOpenClaw(root: string): string {
return scriptPath;
}
function writeNoisySecretsConfigureOpenClaw(root: string): string {
const scriptPath = path.join(root, "fake-noisy-secrets-configure-openclaw.mjs");
fs.writeFileSync(
scriptPath,
[
"#!/usr/bin/env node",
"const args = process.argv.slice(2);",
"if (args[0] === 'secrets' && args[1] === 'configure') {",
" process.stdout.write('x'.repeat(4096));",
" process.exit(7);",
"}",
"process.exit(2);",
"",
].join("\n"),
{ mode: 0o755 },
);
return scriptPath;
}
function runProofHarness(
root: string,
fakeOpenClaw: string,
@@ -237,6 +256,47 @@ describe("secret provider integration proof harness", () => {
}
});
it.runIf(process.platform !== "win32")("bounds captured PTY configure output", async () => {
const root = makeTempDir();
const fakeOpenClaw = writeNoisySecretsConfigureOpenClaw(root);
const previousLimit = process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES;
const previousEntry = process.env.OPENCLAW_ENTRY;
process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES = "128";
process.env.OPENCLAW_ENTRY = fakeOpenClaw;
try {
const proof = await import(
`${pathToFileURL(proofScriptPath).href}?case=pty-output-${Date.now()}`
);
const error = await proof
.runPtySecretsConfigurePreset({
env: {
...process.env,
OPENCLAW_ENTRY: fakeOpenClaw,
},
})
.catch((caught: unknown) => caught);
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toContain("secrets configure preset failed (7)");
expect((error as Error).message).toContain(
"secrets configure stdout truncated after 128 bytes",
);
expect((error as Error).message.length).toBeLessThan(600);
} finally {
if (previousLimit === undefined) {
delete process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES;
} else {
process.env.OPENCLAW_SECRET_PROOF_OUTPUT_BYTES = previousLimit;
}
if (previousEntry === undefined) {
delete process.env.OPENCLAW_ENTRY;
} else {
process.env.OPENCLAW_ENTRY = previousEntry;
}
}
});
it.runIf(process.platform !== "win32")(
"fails mandatory commands that exit by signal",
async () => {