Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion tests/cli/cli-connect-readiness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { beforeAll, describe, expect, spyOn, test } from "bun:test";
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import { chmodSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { chmodSync, copyFileSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { delimiter, join } from "node:path";
import { COLD_SPAWN_WARMUP_HOOK_BUDGET_MS, warmColdSpawn } from "../helpers/cold-spawn-warmup";
Expand Down Expand Up @@ -55,6 +55,10 @@ type ProbeResult = {
newerVersion?: string;
selectionUnchanged: boolean;
failures: RuntimeProbeFailure[];
installedIsolation?: Record<"isolated" | "inherited", {
sentinelCalls: Array<{ args: string[]; completed: boolean }>;
lowerCalls: string[];
}>;
};
};

Expand Down Expand Up @@ -104,6 +108,8 @@ function runStatusProbe(options: {
preferred?: "valid" | "failed" | "missing";
persisted?: boolean;
fullDiagnostics?: boolean;
/** Windows-only negative control; always a test-owned external install root. */
externalInstalledRoot?: string;
/** "connect" drives `ocx connect status`; "status" drives the general `ocx status` collector. */
surface?: "connect" | "status";
/**
Expand Down Expand Up @@ -158,6 +164,8 @@ function runStatusProbe(options: {
runtimeEnv.PATH = [selectedDir, lowerDir].join(delimiter);
runtimeEnv.HOME = opencodexHome;
runtimeEnv.USERPROFILE = opencodexHome;
// Installed Windows runtimes are discovered outside PATH under LOCALAPPDATA.
runtimeEnv.LOCALAPPDATA = join(opencodexHome, "local-app-data");
runtimeEnv.FIXTURE_RUNTIME_DIRS = JSON.stringify({ selected: selectedDir, lower: lowerDir, rejected: rejectedDir });
runtimeEnv.FIXTURE_FULL_DIAGNOSTICS = options.fullDiagnostics ? "1" : "0";
if (options.persisted) writeFileSync(join(opencodexHome, "codex-runtime.json"), JSON.stringify({
Expand Down Expand Up @@ -259,6 +267,32 @@ function runStatusProbe(options: {
}
runtime = { beforeDiagnostics, afterDiagnostics: calls(), diagnosticsCached, newerVersion,
selectionUnchanged: selectionBefore === readOptional(selectionPath), failures };
const externalRoot = process.env.FIXTURE_EXTERNAL_INSTALLED_ROOT;
if (externalRoot) {
const { execFileSync } = require("node:child_process");
const sentinel = join(externalRoot, "OpenAI", "Codex", "bin", "fixture-installed", "codex.exe");
const observeInstalled = env => {
const sentinelCalls = [];
const lowerBefore = calls().lower.length;
resolveCodexRuntime({
env,
execFileSync: (file, args, options) => {
// Record attempts too: a failed external launch still violates isolation.
const call = file === sentinel ? { args: [...args], completed: false } : null;
if (call) sentinelCalls.push(call);
const output = execFileSync(file, args, options);
if (call) call.completed = true;
return output;
},
});
return { sentinelCalls, lowerCalls: calls().lower.slice(lowerBefore) };
};
// Explicit deps make both observations cold without altering the ordinary cache oracle.
runtime.installedIsolation = {
isolated: observeInstalled({ ...process.env }),
inherited: observeInstalled({ ...process.env, LOCALAPPDATA: externalRoot }),
};
}
}
console.log(JSON.stringify({ lines: captured, commandCode, status, runtime, exitCode, errors, catalogUnchanged }));
})();
Expand All @@ -282,6 +316,7 @@ function runStatusProbe(options: {
OPENCODEX_CLAUDE_DESKTOP_CONFIG_DIR: join(opencodexHome, "desktop"),
FIXTURE_LADDER: JSON.stringify(options.ladder),
FIXTURE_SURFACE: options.surface ?? "connect",
FIXTURE_EXTERNAL_INSTALLED_ROOT: options.externalInstalledRoot ?? "",
...runtimeEnv,
},
});
Expand Down Expand Up @@ -423,6 +458,34 @@ describe("connected-client runtime probe scope", () => {
});
}, COLD_SPAWN_WARMUP_HOOK_BUDGET_MS);

test.skipIf(process.platform !== "win32")("isolates inherited Windows installs without disabling full discovery", () => {
const externalRoot = mkdtempSync(join(tmpdir(), "ocx-readiness-external-"));
const previousLocalAppData = process.env.LOCALAPPDATA;
try {
const installed = join(externalRoot, "OpenAI", "Codex", "bin", "fixture-installed");
mkdirSync(installed, { recursive: true });
// A real PE executable: Bun answers --version and is harmless as an unselected candidate.
copyFileSync(process.execPath, join(installed, "codex.exe"));
process.env.LOCALAPPDATA = externalRoot;
const probe = runStatusProbe({ connected: true, ladder: "observed", externalInstalledRoot: externalRoot });
expect(probe.status.readiness).toBe("ready");
expect(probe.runtime?.beforeDiagnostics.selected).toEqual([
"--version", "debug models --bundled", "debug models --bundled",
]);
expect(probe.runtime?.beforeDiagnostics.lower).toEqual([]);
expect(probe.runtime?.selectionUnchanged).toBe(true);
expect(probe.runtime?.installedIsolation).toEqual({
isolated: { sentinelCalls: [], lowerCalls: ["--version"] },
// Removing only the environment isolation must execute the external sentinel.
inherited: { sentinelCalls: [{ args: ["--version"], completed: true }], lowerCalls: ["--version"] },
});
} finally {
if (previousLocalAppData === undefined) delete process.env.LOCALAPPDATA;
else process.env.LOCALAPPDATA = previousLocalAppData;
removeTreeWithRetry(externalRoot);
}
}, SPAWN_BUDGET_MS);

test("observes only the selected runtime and leaves full diagnostics available", () => {
const probe = runStatusProbe({ connected: true, ladder: "observed", fullDiagnostics: true });

Expand Down
17 changes: 16 additions & 1 deletion tests/codex-integration/codex-reset-credit-auto-redeem.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ describe("reset-credit auto-redeemer runtime (#822)", () => {
test("two processes reserve one durable id before either consume settles", async () => {
const journalFile = join(dir, "j.json");
const moduleUrl = pathToFileURL(repoPath("src/codex/reset-credit-auto-redeem.ts")).href;
const aclUrl = pathToFileURL(repoPath("src/lib/windows-secret-acl.ts")).href;
const principalUrl = pathToFileURL(repoPath("src/lib/windows-user-principal.ts")).href;
const deadline = performance.now() + 25_000;
const markerPath = (name: string) => join(dir, name + ".json");
const publish = (name: string) => {
Expand All @@ -291,6 +293,16 @@ describe("reset-credit auto-redeemer runtime (#822)", () => {
import { existsSync, writeFileSync, renameSync } from "node:fs";
import { join } from "node:path";
import { createResetCreditAutoRedeemer } from ${JSON.stringify(moduleUrl)};
import { setIcaclsRunnerForTests } from ${JSON.stringify(aclUrl)};
import { setSyntheticWindowsPrincipalForTests } from ${JSON.stringify(principalUrl)};
// This case proves cross-process SQLite reservation and durable publication,
// not host ACL tools. Their 30s budget exceeds this fixture's 20s deadline.
// Keep real file/SQLite I/O; isolate only unrelated OS helper processes in
// these disposable children. Production hardening remains unchanged.
if (process.platform === "win32") {
setSyntheticWindowsPrincipalForTests("*S-1-5-21-1-2-3-1001");
setIcaclsRunnerForTests(() => ({ success: true, exitCode: 0, timedOut: false, stdout: "" }));
}
const home = ${JSON.stringify(dir)};
const worker = ${JSON.stringify(worker)};
const deadline = performance.now() + 20_000;
Expand Down Expand Up @@ -335,6 +347,7 @@ describe("reset-credit auto-redeemer runtime (#822)", () => {
while (true) {
if (performance.now() >= deadline) throw new Error("reservation contention deadline exceeded");
scheduledMs = null;
publish(worker + "-tick", {});
outcome = await redeemer.tick();
if (outcome.kind === "dispatched") break;
const contention = outcome.kind === "error" && (
Expand Down Expand Up @@ -384,7 +397,9 @@ describe("reset-credit auto-redeemer runtime (#822)", () => {
const children: ReturnType<typeof launch>[] = [];
const released = new Set<string>();
const diagnostics = () => children.map(({ worker, child, output }) =>
`${worker} pid=${child.pid} exit=${child.exitCode}\nstdout: ${output.stdout}\nstderr: ${output.stderr}`).join("\n");
`${worker} pid=${child.pid} exit=${child.exitCode} phases=${JSON.stringify(
Object.fromEntries(["ready", "tick", "consume", "result"].map(phase => [phase, existsSync(markerPath(worker + "-" + phase))])),
)}\nstdout: ${output.stdout}\nstderr: ${output.stderr}`).join("\n");
const waitUntil = async (label: string, ready: () => boolean) => {
while (true) {
for (const { worker, child } of children) {
Expand Down
Loading