mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
* test: integrate upgrade survivor baseline controls * test: gate published upgrade survivor path * test: preserve upgrade survivor fixture contract * test: keep upgrade survivor temp state off overlay
229 lines
9.4 KiB
TypeScript
229 lines
9.4 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { promisify } from "node:util";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const scriptPath = path.join(repoRoot, "scripts/lib/openclaw-test-state.mjs");
|
|
const onboardDockerScriptPath = path.join(repoRoot, "scripts/e2e/onboard-docker.sh");
|
|
|
|
function shellQuote(value: string): string {
|
|
return `'${value.replace(/'/gu, `'\\''`)}'`;
|
|
}
|
|
|
|
function escapeRegex(value: string): string {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
}
|
|
|
|
describe("scripts/lib/openclaw-test-state", () => {
|
|
it("creates a sourceable env file and JSON description", async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-state-script-"));
|
|
const envFile = path.join(tempRoot, "env.sh");
|
|
try {
|
|
const { stdout } = await execFileAsync(process.execPath, [
|
|
scriptPath,
|
|
"--",
|
|
"create",
|
|
"--label",
|
|
"script-test",
|
|
"--scenario",
|
|
"update-stable",
|
|
"--env-file",
|
|
envFile,
|
|
"--json",
|
|
]);
|
|
const payload = JSON.parse(stdout);
|
|
expect(payload).toMatchObject({
|
|
label: "script-test",
|
|
scenario: "update-stable",
|
|
home: expect.any(String),
|
|
stateDir: expect.any(String),
|
|
configPath: expect.any(String),
|
|
workspaceDir: expect.any(String),
|
|
env: {
|
|
HOME: expect.any(String),
|
|
OPENCLAW_HOME: expect.any(String),
|
|
OPENCLAW_STATE_DIR: expect.any(String),
|
|
OPENCLAW_CONFIG_PATH: expect.any(String),
|
|
},
|
|
});
|
|
expect(payload.config).toEqual({
|
|
update: {
|
|
channel: "stable",
|
|
},
|
|
plugins: {},
|
|
});
|
|
|
|
const envFileText = await fs.readFile(envFile, "utf8");
|
|
expect(envFileText).toContain("export HOME=");
|
|
expect(envFileText).toContain("export OPENCLAW_HOME=");
|
|
expect(envFileText).toContain("export OPENCLAW_STATE_DIR=");
|
|
expect(envFileText).toContain("export OPENCLAW_CONFIG_PATH=");
|
|
|
|
const probe = await execFileAsync("bash", [
|
|
"-lc",
|
|
`source ${shellQuote(envFile)}; node -e 'const fs=require("node:fs"); const config=JSON.parse(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH,"utf8")); process.stdout.write(JSON.stringify({home:process.env.HOME,stateDir:process.env.OPENCLAW_STATE_DIR,channel:config.update.channel}));'`,
|
|
]);
|
|
expect(JSON.parse(probe.stdout)).toEqual({
|
|
home: payload.home,
|
|
stateDir: payload.stateDir,
|
|
channel: "stable",
|
|
});
|
|
await fs.rm(payload.root, { recursive: true, force: true });
|
|
} finally {
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("renders a Docker-friendly shell snippet", async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-state-shell-"));
|
|
const snippetFile = path.join(tempRoot, "state.sh");
|
|
try {
|
|
const { stdout } = await execFileAsync(process.execPath, [
|
|
scriptPath,
|
|
"shell",
|
|
"--label",
|
|
"update-channel-switch",
|
|
"--scenario",
|
|
"update-stable",
|
|
]);
|
|
expect(stdout).toContain(
|
|
'OPENCLAW_TEST_STATE_TMP_ROOT="${OPENCLAW_TEST_STATE_TMPDIR:-${TMPDIR:-/tmp}}"',
|
|
);
|
|
expect(stdout).toContain(
|
|
'mktemp -d "$OPENCLAW_TEST_STATE_TMP_ROOT/openclaw-update-channel-switch-update-stable-home.XXXXXX"',
|
|
);
|
|
expect(stdout).toContain("OPENCLAW_TEST_STATE_JSON");
|
|
expect(stdout).toContain('"channel": "stable"');
|
|
await fs.writeFile(snippetFile, stdout, "utf8");
|
|
|
|
const probe = await execFileAsync("bash", [
|
|
"-lc",
|
|
`source ${shellQuote(snippetFile)}; node -e 'const fs=require("node:fs"); const config=JSON.parse(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH,"utf8")); process.stdout.write(JSON.stringify({home:process.env.HOME,openclawHome:process.env.OPENCLAW_HOME,workspace:process.env.OPENCLAW_TEST_WORKSPACE_DIR,channel:config.update.channel}));'; rm -rf "$HOME"`,
|
|
]);
|
|
|
|
const payload = JSON.parse(probe.stdout);
|
|
expect(payload.home.startsWith(os.tmpdir())).toBe(true);
|
|
expect(path.basename(payload.home)).toMatch(
|
|
/^openclaw-update-channel-switch-update-stable-home\./u,
|
|
);
|
|
expect(payload.openclawHome).toBe(payload.home);
|
|
expect(payload.workspace).toBe(`${payload.home}/workspace`);
|
|
expect(payload.channel).toBe("stable");
|
|
|
|
const customTemp = path.join(tempRoot, "state-tmp");
|
|
const customProbe = await execFileAsync("bash", [
|
|
"-lc",
|
|
`export OPENCLAW_TEST_STATE_TMPDIR=${shellQuote(customTemp)}; source ${shellQuote(snippetFile)}; node -e 'process.stdout.write(JSON.stringify({home:process.env.HOME,tmpRoot:process.env.OPENCLAW_TEST_STATE_TMP_ROOT}));'; rm -rf "$HOME"`,
|
|
]);
|
|
const customPayload = JSON.parse(customProbe.stdout);
|
|
expect(customPayload.tmpRoot).toBe(customTemp);
|
|
expect(customPayload.home).toMatch(
|
|
new RegExp(
|
|
`^${escapeRegex(customTemp)}/openclaw-update-channel-switch-update-stable-home\\.`,
|
|
),
|
|
);
|
|
} finally {
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("creates the upgrade survivor scenario", async () => {
|
|
const { stdout } = await execFileAsync(process.execPath, [
|
|
scriptPath,
|
|
"--",
|
|
"create",
|
|
"--label",
|
|
"upgrade-survivor",
|
|
"--scenario",
|
|
"upgrade-survivor",
|
|
"--json",
|
|
]);
|
|
const payload = JSON.parse(stdout);
|
|
try {
|
|
expect(payload.scenario).toBe("upgrade-survivor");
|
|
expect(payload.config).toMatchObject({
|
|
update: {
|
|
channel: "stable",
|
|
},
|
|
gateway: {
|
|
auth: {
|
|
token: {
|
|
id: "GATEWAY_AUTH_TOKEN_REF",
|
|
source: "env",
|
|
},
|
|
},
|
|
},
|
|
channels: {
|
|
discord: {
|
|
enabled: true,
|
|
dm: {
|
|
policy: "allowlist",
|
|
},
|
|
},
|
|
telegram: {
|
|
enabled: true,
|
|
},
|
|
whatsapp: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
});
|
|
} finally {
|
|
await fs.rm(payload.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("renders a reusable Docker shell function", async () => {
|
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-state-function-"));
|
|
const snippetFile = path.join(tempRoot, "state-function.sh");
|
|
try {
|
|
const { stdout } = await execFileAsync(process.execPath, [scriptPath, "shell-function"]);
|
|
expect(stdout).toContain("openclaw_test_state_create()");
|
|
expect(stdout).toContain("unset OPENCLAW_AGENT_DIR");
|
|
expect(stdout).toContain("update-stable");
|
|
await fs.writeFile(snippetFile, stdout, "utf8");
|
|
|
|
const probe = await execFileAsync("bash", [
|
|
"-lc",
|
|
`export OPENCLAW_TEST_STATE_TMPDIR=${shellQuote(path.join(tempRoot, "function-tmp"))}; source ${shellQuote(snippetFile)}; export OPENCLAW_AGENT_DIR=/tmp/outside-agent; openclaw_test_state_create "onboard case" minimal; node -e 'const fs=require("node:fs"); const config=JSON.parse(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH,"utf8")); process.stdout.write(JSON.stringify({home:process.env.HOME,tmpDir:process.env.OPENCLAW_TEST_STATE_TMPDIR,agentDir:process.env.OPENCLAW_AGENT_DIR || null,workspace:process.env.OPENCLAW_TEST_WORKSPACE_DIR,config}));'; rm -rf "$HOME"`,
|
|
]);
|
|
|
|
const payload = JSON.parse(probe.stdout);
|
|
expect(payload.home).toBe(`${payload.tmpDir}/${path.basename(payload.home)}`);
|
|
expect(payload.home).toContain("/openclaw-onboard-case-minimal-home.");
|
|
expect(payload.agentDir).toBeNull();
|
|
expect(payload.workspace).toBe(`${payload.home}/workspace`);
|
|
expect(payload.config).toEqual({});
|
|
|
|
const existingHome = path.join(tempRoot, "existing-home");
|
|
const existingProbe = await execFileAsync("bash", [
|
|
"-lc",
|
|
`source ${shellQuote(snippetFile)}; openclaw_test_state_create ${shellQuote(existingHome)} minimal; printf '{"kept":true}\\n' > "$OPENCLAW_CONFIG_PATH"; openclaw_test_state_create ${shellQuote(existingHome)} empty; node -e 'const fs=require("node:fs"); const config=JSON.parse(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH,"utf8")); process.stdout.write(JSON.stringify({home:process.env.HOME,config}));'`,
|
|
]);
|
|
|
|
const existingPayload = JSON.parse(existingProbe.stdout);
|
|
expect(existingPayload.home).toBe(existingHome);
|
|
expect(existingPayload.config).toEqual({ kept: true });
|
|
} finally {
|
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("keeps onboard Docker temp homes on the shared test-state helper", async () => {
|
|
const scriptText = await fs.readFile(onboardDockerScriptPath, "utf8");
|
|
const scenarioText = await fs.readFile("scripts/e2e/lib/onboard/scenario.sh", "utf8");
|
|
|
|
expect(scriptText).toContain("OPENCLAW_TEST_STATE_FUNCTION_B64");
|
|
expect(scriptText).toContain("scripts/e2e/lib/onboard/scenario.sh");
|
|
expect(scenarioText).toContain("set_isolated_openclaw_env local-basic");
|
|
expect(scenarioText).toContain("run_wizard_cmd channels channels");
|
|
expect(scriptText).not.toContain("make_home");
|
|
expect(scenarioText).not.toContain("make_home");
|
|
});
|
|
});
|