Files
AgentHub/server/__tests__/codex-discovery.test.js
nntrivi2001 b0788eea80 fix(codex): backslash parity in the TOML closing-quote scan
Round 2's escape check rejected a quote whenever a single backslash
preceded it, so an escaped backslash before a real closing quote (e.g.
path = "path\\" # c) was misread as an escaped quote and the field was
dropped. Now counts the run of backslashes and only treats an ODD run as
escaping the quote. Folded the old two-branch quote handling into one
scan (it already finds the terminator correctly with or without a
trailing comment) and decode \\ -> \ in the extracted value, since the
scan already knows which backslash run is real.
2026-09-08 16:57:02 +07:00

182 lines
7.1 KiB
JavaScript

/**
* @file Tests for codex-discovery.js: read-only discovery of Codex config
* surfaces (skills, agents, rules, memory, config) across the user (CODEX_HOME)
* and project (<cwd>/.codex) scopes.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const fs = require("node:fs");
const path = require("node:path");
const os = require("node:os");
const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
function write(file, content) {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, content);
}
describe("codex-discovery", () => {
let TMP;
let REPO;
let prevCodexHome;
let cx;
before(() => {
prevCodexHome = process.env.CODEX_HOME;
TMP = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-"));
REPO = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-repo-"));
process.env.CODEX_HOME = TMP;
delete require.cache[require.resolve("../lib/codex-home")];
delete require.cache[require.resolve("../lib/codex-discovery")];
cx = require("../lib/codex-discovery");
});
after(() => {
if (prevCodexHome === undefined) delete process.env.CODEX_HOME;
else process.env.CODEX_HOME = prevCodexHome;
fs.rmSync(TMP, { recursive: true, force: true });
fs.rmSync(REPO, { recursive: true, force: true });
});
it("reads a user skill and a project skill into one scoped list", () => {
write(
path.join(TMP, "skills", "release-guard", "SKILL.md"),
"---\nname: release-guard\n---\nbody"
);
write(
path.join(REPO, ".codex", "skills", "onboarding", "SKILL.md"),
"---\nname: onboarding\n---\nb"
);
const rows = cx.readSkills({ cwd: REPO });
assert.deepEqual(rows.map((r) => [r.scope, r.name]).sort(), [
["project", "onboarding"],
["user", "release-guard"],
]);
assert.equal(rows.find((r) => r.name === "release-guard").frontmatter.name, "release-guard");
});
it("pulls an agent's identity out of its TOML without a TOML parser", () => {
write(
path.join(REPO, ".codex", "agents", "reviewer.toml"),
'name = "reviewer"\ndescription = "Read-only review agent."\nmodel = "gpt-5.4"\n' +
'developer_instructions = """\nmulti\nline\n"""\n'
);
const [agent] = cx.readAgents({ cwd: REPO, scope: "project" });
assert.equal(agent.meta.name, "reviewer");
assert.equal(agent.meta.description, "Read-only review agent.");
assert.equal(agent.meta.model, "gpt-5.4");
});
it("does not mistake a key inside a triple-quoted block for a top-level field", () => {
const meta = cx.readTomlMeta(
'name = "implementer"\n' +
'sandbox_mode = "workspace-write"\n' +
'developer_instructions = """\n' +
'model = "not-a-real-field"\n' +
'"""\n' +
'model = "gpt-5.3-codex-spark"\n'
);
assert.equal(meta.name, "implementer");
assert.equal(meta.sandbox_mode, "workspace-write");
assert.equal(meta.model, "gpt-5.3-codex-spark");
});
it("keeps a field with a trailing inline comment, and keeps a '#' that is inside the string", () => {
const meta = cx.readTomlMeta(
'model = "gpt-5.4" # default\n' + 'description = "uses # for headings"\n'
);
assert.equal(meta.model, "gpt-5.4");
assert.equal(meta.description, "uses # for headings");
});
it("does not let an escaped quote before a real trailing comment truncate the value", () => {
const meta = cx.readTomlMeta('description = "say \\"hi\\"" # not a comment\n');
assert.equal(meta.description, 'say \\"hi\\"');
});
it("recognises a quote preceded by an escaped backslash as a real terminator", () => {
const meta = cx.readTomlMeta('path = "path\\\\" # c\n');
assert.equal(meta.path, "path\\");
});
it("keeps counting backslash pairs correctly with four of them before the terminator", () => {
const meta = cx.readTomlMeta('key = "a\\\\\\\\" # c\n');
assert.equal(meta.key, "a\\\\");
});
it("reads a rule file's preview", () => {
write(
path.join(TMP, "rules", "no-force-push.rules"),
"never force-push main\nask before deleting"
);
const [rule] = cx.readRules({ cwd: REPO, scope: "user" });
assert.equal(rule.name, "no-force-push");
assert.match(rule.preview, /never force-push main/);
});
it("reports a missing AGENTS.md as a row that does not exist, not a gap", () => {
const rows = cx.readMemory({ cwd: REPO });
const user = rows.find((r) => r.scope === "user");
assert.equal(user.exists, false, "the UI offers to create it, so the row must be present");
});
it("reads project AGENTS.md from the repo root, not under .codex", () => {
write(path.join(REPO, "AGENTS.md"), "# Project instructions");
const rows = cx.readMemory({ cwd: REPO });
const project = rows.find((r) => r.scope === "project");
assert.equal(project.exists, true);
assert.equal(project.file, path.join(REPO, "AGENTS.md"));
});
it("reads config.toml content for both scopes", () => {
write(path.join(TMP, "config.toml"), 'model = "gpt-5.3-codex-spark"\n');
const rows = cx.readConfig({ cwd: REPO });
const user = rows.find((r) => r.scope === "user");
assert.equal(user.exists, true);
assert.match(user.content, /gpt-5\.3-codex-spark/);
const project = rows.find((r) => r.scope === "project");
assert.equal(project.exists, false);
});
it("follows a skill directory symlinked in from outside CODEX_HOME — a supported install pattern", () => {
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-outside-"));
write(path.join(outside, "SKILL.md"), "---\nname: linked\n---\nbody");
const linkPath = path.join(TMP, "skills", "linked");
fs.symlinkSync(outside, linkPath, "dir");
const [linked] = cx.readSkills({ cwd: REPO, scope: "user" });
assert.equal(linked.scope, "user");
assert.equal(linked.name, "linked");
assert.equal(linked.frontmatter.name, "linked", "frontmatter must be read through the link");
assert.equal(linked.file, path.join(linkPath, "SKILL.md"));
fs.rmSync(outside, { recursive: true, force: true });
});
it("follows an agent .toml symlinked in from outside CODEX_HOME", () => {
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "codex-disc-outside-"));
const realFile = path.join(outside, "reviewer.toml");
write(realFile, 'name = "reviewer"\n');
const linkPath = path.join(TMP, "agents", "reviewer.toml");
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
fs.symlinkSync(realFile, linkPath, "file");
const [agent] = cx.readAgents({ cwd: REPO, scope: "user" });
assert.equal(agent.scope, "user");
assert.equal(agent.name, "reviewer");
assert.equal(agent.meta.name, "reviewer", "meta must be read through the link");
assert.equal(agent.file, linkPath);
fs.rmSync(outside, { recursive: true, force: true });
});
it("summarizes counts and roots in the overview", () => {
const overview = cx.readOverview({ cwd: REPO });
assert.equal(overview.roots.projectRoot, REPO);
assert.ok(overview.counts.skills.user >= 1);
assert.ok(overview.counts.agents.project >= 1);
});
});