124 lines
4.3 KiB
JavaScript
124 lines
4.3 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { spawn } from "node:child_process";
|
|
|
|
import { detectCodexDesktopIntegration } from "../scripts/codex-desktop-integration-probe.mjs";
|
|
|
|
const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
|
|
async function makeFakeCodexApp() {
|
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "boss-codex-app-"));
|
|
const appPath = path.join(tempDir, "Codex.app");
|
|
const contentsDir = path.join(appPath, "Contents");
|
|
const resourcesDir = path.join(contentsDir, "Resources");
|
|
await fs.mkdir(resourcesDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(contentsDir, "Info.plist"),
|
|
`<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>CFBundleIdentifier</key>
|
|
<string>com.openai.codex</string>
|
|
<key>CFBundleShortVersionString</key>
|
|
<string>26.429.30905</string>
|
|
<key>CFBundleVersion</key>
|
|
<string>2345</string>
|
|
<key>CFBundleURLTypes</key>
|
|
<array>
|
|
<dict>
|
|
<key>CFBundleURLName</key>
|
|
<string>Codex</string>
|
|
<key>CFBundleURLSchemes</key>
|
|
<array>
|
|
<string>codex</string>
|
|
</array>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
</plist>
|
|
`,
|
|
"utf8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(resourcesDir, "app.asar"),
|
|
"function copyThreadLink(id){return `codex://threads/${id}`} // no plugin host export",
|
|
"utf8",
|
|
);
|
|
return { tempDir, appPath };
|
|
}
|
|
|
|
test("detectCodexDesktopIntegration reports stable deeplink and bridge capabilities without patching app", async () => {
|
|
const { tempDir, appPath } = await makeFakeCodexApp();
|
|
try {
|
|
const result = await detectCodexDesktopIntegration({
|
|
appPath,
|
|
bridgeEventsUrl: "http://127.0.0.1:4318/api/v1/codex-desktop/events",
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.app.bundleIdentifier, "com.openai.codex");
|
|
assert.equal(result.app.shortVersion, "26.429.30905");
|
|
assert.deepEqual(result.app.urlSchemes, ["codex"]);
|
|
assert.deepEqual(result.capabilities.threadDeepLink, {
|
|
supported: true,
|
|
template: "codex://threads/{threadId}",
|
|
evidence: "CFBundleURLSchemes contains codex and app resources contain codex://threads/",
|
|
});
|
|
assert.equal(result.capabilities.desktopBridgeSse.supported, true);
|
|
assert.equal(result.capabilities.desktopBridgeSse.url, "http://127.0.0.1:4318/api/v1/codex-desktop/events");
|
|
assert.equal(result.capabilities.inAppSubscription.supported, false);
|
|
assert.equal(result.capabilities.packagePatch.supported, false);
|
|
} finally {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("codex desktop refresh bridge daemon exposes capabilities from the integration probe", async () => {
|
|
const { tempDir, appPath } = await makeFakeCodexApp();
|
|
const child = spawn(process.execPath, [path.join(repoRoot, "scripts/codex-desktop-refresh-bridge-daemon.mjs")], {
|
|
cwd: repoRoot,
|
|
env: {
|
|
...process.env,
|
|
BOSS_CODEX_DESKTOP_BRIDGE_HOST: "127.0.0.1",
|
|
BOSS_CODEX_DESKTOP_BRIDGE_PORT: "0",
|
|
BOSS_CODEX_DESKTOP_APP_PATH: appPath,
|
|
BOSS_CODEX_DESKTOP_REFRESH_DRY_RUN: "true",
|
|
},
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
try {
|
|
const ready = await new Promise((resolve, reject) => {
|
|
let buffer = "";
|
|
const timer = setTimeout(() => reject(new Error("daemon not ready")), 4000);
|
|
child.stdout.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk) => {
|
|
buffer += chunk;
|
|
const line = buffer.trim().split(/\r?\n/).at(-1);
|
|
if (line) {
|
|
try {
|
|
clearTimeout(timer);
|
|
resolve(JSON.parse(line));
|
|
} catch {
|
|
// wait
|
|
}
|
|
}
|
|
});
|
|
child.on("error", reject);
|
|
});
|
|
const response = await fetch(`http://${ready.host}:${ready.port}/api/v1/codex-desktop/capabilities`);
|
|
const result = await response.json();
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(result.ok, true);
|
|
assert.equal(result.capabilities.threadDeepLink.supported, true);
|
|
assert.equal(result.capabilities.packagePatch.supported, false);
|
|
} finally {
|
|
child.kill("SIGTERM");
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|