Files
openclaw/test/scripts/docker-e2e-helper-cli.test.ts
Peter Steinberger 7b68c81b60 test(core,plugins,tooling): remove low-value tests (batch d011) (#158811)
* test(agents): deslop s035 tests

* test(scripts): deslop s030 tests

* test(browser): deslop s039 tests

* test(gateway): deslop s034 tests

* test(copilot): deslop s040 tests

* test(auto-reply): deslop s042 tests

* test(agents): deslop s041 tests

* test(scripts): deslop s022 tests

* test(sessions): deslop s036 tests

* test(cli): deslop s037 tests

* test: adapt d011 fixtures to current owners

* test: preserve current native i18n guidance

* test: preserve d011 authorization coverage and diagnostic assertions

* test: retain fixture imports used by newer main tests

* test: fix lint in d011 test reductions

* test(copilot): remove obsolete max-lines suppression
2026-09-26 14:41:59 +00:00

708 lines
26 KiB
TypeScript

import { spawnSync } from "node:child_process";
import {
chmodSync,
existsSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { parse } from "yaml";
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
const LIVE_E2E_WORKFLOW = ".github/workflows/openclaw-live-and-e2e-checks-reusable.yml";
const EXACT_TARGET_REF = "1".repeat(40);
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function writeArtifact(fileName: string, payload: unknown) {
const root = tempDirs.make("openclaw-docker-e2e-helper-");
const file = path.join(root, fileName);
writeFileSync(file, `${JSON.stringify(payload)}\n`, "utf8");
return file;
}
function runHelper(script: string, ...args: Array<string | Record<string, string>>) {
const maybeEnv = args.at(-1);
const env =
maybeEnv && typeof maybeEnv === "object"
? (args.pop() as unknown as Record<string, string>)
: {};
const nodeArgs = script.endsWith(".mts")
? ["--import", "tsx", script, ...(args as string[])]
: [script, ...(args as string[])];
return spawnSync(process.execPath, nodeArgs, {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
GH_FORCE_TTY: "0",
NO_COLOR: "1",
...env,
},
});
}
function mockGitHub(
root: string,
artifactNames: string[],
download: string[],
runInfo: Record<string, unknown> = {
headBranch: "main",
headSha: "f".repeat(40),
url: "https://example.invalid/run",
workflowName: "Live E2E",
},
) {
const binDir = path.join(root, "bin");
const ghPath = path.join(binDir, "gh");
mkdirSync(binDir, { recursive: true });
writeFileSync(
ghPath,
[
"#!/usr/bin/env node",
"const fs = require('node:fs');",
"const path = require('node:path');",
"const args = process.argv.slice(2);",
"if (args[0] === 'run' && args[1] === 'view') {",
` console.log(JSON.stringify(${JSON.stringify(runInfo)}));`,
" process.exit(0);",
"}",
"if (args[0] === 'api') {",
` console.log(JSON.stringify(${JSON.stringify(artifactNames.map((name) => ({ expired: false, name })))}));`,
" process.exit(0);",
"}",
"if (args[0] === 'run' && args[1] === 'download') {",
...download,
" process.exit(0);",
"}",
"console.error(`unexpected gh args: ${args.join(' ')}`);",
"process.exit(1);",
"",
].join("\n"),
"utf8",
);
chmodSync(ghPath, 0o755);
return { PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}` };
}
function downloadedDir(stdout: string) {
const match = stdout.match(/^Downloaded: (.+)$/mu);
const dir = match?.[1];
if (!dir) {
throw new Error(`missing downloaded dir in stdout:\n${stdout}`);
}
return dir;
}
function emittedWorkflowCommands(stdout: string): string[] {
return stdout
.split(/\r?\n/u)
.filter((line) => line.includes("gh workflow run"))
.map((line) => line.slice(line.indexOf("gh workflow run")));
}
function expectDeclaredDispatchInputs(command: string): void {
const workflow = parse(readFileSync(LIVE_E2E_WORKFLOW, "utf8")) as {
on?: { workflow_dispatch?: { inputs?: Record<string, unknown> } };
};
const declared = new Set(Object.keys(workflow.on?.workflow_dispatch?.inputs ?? {}));
const emitted = [...command.matchAll(/(?:^|\s)-f\s+([a-z0-9_]+)=/gu)].flatMap((match) =>
match[1] === undefined ? [] : [match[1]],
);
expect(emitted.length).toBeGreaterThan(0);
for (const input of emitted) {
expect(declared.has(input), `undeclared workflow_dispatch input: ${input}`).toBe(true);
}
}
describe("Docker E2E helper CLIs", () => {
it("prints scheduler helper help without throwing a stack trace", () => {
const result = runHelper("scripts/docker-e2e.mjs", "--help");
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("node scripts/docker-e2e.mjs github-outputs <plan.json>");
});
it("prints scheduler helper usage errors without a Node stack trace", () => {
const result = runHelper("scripts/docker-e2e.mjs");
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("node scripts/docker-e2e.mjs github-outputs <plan.json>");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it("emits prerelease plugin registry planning outputs", () => {
const file = writeArtifact("plan.json", {
requiredPrepublishPluginPackages: ["@openclaw/discord", "@openclaw/feishu"],
needs: { prepublishPluginRegistry: true },
});
const result = runHelper("scripts/docker-e2e.mjs", "github-outputs", file);
expect(result.status).toBe(0);
expect(result.stdout).toContain("needs_prepublish_plugin_registry=1");
expect(result.stdout).toContain(
'required_prepublish_plugin_packages=["@openclaw/discord","@openclaw/feishu"]',
);
});
it("rejects oversized scheduler helper JSON artifacts without a Node stack trace", () => {
const file = writeArtifact("summary.json", { filler: "x".repeat(128) });
const result = runHelper("scripts/docker-e2e.mjs", "failed-reruns", file, {
OPENCLAW_DOCKER_E2E_JSON_ARTIFACT_MAX_BYTES: "64",
});
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("JSON artifact exceeded 64 bytes");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it("prints timings help without treating --help as an artifact path", () => {
const result = runHelper("scripts/docker-e2e-timings.mts", "--help");
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain(
"Usage: node --import tsx scripts/docker-e2e-timings.mts <summary.json|lane-timings.json>",
);
});
it("rejects malformed timings limits without a Node stack trace", () => {
const result = runHelper("scripts/docker-e2e-timings.mts", "summary.json", "--limit=1e3");
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("--limit must be a positive integer");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it("rejects unknown timings options without treating them as artifact paths", () => {
const result = runHelper("scripts/docker-e2e-timings.mts", "--wat");
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("unknown argument: --wat");
expect(result.stderr).toContain(
"Usage: node --import tsx scripts/docker-e2e-timings.mts <summary.json|lane-timings.json>",
);
expect(result.stderr).not.toContain("ENOENT");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it("rejects oversized timing JSON artifacts without a Node stack trace", () => {
const file = writeArtifact("summary.json", { filler: "x".repeat(128) });
const result = runHelper("scripts/docker-e2e-timings.mts", file, {
OPENCLAW_DOCKER_E2E_JSON_ARTIFACT_MAX_BYTES: "64",
});
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("JSON artifact exceeded 64 bytes");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it("rejects missing timings limits without a Node stack trace", () => {
for (const limit of [undefined, "-h"]) {
const args = ["scripts/docker-e2e-timings.mts", "summary.json", "--limit"];
const result = runHelper(args[0]!, ...args.slice(1), ...(limit === undefined ? [] : [limit]));
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("--limit requires a value");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
}
});
it("prints rerun help without detecting the GitHub repository", () => {
const result = runHelper("scripts/docker-e2e-rerun.mts", "--help");
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain(
"node --import tsx scripts/docker-e2e-rerun.mts <run-id|summary.json|failures.json>",
);
});
it("rejects oversized rerun JSON artifacts without a Node stack trace", () => {
const file = writeArtifact("summary.json", { filler: "x".repeat(128) });
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", EXACT_TARGET_REF, {
OPENCLAW_DOCKER_E2E_JSON_ARTIFACT_MAX_BYTES: "64",
});
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("JSON artifact exceeded 64 bytes");
expect(result.stderr).not.toContain("Error:");
expect(result.stderr).not.toContain("at file:");
});
it.each(["summary.json", "failures.json"])(
"prints local cleanup reruns without synthesizing Docker lane reruns from %s",
(fileName) => {
const cleanupFailure = {
lane: "cleanup-smoke",
logFile: "cleanup-smoke.log",
name: "cleanup-smoke",
rerunCommand: "pnpm test:docker:cleanup",
status: 42,
targetable: false,
};
const payload =
fileName === "summary.json"
? {
failures: [cleanupFailure],
lanes: [
{
name: "gateway-network",
status: 0,
},
],
ref: "HEAD",
status: "failed",
}
: {
lanes: [cleanupFailure],
ref: "HEAD",
status: "failed",
};
const file = writeArtifact(fileName, payload);
const result = runHelper("scripts/docker-e2e-rerun.mts", file);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("Failed Docker E2E entries: cleanup-smoke");
expect(result.stdout).toContain("No targetable failed Docker E2E lanes found.");
expect(result.stdout).toContain("- cleanup-smoke: pnpm test:docker:cleanup");
expect(result.stdout).not.toContain("docker_lanes='cleanup-smoke'");
},
);
it("ignores artifact-provided GitHub rerun commands", () => {
const file = writeArtifact("failures.json", {
lanes: [
{
ghWorkflowCommand: "echo poisoned-command",
name: "gateway-network",
status: 1,
},
],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", EXACT_TARGET_REF);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("docker_lanes='gateway-network'");
expect(result.stdout).not.toContain("poisoned-command");
});
it.each([
["failures.json", (targetRef: string) => ({ ref: targetRef })],
["summary.json", (targetRef: string) => ({ github: { selectedSha: targetRef } })],
] as const)(
"uses the exact artifact target from %s instead of the workflow head",
(name, refData) => {
const targetRef = "a".repeat(40);
const workflowHead = "b".repeat(40);
const file = writeArtifact(name, {
...refData(targetRef),
failures: [{ name: "gateway-network", status: 1 }],
lanes: [{ name: "gateway-network", status: 1 }],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, {
GITHUB_SHA: workflowHead,
});
expect(result.status, result.stderr).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain(`Ref: ${targetRef}`);
expect(result.stdout).toContain(`-f ref='${targetRef}'`);
expect(result.stdout).not.toContain(workflowHead);
},
);
it("lets an explicit target ref override artifact refs", () => {
const artifactRef = "a".repeat(40);
const explicitRef = "c".repeat(40);
const file = writeArtifact("failures.json", {
images: { bare: "ghcr.io/openclaw/openclaw-bare:artifact-a" },
lanes: [{ name: "gateway-network", status: 1 }],
ref: artifactRef,
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", explicitRef);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(`Ref: ${explicitRef}`);
expect(result.stdout).toContain(`-f ref='${explicitRef}'`);
expect(result.stdout).not.toContain(`-f ref='${artifactRef}'`);
expect(result.stdout).not.toContain("docker_e2e_bare_image=");
expect(result.stdout).not.toContain("shared_image_policy=existing-only");
});
it("requires an artifact target ref when no explicit ref is supplied", () => {
const file = writeArtifact("failures.json", {
lanes: [{ name: "gateway-network", status: 1 }],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("missing an exact target ref");
expect(result.stderr).toContain("pass --ref explicitly");
expect(result.stderr).not.toContain("HEAD");
});
it("rejects a non-exact artifact target for a targetable rerun", () => {
const file = writeArtifact("failures.json", {
lanes: [{ name: "gateway-network", status: 1 }],
ref: "HEAD",
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("invalid artifact target ref");
expect(result.stderr).toContain("full commit SHA");
});
it.each(["abc123", "A".repeat(40), "main"])(
"rejects a non-exact explicit target ref: %s",
(explicitRef) => {
const file = writeArtifact("failures.json", {
lanes: [{ name: "gateway-network", status: 1 }],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", explicitRef);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("exact lowercase 40-character target SHA");
},
);
it("preserves declared rerun inputs but ignores package and workflow refs", () => {
const file = writeArtifact("failures.json", {
lanes: [
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'full-release-validation-temp-deleted' -f package_artifact_run_id='12345' -f package_artifact_name='docker-e2e-package' -f docker_e2e_bare_image='ghcr.io/openclaw/openclaw-bare:test' -f published_upgrade_survivor_baselines='openclaw@2026.5.3' -f published_upgrade_survivor_scenarios='plugin-dependency-cleanup' -f allow_unreleased_changelog=true -f unsafe_input='do-not-copy'",
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
],
ref: EXACT_TARGET_REF,
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", EXACT_TARGET_REF);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
const combinedCommand = result.stdout.match(/Combined GitHub rerun:\n([^\n]+)/u)?.[1] ?? "";
expect(combinedCommand).not.toContain("--ref 'full-release-validation-temp-deleted'");
expect(combinedCommand).not.toContain("package_artifact_run_id=");
expect(combinedCommand).not.toContain("package_artifact_name=");
expect(combinedCommand).toContain(
"docker_e2e_bare_image='ghcr.io/openclaw/openclaw-bare:test'",
);
expect(combinedCommand).toContain("shared_image_policy=existing-only");
expect(combinedCommand).toContain("published_upgrade_survivor_baselines='openclaw@2026.5.3'");
expect(combinedCommand).toContain(
"published_upgrade_survivor_scenarios='plugin-dependency-cleanup'",
);
expect(combinedCommand).toContain("allow_unreleased_changelog=true");
expect(combinedCommand).not.toContain("unsafe_input");
expect(result.stdout).not.toContain("package_artifact_run_id=");
expect(result.stdout).not.toContain("package_artifact_name=");
expect(result.stdout).toContain("docker_e2e_bare_image='ghcr.io/openclaw/openclaw-bare:test'");
expect(result.stdout).toContain("shared_image_policy=existing-only");
expect(result.stdout).toContain("published_upgrade_survivor_baselines='openclaw@2026.5.3'");
expect(result.stdout).toContain(
"published_upgrade_survivor_scenarios='plugin-dependency-cleanup'",
);
expect(result.stdout).not.toContain("unsafe_input");
expect(result.stdout).not.toContain("do-not-copy");
const commands = emittedWorkflowCommands(result.stdout);
expect(commands.length).toBeGreaterThan(0);
for (const command of commands) {
expectDeclaredDispatchInputs(command);
}
});
it("rejects non-boolean unreleased changelog intent from summary artifacts", () => {
const file = writeArtifact("summary.json", {
allowUnreleasedChangelog: "true",
failures: [{ name: "install-e2e", status: 1 }],
github: { selectedSha: EXACT_TARGET_REF },
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).not.toContain("allow_unreleased_changelog");
});
it("groups combined reruns by recovered workflow inputs", () => {
const file = writeArtifact("failures.json", {
lanes: [
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'release/2026.6' -f published_upgrade_survivor_baselines='openclaw@2026.5.3' -f allow_unreleased_changelog=1",
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'release/2026.6' -f published_upgrade_survivor_baselines='openclaw@2026.5.2'",
name: "published-upgrade-survivor-openclaw-2026-5-2",
status: 1,
},
],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", EXACT_TARGET_REF);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("Combined GitHub reruns:");
expect(result.stdout).toContain(
"- published-upgrade-survivor-openclaw-2026-5-3: gh workflow run",
);
expect(result.stdout).toContain(
"- published-upgrade-survivor-openclaw-2026-5-2: gh workflow run",
);
expect(result.stdout).toContain("docker_lanes='published-upgrade-survivor-openclaw-2026-5-3'");
expect(result.stdout).toContain("docker_lanes='published-upgrade-survivor-openclaw-2026-5-2'");
expect(result.stdout).not.toContain(
"docker_lanes='published-upgrade-survivor-openclaw-2026-5-3 published-upgrade-survivor-openclaw-2026-5-2'",
);
expect(result.stdout).not.toContain("allow_unreleased_changelog");
});
it("merges duplicate lane entries before printing reruns", () => {
const file = writeArtifact("failures.json", {
lanes: [
{
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
{
ghWorkflowCommand:
"gh workflow run 'openclaw-live-and-e2e-checks-reusable.yml' --ref 'release/2026.6' -f published_upgrade_survivor_baselines='openclaw@2026.5.3'",
name: "published-upgrade-survivor-openclaw-2026-5-3",
status: 1,
},
],
status: "failed",
});
const result = runHelper("scripts/docker-e2e-rerun.mts", file, "--ref", EXACT_TARGET_REF);
expect(result.status).toBe(0);
expect(result.stderr).toBe("");
const combinedCommand = result.stdout.match(/Combined GitHub rerun:\n([^\n]+)/u)?.[1] ?? "";
expect(combinedCommand).not.toContain("--ref 'release/2026.6'");
expect(combinedCommand).toContain("published_upgrade_survivor_baselines='openclaw@2026.5.3'");
});
it("downloads GitHub run artifacts into distinct default directories", () => {
const root = tempDirs.make("openclaw-docker-e2e-rerun-gh-");
const generatedDirs: string[] = [];
try {
const env = mockGitHub(
root,
["docker-e2e-gateway-network"],
[
" const dir = args[args.indexOf('--dir') + 1];",
" fs.mkdirSync(path.join(dir, 'artifact'), { recursive: true });",
" fs.writeFileSync(path.join(dir, 'artifact', 'failures.json'), JSON.stringify({",
" lanes: [{ name: 'gateway-network', status: 1 }],",
" ref: 'd'.repeat(40),",
" status: 'failed',",
" }));",
],
{
conclusion: "failure",
databaseId: 12345,
headBranch: "main",
headSha: "abc123",
status: "completed",
url: "https://github.com/openclaw/openclaw/actions/runs/12345",
workflowName: "OpenClaw Live and E2E Checks",
},
);
const first = runHelper(
"scripts/docker-e2e-rerun.mts",
"12345",
"--repo",
"openclaw/openclaw",
env,
);
const second = runHelper(
"scripts/docker-e2e-rerun.mts",
"12345",
"--repo",
"openclaw/openclaw",
env,
);
expect(first.status, first.stderr).toBe(0);
expect(second.status, second.stderr).toBe(0);
const firstDir = downloadedDir(first.stdout);
const secondDir = downloadedDir(second.stdout);
generatedDirs.push(firstDir, secondDir);
expect(firstDir).not.toBe(secondDir);
expect(path.basename(firstDir)).toMatch(/^openclaw-docker-e2e-rerun-12345-/u);
expect(path.basename(secondDir)).toMatch(/^openclaw-docker-e2e-rerun-12345-/u);
const firstArtifactDir = readdirSync(firstDir, { withFileTypes: true }).find((entry) =>
entry.isDirectory(),
);
const secondArtifactDir = readdirSync(secondDir, { withFileTypes: true }).find((entry) =>
entry.isDirectory(),
);
expect(firstArtifactDir).toBeDefined();
expect(secondArtifactDir).toBeDefined();
expect(
existsSync(path.join(firstDir, firstArtifactDir?.name ?? "", "artifact", "failures.json")),
).toBe(true);
expect(
existsSync(
path.join(secondDir, secondArtifactDir?.name ?? "", "artifact", "failures.json"),
),
).toBe(true);
expect(first.stdout).toContain(`-f ref='${"d".repeat(40)}'`);
expect(first.stdout).not.toContain("-f ref='abc123'");
expect(second.stdout).toContain(`-f ref='${"d".repeat(40)}'`);
} finally {
for (const dir of generatedDirs) {
rmSync(dir, { force: true, recursive: true });
}
}
});
it("isolates files with the same name across downloaded artifacts", () => {
const root = tempDirs.make("openclaw-docker-e2e-rerun-collisions-");
const env = mockGitHub(
root,
["docker-e2e-a", "docker-e2e-b"],
[
" const name = args[args.indexOf('--name') + 1];",
" const dir = args[args.indexOf('--dir') + 1];",
" fs.mkdirSync(dir, { recursive: true });",
" const plan = path.join(dir, 'targeted-plan.json');",
" if (fs.existsSync(plan)) {",
" console.error('targeted-plan.json already exists');",
" process.exit(2);",
" }",
" fs.writeFileSync(plan, '{}');",
" fs.writeFileSync(path.join(dir, 'failures.json'), JSON.stringify({",
" lanes: [{ name: name.endsWith('-a') ? 'gateway-network' : 'install-e2e', status: 1 }],",
" ref: 'd'.repeat(40),",
" status: 'failed',",
" }));",
],
);
const outputDir = path.join(root, "artifacts");
const result = runHelper(
"scripts/docker-e2e-rerun.mts",
"12345",
"--repo",
"openclaw/openclaw",
"--dir",
outputDir,
env,
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain("gateway-network");
expect(result.stdout).toContain("install-e2e");
const artifactDirs = readdirSync(outputDir, { withFileTypes: true }).filter((entry) =>
entry.isDirectory(),
);
expect(artifactDirs).toHaveLength(2);
for (const entry of artifactDirs) {
expect(existsSync(path.join(outputDir, entry.name, "targeted-plan.json"))).toBe(true);
}
});
it("fails closed when downloaded artifacts contain mixed target refs", () => {
const root = tempDirs.make("openclaw-docker-e2e-rerun-mixed-refs-");
const env = mockGitHub(
root,
["docker-e2e-a", "docker-e2e-b"],
[
" const name = args[args.indexOf('--name') + 1];",
" const dir = args[args.indexOf('--dir') + 1];",
" const target = path.join(dir, name);",
" fs.mkdirSync(target, { recursive: true });",
" fs.writeFileSync(path.join(target, 'failures.json'), JSON.stringify({",
" lanes: [{ name: name.endsWith('-a') ? 'gateway-network' : 'install-e2e', status: 1 }],",
" ref: (name.endsWith('-a') ? 'a' : 'b').repeat(40),",
" status: 'failed',",
" }));",
],
);
const outputDir = path.join(root, "artifacts");
const result = runHelper(
"scripts/docker-e2e-rerun.mts",
"12345",
"--repo",
"openclaw/openclaw",
"--dir",
outputDir,
env,
);
expect(result.status).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain("mixed target refs");
expect(result.stderr).toContain("a".repeat(40));
expect(result.stderr).toContain("b".repeat(40));
const explicitRef = "c".repeat(40);
const override = runHelper(
"scripts/docker-e2e-rerun.mts",
"12345",
"--repo",
"openclaw/openclaw",
"--dir",
outputDir,
"--ref",
explicitRef,
env,
);
expect(override.status, override.stderr).toBe(0);
expect(override.stdout).toContain(`-f ref='${explicitRef}'`);
});
});