163 lines
5.9 KiB
JavaScript
163 lines
5.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import {
|
|
codexDesktopAppName,
|
|
findInstalledCodexAppExecutable,
|
|
removeLegacyCodexVirtualAuthMarker,
|
|
writeCodexCompatibleAppModelCatalog
|
|
} from "../../packages/core/src/agents/codex/app-launch.ts";
|
|
|
|
test("ChatGPT model catalog write includes patch bridge capabilities", () => {
|
|
const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-codex-app-catalog-"));
|
|
try {
|
|
const config = {
|
|
Providers: [
|
|
{ name: "DeepSeek", type: "openai_chat_completions", models: ["deepseek-v4-flash"] }
|
|
],
|
|
Router: {
|
|
builtInRules: {
|
|
"claude-code": { enabled: true },
|
|
codex: { enabled: true }
|
|
},
|
|
fallback: { mode: "off", models: [], retryCount: 1 },
|
|
rules: []
|
|
}
|
|
};
|
|
const profile = {
|
|
agent: "codex",
|
|
enabled: true,
|
|
id: "codex-main",
|
|
model: "DeepSeek/deepseek-v4-flash",
|
|
name: "Codex Main",
|
|
providerId: "openai-codex",
|
|
scope: "ccr",
|
|
surface: "app"
|
|
};
|
|
|
|
const result = writeCodexCompatibleAppModelCatalog(configDir, profile, config);
|
|
assert.equal(result.changed, true);
|
|
assert.equal(path.basename(result.file), "ccr-codex-model-catalog.json");
|
|
assert.equal(
|
|
result.userDataDir,
|
|
path.join(configDir, "profiles", "codex-main", "codex", ".claude-code-router", "codex-app-user-data", "codex-main")
|
|
);
|
|
|
|
const catalog = JSON.parse(readFileSync(result.file, "utf8"));
|
|
const model = catalog.models.find((item) => item.slug === "DeepSeek/deepseek-v4-flash");
|
|
assert.ok(model);
|
|
assert.equal(model.apply_patch_tool_type, "freeform");
|
|
|
|
const second = writeCodexCompatibleAppModelCatalog(configDir, profile, config);
|
|
assert.equal(second.changed, false);
|
|
assert.equal(second.file, result.file);
|
|
} finally {
|
|
rmSync(configDir, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
test("ChatGPT desktop app path override discovers the renamed executable", () => {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-app-"));
|
|
const previous = process.env.CHATGPT_APP_PATH;
|
|
try {
|
|
let configuredPath;
|
|
let expectedExecutable;
|
|
if (process.platform === "darwin") {
|
|
configuredPath = path.join(root, "ChatGPT.app");
|
|
const macosDir = path.join(configuredPath, "Contents", "MacOS");
|
|
mkdirSync(macosDir, { recursive: true });
|
|
expectedExecutable = path.join(macosDir, "ChatGPT");
|
|
writeFileSync(expectedExecutable, "");
|
|
writeFileSync(
|
|
path.join(configuredPath, "Contents", "Info.plist"),
|
|
"<plist><dict><key>CFBundleExecutable</key><string>ChatGPT</string></dict></plist>"
|
|
);
|
|
} else {
|
|
expectedExecutable = path.join(root, process.platform === "win32" ? "ChatGPT.exe" : "chatgpt");
|
|
configuredPath = expectedExecutable;
|
|
writeFileSync(expectedExecutable, "");
|
|
}
|
|
|
|
process.env.CHATGPT_APP_PATH = configuredPath;
|
|
const result = findInstalledCodexAppExecutable();
|
|
assert.equal(codexDesktopAppName, "ChatGPT");
|
|
assert.equal(result.executable, expectedExecutable);
|
|
assert.equal(result.checked[0], configuredPath);
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.CHATGPT_APP_PATH;
|
|
} else {
|
|
process.env.CHATGPT_APP_PATH = previous;
|
|
}
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
test("ChatGPT profile appPath overrides process env discovery", () => {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-profile-app-"));
|
|
const previous = process.env.CHATGPT_APP_PATH;
|
|
try {
|
|
const envExecutable = path.join(root, "env", "ChatGPT");
|
|
mkdirSync(path.dirname(envExecutable), { recursive: true });
|
|
writeFileSync(envExecutable, "");
|
|
process.env.CHATGPT_APP_PATH = envExecutable;
|
|
|
|
const profileExecutable = path.join(root, "profile", "ChatGPT");
|
|
mkdirSync(path.dirname(profileExecutable), { recursive: true });
|
|
writeFileSync(profileExecutable, "");
|
|
|
|
withPlatform("linux", () => {
|
|
const result = findInstalledCodexAppExecutable(profileExecutable);
|
|
assert.equal(result.executable, profileExecutable);
|
|
assert.equal(result.checked[0], profileExecutable);
|
|
});
|
|
} finally {
|
|
if (previous === undefined) {
|
|
delete process.env.CHATGPT_APP_PATH;
|
|
} else {
|
|
process.env.CHATGPT_APP_PATH = previous;
|
|
}
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
function withPlatform(platform, callback) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(process, "platform");
|
|
Object.defineProperty(process, "platform", {
|
|
configurable: true,
|
|
value: platform
|
|
});
|
|
try {
|
|
return callback();
|
|
} finally {
|
|
Object.defineProperty(process, "platform", descriptor);
|
|
}
|
|
}
|
|
|
|
test("ChatGPT migration removes only the exact legacy CCR auth marker", () => {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), "ccr-chatgpt-auth-migration-"));
|
|
const authFile = path.join(root, "auth.json");
|
|
try {
|
|
writeFileSync(authFile, JSON.stringify({
|
|
auth_mode: "apikey",
|
|
OPENAI_API_KEY: "ccr-local-profile"
|
|
}));
|
|
assert.equal(removeLegacyCodexVirtualAuthMarker(root), true);
|
|
assert.equal(existsSync(authFile), false);
|
|
|
|
const realAuth = { auth_mode: "chatgpt", tokens: { access_token: "preserve-me" } };
|
|
writeFileSync(authFile, JSON.stringify(realAuth));
|
|
assert.equal(removeLegacyCodexVirtualAuthMarker(root), false);
|
|
assert.deepEqual(JSON.parse(readFileSync(authFile, "utf8")), realAuth);
|
|
|
|
const customApiKey = { auth_mode: "apikey", OPENAI_API_KEY: "user-key" };
|
|
writeFileSync(authFile, JSON.stringify(customApiKey));
|
|
assert.equal(removeLegacyCodexVirtualAuthMarker(root), false);
|
|
assert.deepEqual(JSON.parse(readFileSync(authFile, "utf8")), customApiKey);
|
|
} finally {
|
|
rmSync(root, { force: true, recursive: true });
|
|
}
|
|
});
|