
























1+// Codex Install Assertions tests cover Codex plugin install E2E helpers.
2+import { spawnSync } from "node:child_process";
3+import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4+import os from "node:os";
5+import path from "node:path";
6+import { DatabaseSync } from "node:sqlite";
7+import { afterEach, describe, expect, it } from "vitest";
8+import {
9+assertPathInside,
10+findPackageJson,
11+npmProjectRootForInstalledPackage,
12+} from "../../scripts/e2e/lib/codex-install-utils.mjs";
13+import { cleanupTempDirs, makeTempDir } from "../helpers/temp-dir.js";
14+15+const ASSERTIONS_SCRIPT = "scripts/e2e/lib/codex-on-demand/assertions.mjs";
16+const DISABLE_EXPERIMENTAL_WARNING = "--disable-warning=ExperimentalWarning";
17+const tempDirs: string[] = [];
18+const tmpFixtureFiles = ["/tmp/openclaw-codex-inspect.json", "/tmp/openclaw-plugins-list.json"];
19+20+afterEach(() => {
21+for (const file of tmpFixtureFiles) {
22+rmSync(file, { force: true });
23+}
24+cleanupTempDirs(tempDirs);
25+});
26+27+function nodeOptionsWithoutExperimentalWarnings(): string {
28+const current = process.env.NODE_OPTIONS ?? "";
29+return current.includes(DISABLE_EXPERIMENTAL_WARNING)
30+ ? current
31+ : [current, DISABLE_EXPERIMENTAL_WARNING].filter(Boolean).join(" ");
32+}
33+34+function writeJson(filePath: string, value: unknown) {
35+mkdirSync(path.dirname(filePath), { recursive: true });
36+writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
37+}
38+39+function writeAuthProfileStoreSqlite(agentDir: string) {
40+mkdirSync(agentDir, { recursive: true });
41+const db = new DatabaseSync(path.join(agentDir, "openclaw-agent.sqlite"));
42+try {
43+db.exec(`
44+ CREATE TABLE IF NOT EXISTS auth_profile_store (
45+ store_key TEXT NOT NULL PRIMARY KEY,
46+ store_json TEXT NOT NULL,
47+ updated_at INTEGER NOT NULL
48+ );
49+ `);
50+db.prepare(
51+`
52+ INSERT INTO auth_profile_store (store_key, store_json, updated_at)
53+ VALUES (?, ?, ?)
54+ `,
55+).run(
56+"primary",
57+JSON.stringify({
58+version: 1,
59+profiles: {
60+"openai:api-key": {
61+type: "api_key",
62+provider: "openai",
63+keyRef: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
64+},
65+},
66+}),
67+Date.now(),
68+);
69+} finally {
70+db.close();
71+}
72+}
73+74+function runCodexOnDemandAssertions(root: string) {
75+return spawnSync(process.execPath, [ASSERTIONS_SCRIPT], {
76+encoding: "utf8",
77+env: {
78+ ...process.env,
79+HOME: path.join(root, "home"),
80+NODE_OPTIONS: nodeOptionsWithoutExperimentalWarnings(),
81+OPENCLAW_CONFIG_PATH: path.join(root, "state", "openclaw.json"),
82+OPENCLAW_STATE_DIR: path.join(root, "state"),
83+},
84+});
85+}
86+87+function createCodexInstallFixture(root: string) {
88+const stateDir = path.join(root, "state");
89+const npmRoot = path.join(stateDir, "npm");
90+const installPath = path.join(npmRoot, "projects", "codex", "node_modules", "@openclaw", "codex");
91+const projectRoot = npmProjectRootForInstalledPackage(installPath, "@openclaw/codex");
92+writeJson(path.join(installPath, "package.json"), { name: "@openclaw/codex" });
93+const openAiCodexRoot = path.join(projectRoot, "node_modules", "@openai", "codex");
94+writeJson(path.join(openAiCodexRoot, "package.json"), {
95+name: "@openai/codex",
96+bin: { codex: "bin/codex.js" },
97+});
98+const codexBin = path.join(openAiCodexRoot, "bin", "codex.js");
99+mkdirSync(path.dirname(codexBin), { recursive: true });
100+writeFileSync(codexBin, "#!/usr/bin/env node\n", { mode: 0o755 });
101+chmodSync(codexBin, 0o755);
102+writeJson(path.join(stateDir, "openclaw.json"), {
103+agents: { defaults: { model: { primary: "openai/gpt-5.5" } } },
104+models: { providers: { openai: { agentRuntime: { id: "codex" } } } },
105+plugins: {
106+installs: {
107+codex: {
108+ installPath,
109+source: "npm",
110+spec: "npm:@openclaw/codex",
111+},
112+},
113+},
114+});
115+writeJson("/tmp/openclaw-codex-inspect.json", {
116+plugin: { id: "codex", status: "loaded", agentHarnessIds: ["codex"] },
117+});
118+writeJson("/tmp/openclaw-plugins-list.json", {
119+plugins: [{ id: "codex", enabled: true, status: "loaded" }],
120+});
121+writeAuthProfileStoreSqlite(path.join(stateDir, "agents", "main", "agent"));
122+}
123+124+describe("Codex install helpers", () => {
125+it("resolves package roots and package manifests inside managed npm installs", () => {
126+const root = makeTempDir(tempDirs, "openclaw-codex-install-utils-");
127+const packageRoot = path.join(
128+root,
129+"state",
130+"npm",
131+"projects",
132+"codex",
133+"node_modules",
134+"@openclaw",
135+"codex",
136+);
137+const projectRoot = npmProjectRootForInstalledPackage(packageRoot, "@openclaw/codex");
138+const dependencyPackage = path.join(
139+projectRoot,
140+"node_modules",
141+"@openai",
142+"codex",
143+"package.json",
144+);
145+writeJson(dependencyPackage, { name: "@openai/codex" });
146+147+expect(projectRoot).toBe(path.join(root, "state", "npm", "projects", "codex"));
148+expect(findPackageJson("@openai/codex", [packageRoot, projectRoot])).toBe(dependencyPackage);
149+expect(() =>
150+assertPathInside(projectRoot, dependencyPackage, "codex dependency"),
151+).not.toThrow();
152+expect(() => assertPathInside(projectRoot, os.tmpdir(), "outside path")).toThrow(
153+"outside path resolved outside",
154+);
155+});
156+157+it("accepts a complete on-demand Codex npm install fixture", () => {
158+const root = makeTempDir(tempDirs, "openclaw-codex-on-demand-");
159+createCodexInstallFixture(root);
160+161+const result = runCodexOnDemandAssertions(root);
162+163+expect(result.status).toBe(0);
164+expect(result.stderr).toBe("");
165+});
166+167+it("rejects on-demand fixtures missing the managed @openai/codex dependency", () => {
168+const root = makeTempDir(tempDirs, "openclaw-codex-on-demand-missing-");
169+createCodexInstallFixture(root);
170+rmSync(path.join(root, "state", "npm", "projects", "codex", "node_modules", "@openai"), {
171+force: true,
172+recursive: true,
173+});
174+175+const result = runCodexOnDemandAssertions(root);
176+177+expect(result.status).not.toBe(0);
178+expect(result.stderr).toContain("missing @openai/codex dependency under managed npm root");
179+});
180+181+it("rejects on-demand fixtures missing the managed Codex executable", () => {
182+const root = makeTempDir(tempDirs, "openclaw-codex-on-demand-missing-bin-");
183+createCodexInstallFixture(root);
184+rmSync(
185+path.join(
186+root,
187+"state",
188+"npm",
189+"projects",
190+"codex",
191+"node_modules",
192+"@openai",
193+"codex",
194+"bin",
195+),
196+{ force: true, recursive: true },
197+);
198+199+const result = runCodexOnDemandAssertions(root);
200+201+expect(result.status).not.toBe(0);
202+expect(result.stderr).toContain("missing managed Codex binary:");
203+});
204+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。