mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(qa-matrix): feed recovery keys through stdin
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
redactMatrixQaCliOutput,
|
||||
resolveMatrixQaOpenClawCliEntryPath,
|
||||
runMatrixQaOpenClawCli,
|
||||
startMatrixQaOpenClawCli,
|
||||
} from "./scenario-runtime-cli.js";
|
||||
|
||||
describe("Matrix QA CLI runtime", () => {
|
||||
@@ -72,4 +73,71 @@ describe("Matrix QA CLI runtime", () => {
|
||||
await rm(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("can pass stdin to CLI commands", async () => {
|
||||
const root = await mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "matrix-qa-cli-stdin-"));
|
||||
try {
|
||||
await mkdir(path.join(root, "dist"));
|
||||
await writeFile(
|
||||
path.join(root, "dist", "index.mjs"),
|
||||
[
|
||||
"let input = '';",
|
||||
"process.stdin.setEncoding('utf8');",
|
||||
"process.stdin.on('data', (chunk) => { input += chunk; });",
|
||||
"process.stdin.on('end', () => {",
|
||||
" process.stdout.write(JSON.stringify({ input: input.trim() }));",
|
||||
"});",
|
||||
].join("\n"),
|
||||
);
|
||||
const result = await runMatrixQaOpenClawCli({
|
||||
args: ["matrix", "verify", "backup", "restore", "--recovery-key-stdin", "--json"],
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
stdin: "stdin-recovery-key\n",
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
expect(result.stdout).toContain('"input":"stdin-recovery-key"');
|
||||
} finally {
|
||||
await rm(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("can close stdin after interactive CLI prompts", async () => {
|
||||
const root = await mkdtemp(
|
||||
path.join(resolvePreferredOpenClawTmpDir(), "matrix-qa-cli-interactive-"),
|
||||
);
|
||||
try {
|
||||
await mkdir(path.join(root, "dist"));
|
||||
await writeFile(
|
||||
path.join(root, "dist", "index.mjs"),
|
||||
[
|
||||
"let input = '';",
|
||||
"process.stdin.setEncoding('utf8');",
|
||||
"process.stdin.on('data', (chunk) => { input += chunk; process.stdout.write('prompt answered\\n'); });",
|
||||
"process.stdin.on('end', () => {",
|
||||
" process.stdout.write(JSON.stringify({ input: input.trim(), ended: true }));",
|
||||
"});",
|
||||
].join("\n"),
|
||||
);
|
||||
const session = startMatrixQaOpenClawCli({
|
||||
args: ["matrix", "verify", "self"],
|
||||
cwd: root,
|
||||
env: process.env,
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
await session.writeStdin("yes\n");
|
||||
await session.waitForOutput(
|
||||
(output) => output.text.includes("prompt answered"),
|
||||
"interactive prompt acknowledgement",
|
||||
5_000,
|
||||
);
|
||||
session.endStdin();
|
||||
const result = await session.wait();
|
||||
|
||||
expect(result.stdout).toContain('"input":"yes"');
|
||||
expect(result.stdout).toContain('"ended":true');
|
||||
} finally {
|
||||
await rm(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ export type MatrixQaCliRunResult = {
|
||||
|
||||
export type MatrixQaCliSession = {
|
||||
args: string[];
|
||||
endStdin: () => void;
|
||||
output: () => { stderr: string; stdout: string };
|
||||
wait: () => Promise<MatrixQaCliRunResult>;
|
||||
waitForOutput: (
|
||||
@@ -95,6 +96,7 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
stdin?: string;
|
||||
timeoutMs: number;
|
||||
}): MatrixQaCliSession {
|
||||
const cwd = params.cwd ?? process.cwd();
|
||||
@@ -150,6 +152,9 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
|
||||
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
||||
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
||||
if (params.stdin !== undefined) {
|
||||
child.stdin.end(params.stdin);
|
||||
}
|
||||
child.on("error", (error) => {
|
||||
clearTimeout(timeout);
|
||||
finish(
|
||||
@@ -177,6 +182,11 @@ export function startMatrixQaOpenClawCli(params: {
|
||||
|
||||
return {
|
||||
args: params.args,
|
||||
endStdin: () => {
|
||||
if (!child.stdin.destroyed) {
|
||||
child.stdin.end();
|
||||
}
|
||||
},
|
||||
output: readOutput,
|
||||
wait: async () =>
|
||||
await new Promise<MatrixQaCliRunResult>((resolve, reject) => {
|
||||
@@ -230,6 +240,7 @@ export async function runMatrixQaOpenClawCli(params: {
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
stdin?: string;
|
||||
timeoutMs: number;
|
||||
}): Promise<MatrixQaCliRunResult> {
|
||||
return await startMatrixQaOpenClawCli(params).wait();
|
||||
@@ -327,12 +338,13 @@ export async function createMatrixQaOpenClawCliRuntime(params: {
|
||||
},
|
||||
run: async (
|
||||
args: string[],
|
||||
opts: { allowNonZero?: boolean; timeoutMs: number },
|
||||
opts: { allowNonZero?: boolean; stdin?: string; timeoutMs: number },
|
||||
): Promise<MatrixQaCliRunResult> =>
|
||||
await runMatrixQaOpenClawCli({
|
||||
allowNonZero: opts.allowNonZero,
|
||||
args,
|
||||
env,
|
||||
stdin: opts.stdin,
|
||||
timeoutMs: opts.timeoutMs,
|
||||
}),
|
||||
start: (args: string[], opts: { allowNonZero?: boolean; timeoutMs: number }) =>
|
||||
|
||||
@@ -303,10 +303,12 @@ async function runMatrixQaCliJson<T>(params: {
|
||||
decode?: (payload: unknown) => T;
|
||||
label: string;
|
||||
runtime: MatrixQaCliRuntime;
|
||||
stdin?: string;
|
||||
timeoutMs: number;
|
||||
}) {
|
||||
const result = await params.runtime.run(params.args, {
|
||||
allowNonZero: params.allowNonZero,
|
||||
stdin: params.stdin,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
const artifacts = await writeMatrixQaCliArtifacts({
|
||||
@@ -696,12 +698,12 @@ export async function runMatrixQaE2eeStateLossExternalRecoveryKeyScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"external-key",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "restore-with-external-key",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreSucceeded(restored.payload, "external recovery-key");
|
||||
@@ -711,13 +713,14 @@ export async function runMatrixQaE2eeStateLossExternalRecoveryKeyScenario(
|
||||
"matrix",
|
||||
"verify",
|
||||
"device",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--account",
|
||||
"external-key",
|
||||
"--json",
|
||||
],
|
||||
label: "verify-device-diagnostics",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
const backupKeyLoaded =
|
||||
@@ -835,12 +838,12 @@ export async function runMatrixQaE2eeStateLossStoredRecoveryKeyScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"stored-key",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "initial-restore-stores-key",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreSucceeded(initial.payload, "initial stored-key");
|
||||
@@ -976,12 +979,12 @@ export async function runMatrixQaE2eeStaleRecoveryKeyAfterBackupResetScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"stale-key",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "restore-with-stale-key",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreFailed(restored.payload, "stale recovery-key restore");
|
||||
@@ -1078,12 +1081,12 @@ async function waitForMatrixQaNonEmptyCliBackupRestore(params: {
|
||||
"restore",
|
||||
"--account",
|
||||
params.accountId,
|
||||
"--recovery-key",
|
||||
params.recoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: params.label,
|
||||
runtime: params.cli,
|
||||
stdin: `${params.recoveryKey}\n`,
|
||||
timeoutMs: Math.max(1, remainingMs),
|
||||
});
|
||||
last = restored;
|
||||
@@ -1197,12 +1200,12 @@ export async function runMatrixQaE2eeCorruptCryptoIdbSnapshotScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"corrupt-idb",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "initial-restore-before-corruption",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreSucceeded(initial.payload, "corrupt-idb initial restore");
|
||||
@@ -1219,12 +1222,12 @@ export async function runMatrixQaE2eeCorruptCryptoIdbSnapshotScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"corrupt-idb",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "restore-after-idb-corruption",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreSucceeded(repaired.payload, "corrupt-idb recovery");
|
||||
@@ -1273,12 +1276,12 @@ export async function runMatrixQaE2eeServerDeviceDeletedLocalStateIntactScenario
|
||||
"restore",
|
||||
"--account",
|
||||
"deleted-device",
|
||||
"--recovery-key",
|
||||
setup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "restore-before-device-delete",
|
||||
runtime: cli,
|
||||
stdin: `${setup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreSucceeded(restored.payload, "deleted-device preflight");
|
||||
@@ -1454,12 +1457,12 @@ export async function runMatrixQaE2eeWrongAccountRecoveryKeyScenario(
|
||||
"restore",
|
||||
"--account",
|
||||
"wrong-account",
|
||||
"--recovery-key",
|
||||
driverSetup.encodedRecoveryKey,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
label: "restore-with-wrong-account-key",
|
||||
runtime: cli,
|
||||
stdin: `${driverSetup.encodedRecoveryKey}\n`,
|
||||
timeoutMs: context.timeoutMs,
|
||||
});
|
||||
assertMatrixQaCliBackupRestoreFailed(restored.payload, "wrong-account recovery-key restore");
|
||||
|
||||
@@ -458,10 +458,11 @@ async function createMatrixQaCliSelfVerificationRuntime(params: {
|
||||
OPENCLAW_DISABLE_AUTO_UPDATE: "1",
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
};
|
||||
const run = async (args: string[], timeoutMs = params.context.timeoutMs) =>
|
||||
const run = async (args: string[], timeoutMs = params.context.timeoutMs, stdin?: string) =>
|
||||
await runMatrixQaOpenClawCli({
|
||||
args,
|
||||
env,
|
||||
stdin,
|
||||
timeoutMs,
|
||||
});
|
||||
const start = (args: string[], timeoutMs = params.context.timeoutMs) =>
|
||||
@@ -1227,17 +1228,20 @@ export async function runMatrixQaE2eeCliSelfVerificationScenario(
|
||||
userId: cliDevice.userId,
|
||||
});
|
||||
try {
|
||||
const restoreResult = await cli.run([
|
||||
"matrix",
|
||||
"verify",
|
||||
"backup",
|
||||
"restore",
|
||||
"--account",
|
||||
accountId,
|
||||
"--recovery-key",
|
||||
encodedRecoveryKey,
|
||||
"--json",
|
||||
]);
|
||||
const restoreResult = await cli.run(
|
||||
[
|
||||
"matrix",
|
||||
"verify",
|
||||
"backup",
|
||||
"restore",
|
||||
"--account",
|
||||
accountId,
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
context.timeoutMs,
|
||||
`${encodedRecoveryKey}\n`,
|
||||
);
|
||||
const restoreArtifacts = await writeMatrixQaCliOutputArtifacts({
|
||||
label: "verify-backup-restore",
|
||||
result: restoreResult,
|
||||
@@ -1310,8 +1314,9 @@ export async function runMatrixQaE2eeCliSelfVerificationScenario(
|
||||
cliSas,
|
||||
owner: ownerSas,
|
||||
});
|
||||
await session.writeStdin("yes\n");
|
||||
await owner.confirmVerificationSas(ownerSas.id);
|
||||
await session.writeStdin("yes\n");
|
||||
session.endStdin();
|
||||
const completedCli = await session.wait();
|
||||
const selfVerificationArtifacts = await writeMatrixQaCliOutputArtifacts({
|
||||
label: "verify-self",
|
||||
|
||||
@@ -3107,8 +3107,10 @@ describe("matrix live qa scenarios", () => {
|
||||
"Verification id: verification-1\nCompleted: yes\nDevice verified by owner: yes\nCross-signing verified: yes\n",
|
||||
});
|
||||
const kill = vi.fn();
|
||||
const endStdin = vi.fn();
|
||||
startMatrixQaOpenClawCli.mockReturnValue({
|
||||
args: ["matrix", "verify", "self", "--account", "cli"],
|
||||
endStdin,
|
||||
kill,
|
||||
output: vi.fn(() => ({ stderr: "", stdout: "" })),
|
||||
wait,
|
||||
@@ -3116,7 +3118,7 @@ describe("matrix live qa scenarios", () => {
|
||||
writeStdin,
|
||||
});
|
||||
let cliAccountConfigDuringRun: Record<string, unknown> | null = null;
|
||||
runMatrixQaOpenClawCli.mockImplementation(async ({ args, env }) => {
|
||||
runMatrixQaOpenClawCli.mockImplementation(async ({ args, env, stdin }) => {
|
||||
if (!cliAccountConfigDuringRun && env.OPENCLAW_CONFIG_PATH) {
|
||||
const cliConfig = JSON.parse(
|
||||
await readFile(String(env.OPENCLAW_CONFIG_PATH), "utf8"),
|
||||
@@ -3158,10 +3160,8 @@ describe("matrix live qa scenarios", () => {
|
||||
}),
|
||||
};
|
||||
}
|
||||
if (
|
||||
joined ===
|
||||
"matrix verify backup restore --account cli --recovery-key encoded-recovery-key --json"
|
||||
) {
|
||||
if (joined === "matrix verify backup restore --account cli --recovery-key-stdin --json") {
|
||||
expect(stdin).toBe("encoded-recovery-key\n");
|
||||
return {
|
||||
args,
|
||||
exitCode: 0,
|
||||
@@ -3216,6 +3216,7 @@ describe("matrix live qa scenarios", () => {
|
||||
]);
|
||||
expect(waitForOutput).toHaveBeenCalledTimes(2);
|
||||
expect(writeStdin).toHaveBeenCalledWith("yes\n");
|
||||
expect(endStdin).toHaveBeenCalledTimes(1);
|
||||
expect(wait).toHaveBeenCalledTimes(1);
|
||||
expect(kill).toHaveBeenCalledTimes(1);
|
||||
expect(runMatrixQaOpenClawCli).toHaveBeenCalledTimes(2);
|
||||
@@ -3227,12 +3228,12 @@ describe("matrix live qa scenarios", () => {
|
||||
"restore",
|
||||
"--account",
|
||||
"cli",
|
||||
"--recovery-key",
|
||||
"encoded-recovery-key",
|
||||
"--recovery-key-stdin",
|
||||
"--json",
|
||||
],
|
||||
["matrix", "verify", "status", "--account", "cli", "--json"],
|
||||
]);
|
||||
expect(runMatrixQaOpenClawCli.mock.calls[0]?.[0].stdin).toBe("encoded-recovery-key\n");
|
||||
const cliEnv = startMatrixQaOpenClawCli.mock.calls[0]?.[0].env;
|
||||
expect(cliEnv?.OPENCLAW_STATE_DIR).toContain("openclaw-matrix-cli-qa-");
|
||||
expect(cliEnv?.OPENCLAW_CONFIG_PATH).toContain("openclaw-matrix-cli-qa-");
|
||||
|
||||
Reference in New Issue
Block a user