feat(ai-tools): install/update AI CLIs, remote catalog sync, cached favicons (#40 A2)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @file Tests for server/lib/ai-tools.js — catalog validation and override,
|
||||
* PATH detection over fake executables, and the enable/default state rules.
|
||||
* PATH detection over fake executables, the enable/default state rules, and
|
||||
* the A2 install/update fields with their three trust tiers.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -195,6 +196,8 @@ describe("enable / default", () => {
|
||||
resume: false,
|
||||
bypass: false,
|
||||
routerCapable: false,
|
||||
install: null,
|
||||
update: null,
|
||||
});
|
||||
assert.equal(v.tools.find((t) => t.id === "codex").routerCapable, true);
|
||||
});
|
||||
@@ -213,3 +216,113 @@ describe("built-in catalog", () => {
|
||||
aiTools.__setCatalogDirs([builtin, custom]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("install/update fields and trust tiers", () => {
|
||||
const tiers = path.join(root, "tiers");
|
||||
const tb = path.join(tiers, "builtin");
|
||||
const tr = path.join(tiers, "remote");
|
||||
const tc = path.join(tiers, "custom");
|
||||
const base = (id, extra = {}) => ({
|
||||
id,
|
||||
label: id,
|
||||
detect: [id],
|
||||
prompt: { mode: "argv" },
|
||||
homepageUrl: "https://docs.example.com/cli",
|
||||
...extra,
|
||||
});
|
||||
|
||||
before(() => {
|
||||
for (const d of [tb, tr, tc]) fs.mkdirSync(d, { recursive: true });
|
||||
write(tb, "claude.json", {
|
||||
id: "claude",
|
||||
label: "Claude",
|
||||
detect: ["claude"],
|
||||
launcher: "claude",
|
||||
update: { kind: "self", args: ["update"] },
|
||||
});
|
||||
write(
|
||||
tb,
|
||||
"npmok.json",
|
||||
base("npmok", {
|
||||
install: { kind: "npm", package: "@scope/np-m.ok" },
|
||||
update: { kind: "npm" },
|
||||
})
|
||||
);
|
||||
write(
|
||||
tb,
|
||||
"scriptok.json",
|
||||
base("scriptok", {
|
||||
install: { kind: "script", url: "https://example.com/install" },
|
||||
update: { kind: "script" },
|
||||
})
|
||||
);
|
||||
write(tb, "badpkg.json", base("badpkg", { install: { kind: "npm", package: "Bad Pkg;rm" } }));
|
||||
write(
|
||||
tb,
|
||||
"httpurl.json",
|
||||
base("httpurl", { install: { kind: "script", url: "http://example.com/i" } })
|
||||
);
|
||||
write(
|
||||
tb,
|
||||
"offhost.json",
|
||||
base("offhost", { install: { kind: "script", url: "https://evil.io/i" } })
|
||||
);
|
||||
write(tb, "npmnoinst.json", base("npmnoinst", { update: { kind: "npm" } }));
|
||||
write(tb, "selfbad.json", base("selfbad", { update: { kind: "self", args: "update" } }));
|
||||
write(tb, "shared.json", base("shared", { label: "Shared builtin" }));
|
||||
write(tr, "shared.json", base("shared", { label: "Shared remote" }));
|
||||
write(tr, "codex.json", {
|
||||
id: "codex",
|
||||
label: "Codex R",
|
||||
detect: ["codex"],
|
||||
launcher: "codex",
|
||||
});
|
||||
write(
|
||||
tr,
|
||||
"remoteonly.json",
|
||||
base("remoteonly", { install: { kind: "npm", package: "remote-only" } })
|
||||
);
|
||||
write(tc, "shared.json", base("shared", { label: "Shared custom" }));
|
||||
write(tc, "custinst.json", base("custinst", { install: { kind: "npm", package: "x" } }));
|
||||
write(tc, "custupd.json", base("custupd", { update: { kind: "self", args: ["up"] } }));
|
||||
aiTools.__setCatalogDirs([tb, tr, tc]);
|
||||
});
|
||||
after(() => aiTools.__setCatalogDirs([builtin, custom]));
|
||||
|
||||
it("accepts well-formed install/update and rejects the rest", () => {
|
||||
assert.deepEqual(aiTools.get("npmok").install, { kind: "npm", package: "@scope/np-m.ok" });
|
||||
assert.deepEqual(aiTools.get("npmok").update, { kind: "npm" });
|
||||
assert.deepEqual(aiTools.get("scriptok").install, {
|
||||
kind: "script",
|
||||
url: "https://example.com/install",
|
||||
});
|
||||
assert.deepEqual(aiTools.get("claude").update, { kind: "self", args: ["update"] });
|
||||
for (const id of ["badpkg", "httpurl", "offhost", "npmnoinst", "selfbad"]) {
|
||||
assert.equal(aiTools.has(id), false, id);
|
||||
}
|
||||
});
|
||||
|
||||
it("orders custom over remote over builtin and lets a remote file keep launcher", () => {
|
||||
assert.equal(aiTools.get("shared").label, "Shared custom");
|
||||
assert.equal(aiTools.get("codex").source, "remote");
|
||||
assert.equal(aiTools.get("codex").launcher, "codex");
|
||||
assert.equal(aiTools.get("remoteonly").install.package, "remote-only");
|
||||
});
|
||||
|
||||
it("refuses install/update in a custom file", () => {
|
||||
assert.equal(aiTools.has("custinst"), false);
|
||||
assert.equal(aiTools.has("custupd"), false);
|
||||
});
|
||||
|
||||
it("view() shows install/update so the confirm dialog can name the command", () => {
|
||||
const rows = aiTools.view().tools;
|
||||
assert.deepEqual(rows.find((t) => t.id === "npmok").install, {
|
||||
kind: "npm",
|
||||
package: "@scope/np-m.ok",
|
||||
});
|
||||
assert.deepEqual(rows.find((t) => t.id === "claude").update, {
|
||||
kind: "self",
|
||||
args: ["update"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user