Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
491
test/scripts/release-user-journey-assertions.test.ts
Normal file
491
test/scripts/release-user-journey-assertions.test.ts
Normal file
@@ -0,0 +1,491 @@
|
||||
// Release User Journey Assertions tests cover release user journey assertions script behavior.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { createServer, type AddressInfo, type Socket } from "node:net";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runReleaseUserJourneyAssertion } from "../../scripts/e2e/lib/release-user-journey/assertions.mjs";
|
||||
import { withEnvAsync } from "../../src/test-utils/env.js";
|
||||
|
||||
const ASSERTIONS_SCRIPT = "scripts/e2e/lib/release-user-journey/assertions.mjs";
|
||||
const DISABLE_EXPERIMENTAL_WARNING = "--disable-warning=ExperimentalWarning";
|
||||
|
||||
function nodeOptionsWithoutExperimentalWarnings(extra?: string): string {
|
||||
const current = [process.env.NODE_OPTIONS, extra].filter(Boolean).join(" ");
|
||||
return current.includes(DISABLE_EXPERIMENTAL_WARNING)
|
||||
? current
|
||||
: [current, DISABLE_EXPERIMENTAL_WARNING].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
function writeJson(filePath: string, value: unknown) {
|
||||
mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function runAssertion(
|
||||
home: string,
|
||||
args: string[],
|
||||
options: { env?: Record<string, string>; timeoutMs?: number } = {},
|
||||
) {
|
||||
return spawnSync(process.execPath, [ASSERTIONS_SCRIPT, ...args], {
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
...options.env,
|
||||
NODE_OPTIONS: nodeOptionsWithoutExperimentalWarnings(options.env?.NODE_OPTIONS),
|
||||
},
|
||||
killSignal: "SIGKILL",
|
||||
timeout: options.timeoutMs,
|
||||
});
|
||||
}
|
||||
|
||||
async function waitUntil(matches: () => boolean, label: string): Promise<void> {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < 1000) {
|
||||
if (matches()) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
}
|
||||
throw new Error(`timed out waiting for ${label}`);
|
||||
}
|
||||
|
||||
async function startTcpFixtureServer(handler: (socket: Socket) => void): Promise<{
|
||||
port: number;
|
||||
stop: () => Promise<void>;
|
||||
}> {
|
||||
const sockets = new Set<Socket>();
|
||||
const server = createServer(handler);
|
||||
server.on("connection", (socket) => {
|
||||
sockets.add(socket);
|
||||
socket.on("error", () => undefined);
|
||||
socket.on("close", () => sockets.delete(socket));
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
const address = server.address() as AddressInfo;
|
||||
return {
|
||||
port: address.port,
|
||||
stop: async () => {
|
||||
for (const socket of sockets) {
|
||||
socket.destroy();
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("release user journey assertions", () => {
|
||||
it("rejects loose mock OpenAI port args", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
|
||||
try {
|
||||
const result = runAssertion(home, ["configure-mock-model", "1e3"]);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("mock OpenAI port must be a TCP port from 1 to 65535");
|
||||
expect(result.stderr).toContain('"1e3"');
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("scans large files when checking release user journey output text", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const outputPath = path.join(root, "output.log");
|
||||
|
||||
try {
|
||||
const needlePrefix = "journey-plugin";
|
||||
writeFileSync(
|
||||
outputPath,
|
||||
`${"x".repeat(64 * 1024 - needlePrefix.length)}${needlePrefix}-a:pong\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = runAssertion(home, [
|
||||
"assert-file-contains",
|
||||
outputPath,
|
||||
"journey-plugin-a:pong",
|
||||
]);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds release user journey output assertion diagnostics", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const outputPath = path.join(root, "output.log");
|
||||
|
||||
try {
|
||||
writeFileSync(
|
||||
outputPath,
|
||||
`DO_NOT_DUMP_OLD_OUTPUT${"x".repeat(70 * 1024)}\nrecent output tail\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = runAssertion(home, ["assert-file-contains", outputPath, "missing"]);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("Output tail:");
|
||||
expect(result.stderr).toContain("recent output tail");
|
||||
expect(result.stderr).not.toContain("DO_NOT_DUMP_OLD_OUTPUT");
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects oversized JSON artifacts before parsing release user journey config", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const configPath = path.join(home, ".openclaw", "openclaw.json");
|
||||
|
||||
try {
|
||||
mkdirSync(path.dirname(configPath), { recursive: true });
|
||||
writeFileSync(
|
||||
configPath,
|
||||
`DO_NOT_DUMP_OLD_JSON${"x".repeat(2 * 1024 * 1024)}\nrecent json tail`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = runAssertion(home, ["configure-mock-model", "18080"]);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("JSON artifact exceeded");
|
||||
expect(result.stderr).toContain("recent json tail");
|
||||
expect(result.stderr).not.toContain("DO_NOT_DUMP_OLD_JSON");
|
||||
expect(result.stderr.length).toBeLessThan(80 * 1024);
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("fails when uninstall leaves the managed plugin directory behind", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const pluginId = "journey-plugin-a";
|
||||
const installPath = path.join(home, ".openclaw", "extensions", pluginId);
|
||||
const installPathFile = path.join(root, "install-path.txt");
|
||||
|
||||
try {
|
||||
writeJson(path.join(home, ".openclaw", "openclaw.json"), {
|
||||
plugins: {
|
||||
entries: {},
|
||||
allow: [],
|
||||
deny: [],
|
||||
},
|
||||
});
|
||||
writeJson(path.join(home, ".openclaw", "plugins", "installs.json"), {
|
||||
installRecords: {},
|
||||
});
|
||||
mkdirSync(installPath, { recursive: true });
|
||||
writeFileSync(installPathFile, installPath, "utf8");
|
||||
|
||||
const result = runAssertion(home, ["assert-plugin-uninstalled", pluginId, installPathFile]);
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain("managed plugin directory still present");
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("passes after uninstall clears config, records, and managed files", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const installPathFile = path.join(root, "install-path.txt");
|
||||
|
||||
try {
|
||||
writeJson(path.join(home, ".openclaw", "openclaw.json"), {
|
||||
plugins: {
|
||||
entries: {},
|
||||
allow: [],
|
||||
deny: [],
|
||||
},
|
||||
});
|
||||
writeJson(path.join(home, ".openclaw", "plugins", "installs.json"), {
|
||||
installRecords: {},
|
||||
});
|
||||
writeFileSync(
|
||||
installPathFile,
|
||||
path.join(home, ".openclaw", "extensions", "journey-plugin-a"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const result = runAssertion(home, [
|
||||
"assert-plugin-uninstalled",
|
||||
"journey-plugin-a",
|
||||
installPathFile,
|
||||
]);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("remembers the installed plugin path from the install record", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const pluginId = "journey-plugin-a";
|
||||
const sourcePath = path.join(root, "source", pluginId);
|
||||
const installPath = path.join(home, ".openclaw", "extensions", pluginId);
|
||||
const installPathFile = path.join(root, "install-path.txt");
|
||||
const sourcePathFile = path.join(root, "source-path.txt");
|
||||
|
||||
try {
|
||||
mkdirSync(sourcePath, { recursive: true });
|
||||
mkdirSync(installPath, { recursive: true });
|
||||
writeJson(path.join(home, ".openclaw", "plugins", "installs.json"), {
|
||||
installRecords: {
|
||||
[pluginId]: {
|
||||
source: "path",
|
||||
sourcePath,
|
||||
installPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = runAssertion(home, [
|
||||
"remember-plugin-install-path",
|
||||
pluginId,
|
||||
installPathFile,
|
||||
sourcePathFile,
|
||||
sourcePath,
|
||||
]);
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts ready ClickClack fixture state", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) => {
|
||||
const body = JSON.stringify({ socketCount: 1 });
|
||||
socket.end(
|
||||
`HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`,
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home, OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "1000" }, () =>
|
||||
runReleaseUserJourneyAssertion("wait-clickclack-socket", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"1",
|
||||
]),
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("cancels successful ClickClack inbound response bodies", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
let socketClosed = false;
|
||||
const server = await startTcpFixtureServer((socket) => {
|
||||
socket.on("close", () => {
|
||||
socketClosed = true;
|
||||
});
|
||||
socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nleft-open");
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home, OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "1000" }, () =>
|
||||
runReleaseUserJourneyAssertion("post-clickclack-inbound", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"hello",
|
||||
]),
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
await waitUntil(() => socketClosed, "ClickClack inbound socket close");
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds stalled ClickClack fixture HTTP probes", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) =>
|
||||
socket.write("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"),
|
||||
);
|
||||
|
||||
try {
|
||||
const startedAt = Date.now();
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home, OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "100" }, () =>
|
||||
runReleaseUserJourneyAssertion("wait-clickclack-socket", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"1",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow("Timed out waiting for ClickClack websocket connection");
|
||||
expect(Date.now() - startedAt).toBeLessThan(2500);
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects loose HTTP timeout env values instead of parsing prefixes", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) =>
|
||||
socket.write("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"),
|
||||
);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home, OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "100ms" }, () =>
|
||||
runReleaseUserJourneyAssertion("wait-clickclack-socket", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"1",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS must be a positive integer. Got: "100ms"',
|
||||
);
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects loose ClickClack wait timeout args instead of parsing prefixes", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const statePath = path.join(root, "state.json");
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home }, () =>
|
||||
runReleaseUserJourneyAssertion("wait-clickclack-socket", ["http://127.0.0.1:9", "1e3"]),
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'ClickClack websocket timeout seconds must be a positive integer. Got: "1e3"',
|
||||
);
|
||||
await expect(
|
||||
withEnvAsync({ HOME: home }, () =>
|
||||
runReleaseUserJourneyAssertion("wait-clickclack-reply", [
|
||||
statePath,
|
||||
"OPENCLAW_E2E_OK",
|
||||
"30s",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow('ClickClack reply timeout seconds must be a positive integer. Got: "30s"');
|
||||
} finally {
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds ClickClack fixture error response bodies", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) => {
|
||||
const body = "x".repeat(128);
|
||||
socket.end(
|
||||
`HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nContent-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`,
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync(
|
||||
{
|
||||
HOME: home,
|
||||
OPENCLAW_RELEASE_USER_JOURNEY_HTTP_BODY_MAX_BYTES: "16",
|
||||
OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "1000",
|
||||
},
|
||||
() =>
|
||||
runReleaseUserJourneyAssertion("post-clickclack-inbound", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"hello",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow("ClickClack inbound response body exceeded 16 bytes");
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the ClickClack HTTP timeout active while reading error bodies", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) => {
|
||||
socket.write("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\n\r\npartial");
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync(
|
||||
{
|
||||
HOME: home,
|
||||
OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "25",
|
||||
},
|
||||
() =>
|
||||
runReleaseUserJourneyAssertion("post-clickclack-inbound", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"hello",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow(`http://127.0.0.1:${server.port}/fixture/inbound timed out after 25ms`);
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects loose body byte env values instead of parsing prefixes", async () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-"));
|
||||
const home = path.join(root, "home");
|
||||
const server = await startTcpFixtureServer((socket) => {
|
||||
const body = "x".repeat(128);
|
||||
socket.end(
|
||||
`HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/plain\r\nContent-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`,
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
await expect(
|
||||
withEnvAsync(
|
||||
{
|
||||
HOME: home,
|
||||
OPENCLAW_RELEASE_USER_JOURNEY_HTTP_BODY_MAX_BYTES: "16bytes",
|
||||
OPENCLAW_RELEASE_USER_JOURNEY_HTTP_TIMEOUT_MS: "1000",
|
||||
},
|
||||
() =>
|
||||
runReleaseUserJourneyAssertion("post-clickclack-inbound", [
|
||||
`http://127.0.0.1:${server.port}`,
|
||||
"hello",
|
||||
]),
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'OPENCLAW_RELEASE_USER_JOURNEY_HTTP_BODY_MAX_BYTES must be a positive integer. Got: "16bytes"',
|
||||
);
|
||||
} finally {
|
||||
await server.stop();
|
||||
rmSync(root, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user