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
398 lines
12 KiB
TypeScript
398 lines
12 KiB
TypeScript
// Release Scenarios Assertions tests cover release scenarios assertions script behavior.
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const ASSERTIONS_SCRIPT = "scripts/e2e/lib/release-scenarios/assertions.mjs";
|
|
const DISABLE_EXPERIMENTAL_WARNING = "--disable-warning=ExperimentalWarning";
|
|
|
|
function nodeOptionsWithoutExperimentalWarnings(): string {
|
|
const current = process.env.NODE_OPTIONS ?? "";
|
|
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(args: string[], env?: NodeJS.ProcessEnv) {
|
|
return spawnSync(process.execPath, [ASSERTIONS_SCRIPT, ...args], {
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
...env,
|
|
NODE_OPTIONS: nodeOptionsWithoutExperimentalWarnings(),
|
|
},
|
|
});
|
|
}
|
|
|
|
function writeAuthProfileStoreSqlite(agentDir: string, store: unknown) {
|
|
mkdirSync(agentDir, { recursive: true });
|
|
const db = new DatabaseSync(path.join(agentDir, "openclaw-agent.sqlite"));
|
|
try {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS auth_profile_store (
|
|
store_key TEXT NOT NULL PRIMARY KEY,
|
|
store_json TEXT NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
`);
|
|
db.prepare(
|
|
`
|
|
INSERT INTO auth_profile_store (store_key, store_json, updated_at)
|
|
VALUES (?, ?, ?)
|
|
`,
|
|
).run("primary", JSON.stringify(store), Date.now());
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
describe("release scenario assertions", () => {
|
|
it("rejects loose mock OpenAI port args", () => {
|
|
const result = runAssertion(["configure-mock-openai", "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"');
|
|
});
|
|
|
|
it("scans large files when checking release scenario output text", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const outputPath = path.join(root, "output.log");
|
|
|
|
try {
|
|
const needlePrefix = "release-market";
|
|
writeFileSync(
|
|
outputPath,
|
|
`${"x".repeat(64 * 1024 - needlePrefix.length)}${needlePrefix}place-plugin:v2\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const result = runAssertion([
|
|
"assert-file-contains",
|
|
outputPath,
|
|
"release-marketplace-plugin:v2",
|
|
]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe("");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("bounds release output text assertion diagnostics", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
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(["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("scans large request logs for image describe responses", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const outputPath = path.join(root, "describe.json");
|
|
const requestLogPath = path.join(root, "requests.jsonl");
|
|
|
|
try {
|
|
writeJson(outputPath, {
|
|
capability: "image.describe",
|
|
ok: true,
|
|
outputs: [{ provider: "openai", text: "OPENCLAW_E2E_OK describe" }],
|
|
});
|
|
const endpointPrefix = "/v1/res";
|
|
writeFileSync(
|
|
requestLogPath,
|
|
`${"x".repeat(64 * 1024 - endpointPrefix.length)}${endpointPrefix}ponses\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const result = runAssertion(["assert-image-describe", outputPath, requestLogPath]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe("");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects oversized JSON artifacts before parsing release scenario outputs", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const outputPath = path.join(root, "describe.json");
|
|
const requestLogPath = path.join(root, "requests.jsonl");
|
|
|
|
try {
|
|
writeFileSync(
|
|
outputPath,
|
|
`DO_NOT_DUMP_OLD_JSON${"x".repeat(2 * 1024 * 1024)}\nrecent json tail`,
|
|
"utf8",
|
|
);
|
|
writeFileSync(requestLogPath, "/v1/responses\n", "utf8");
|
|
|
|
const result = runAssertion(["assert-image-describe", outputPath, requestLogPath]);
|
|
|
|
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("scans large request logs for image generation requests", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const outputPath = path.join(root, "generate.json");
|
|
const requestLogPath = path.join(root, "requests.jsonl");
|
|
const imagePath = path.join(root, "generated.png");
|
|
|
|
try {
|
|
writeFileSync(imagePath, "png", "utf8");
|
|
writeJson(outputPath, {
|
|
capability: "image.generate",
|
|
ok: true,
|
|
outputs: [{ mimeType: "image/png", path: imagePath }],
|
|
provider: "openai",
|
|
});
|
|
const endpointPrefix = "/v1/images/gener";
|
|
writeFileSync(
|
|
requestLogPath,
|
|
`${"x".repeat(64 * 1024 - endpointPrefix.length)}${endpointPrefix}ations\n`,
|
|
"utf8",
|
|
);
|
|
|
|
const result = runAssertion(["assert-image-generate", outputPath, requestLogPath]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe("");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("accepts OpenAI env refs from the SQLite auth profile store", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const home = path.join(root, "home");
|
|
const stateDir = path.join(home, ".openclaw");
|
|
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
|
const configPath = path.join(stateDir, "openclaw.json");
|
|
|
|
try {
|
|
writeJson(configPath, {
|
|
auth: {
|
|
profiles: {
|
|
"openai:api-key": { provider: "openai", mode: "api_key" },
|
|
},
|
|
},
|
|
});
|
|
writeAuthProfileStoreSqlite(agentDir, {
|
|
version: 1,
|
|
profiles: {
|
|
"openai:api-key": {
|
|
type: "api_key",
|
|
provider: "openai",
|
|
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = runAssertion(["assert-openai-env-ref", "sk-test-raw-key"], {
|
|
HOME: home,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
});
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe("");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects SQLite auth profile stores without a usable OpenAI env ref", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const home = path.join(root, "home");
|
|
const stateDir = path.join(home, ".openclaw");
|
|
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
|
const configPath = path.join(stateDir, "openclaw.json");
|
|
|
|
try {
|
|
writeJson(configPath, {
|
|
auth: {
|
|
profiles: {
|
|
"openai:api-key": { provider: "openai", mode: "api_key" },
|
|
},
|
|
},
|
|
});
|
|
writeAuthProfileStoreSqlite(agentDir, {
|
|
version: 1,
|
|
profiles: {
|
|
"openai:api-key": { note: "OPENAI_API_KEY" },
|
|
},
|
|
});
|
|
|
|
const result = runAssertion(["assert-openai-env-ref", "sk-test-raw-key"], {
|
|
HOME: home,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
});
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("OpenAI env ref was not persisted");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects inline OpenAI keys in the SQLite auth profile store", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const home = path.join(root, "home");
|
|
const stateDir = path.join(home, ".openclaw");
|
|
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
|
const configPath = path.join(stateDir, "openclaw.json");
|
|
|
|
try {
|
|
writeJson(configPath, {
|
|
auth: {
|
|
profiles: {
|
|
"openai:api-key": { provider: "openai", mode: "api_key" },
|
|
},
|
|
},
|
|
});
|
|
writeAuthProfileStoreSqlite(agentDir, {
|
|
version: 1,
|
|
profiles: {
|
|
"openai:api-key": {
|
|
type: "api_key",
|
|
provider: "openai",
|
|
key: "sk-test-raw-key",
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = runAssertion(["assert-openai-env-ref", "sk-test-raw-key"], {
|
|
HOME: home,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
});
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("raw OpenAI key was persisted");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rejects raw OpenAI keys leaked outside the SQLite auth profile store", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const home = path.join(root, "home");
|
|
const stateDir = path.join(home, ".openclaw");
|
|
const agentDir = path.join(stateDir, "agents", "main", "agent");
|
|
const configPath = path.join(stateDir, "openclaw.json");
|
|
|
|
try {
|
|
writeJson(configPath, {
|
|
auth: {
|
|
profiles: {
|
|
"openai:api-key": { provider: "openai", mode: "api_key" },
|
|
},
|
|
},
|
|
models: {
|
|
providers: {
|
|
openai: { apiKey: "sk-test-raw-key" },
|
|
},
|
|
},
|
|
});
|
|
writeAuthProfileStoreSqlite(agentDir, {
|
|
version: 1,
|
|
profiles: {
|
|
"openai:api-key": {
|
|
type: "api_key",
|
|
provider: "openai",
|
|
keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = runAssertion(["assert-openai-env-ref", "sk-test-raw-key"], {
|
|
HOME: home,
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
});
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("raw OpenAI key was persisted");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("passes when the installed package version matches the candidate version", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const packageRoot = path.join(root, "openclaw");
|
|
|
|
try {
|
|
writeJson(path.join(packageRoot, "package.json"), {
|
|
name: "openclaw",
|
|
version: "2026.5.26",
|
|
});
|
|
|
|
const result = runAssertion([
|
|
"assert-package-version",
|
|
packageRoot,
|
|
"2026.5.26",
|
|
"candidate",
|
|
]);
|
|
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe("");
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("fails when the global install still points at the baseline version", () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-"));
|
|
const packageRoot = path.join(root, "openclaw");
|
|
|
|
try {
|
|
writeJson(path.join(packageRoot, "package.json"), {
|
|
name: "openclaw",
|
|
version: "2026.5.22",
|
|
});
|
|
|
|
const result = runAssertion([
|
|
"assert-package-version",
|
|
packageRoot,
|
|
"2026.5.26",
|
|
"candidate",
|
|
]);
|
|
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain(
|
|
"candidate package version mismatch: expected 2026.5.26, got 2026.5.22",
|
|
);
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
});
|