feat(ai-tools): install/update AI CLIs, remote catalog sync, cached favicons (#40 A2)
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @file Tests for server/lib/ai-tool-icons.js — which domain a tool's icon is
|
||||
* fetched from, the raster-only magic-byte sniff (no SVG), the size cap, the
|
||||
* off-site redirect refusal, and the hit/miss cache.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { describe, it, before, beforeEach, after } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ai-icons-"));
|
||||
const cat = path.join(root, "cat");
|
||||
const empty = path.join(root, "empty");
|
||||
const data = path.join(root, "data");
|
||||
for (const d of [cat, empty, data]) fs.mkdirSync(d);
|
||||
const origData = process.env.DASHBOARD_DATA_DIR;
|
||||
process.env.DASHBOARD_DATA_DIR = data;
|
||||
|
||||
const aiTools = require("../lib/ai-tools");
|
||||
const { iconFor, iconDomain } = require("../lib/ai-tool-icons");
|
||||
|
||||
const tool = (id, extra) => ({ id, label: id, detect: [id], prompt: { mode: "argv" }, ...extra });
|
||||
const write = (doc) => fs.writeFileSync(path.join(cat, `${doc.id}.json`), JSON.stringify(doc));
|
||||
|
||||
let calls;
|
||||
const PNG = Buffer.from("89504e470d0a1a0a", "hex");
|
||||
function serve({ body = Buffer.concat([PNG, Buffer.alloc(2)]), url, status = 200 } = {}) {
|
||||
calls = [];
|
||||
globalThis.fetch = async (u) => {
|
||||
calls.push(u);
|
||||
return {
|
||||
ok: status === 200,
|
||||
status,
|
||||
url: url || u,
|
||||
headers: { get: () => "image/png" }, // ignored: the bytes decide
|
||||
arrayBuffer: async () => body,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
let origFetch;
|
||||
before(() => {
|
||||
origFetch = globalThis.fetch;
|
||||
write(tool("fav", { faviconDomain: "example.com" }));
|
||||
write(tool("home", { homepageUrl: "https://docs.example.org/cli" }));
|
||||
write(tool("ip", { faviconDomain: "127.0.0.1" }));
|
||||
write(tool("port", { homepageUrl: "https://example.com:8443/x" }));
|
||||
write(tool("none"));
|
||||
aiTools.__setCatalogDirs([cat, empty]);
|
||||
});
|
||||
beforeEach(() => fs.rmSync(path.join(data, "runtime"), { recursive: true, force: true }));
|
||||
after(() => {
|
||||
globalThis.fetch = origFetch;
|
||||
aiTools.__setCatalogDirs(null);
|
||||
if (origData === undefined) delete process.env.DASHBOARD_DATA_DIR;
|
||||
else process.env.DASHBOARD_DATA_DIR = origData;
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("iconDomain", () => {
|
||||
it("uses faviconDomain, else the homepage host, and refuses IPs, ports and nothing", () => {
|
||||
assert.equal(iconDomain(aiTools.get("fav")), "example.com");
|
||||
assert.equal(iconDomain(aiTools.get("home")), "docs.example.org");
|
||||
assert.equal(iconDomain(aiTools.get("ip")), null);
|
||||
assert.equal(iconDomain(aiTools.get("port")), null);
|
||||
assert.equal(iconDomain(aiTools.get("none")), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("iconFor", () => {
|
||||
it("fetches once and serves the cache after", async () => {
|
||||
serve();
|
||||
const a = await iconFor("fav");
|
||||
assert.equal(a.type, "image/png");
|
||||
assert.equal(a.body.length, 10);
|
||||
assert.deepEqual(calls, ["https://example.com/favicon.ico"]);
|
||||
await iconFor("fav");
|
||||
assert.equal(calls.length, 1);
|
||||
});
|
||||
|
||||
it("recognises an ico with no content type at all", async () => {
|
||||
serve({ body: Buffer.from("0000010001002020", "hex") });
|
||||
assert.equal((await iconFor("fav")).type, "image/x-icon");
|
||||
});
|
||||
|
||||
it("refuses SVG even when labelled image/png, and remembers the miss", async () => {
|
||||
serve({ body: Buffer.from('<svg xmlns="http://www.w3.org/2000/svg"><script/></svg>') });
|
||||
assert.equal(await iconFor("fav"), null);
|
||||
serve();
|
||||
assert.equal(await iconFor("fav"), null);
|
||||
assert.equal(calls.length, 0);
|
||||
});
|
||||
|
||||
it("refuses an oversized body and an off-site redirect", async () => {
|
||||
serve({ body: Buffer.concat([PNG, Buffer.alloc(256 * 1024)]) });
|
||||
assert.equal(await iconFor("fav"), null);
|
||||
fs.rmSync(path.join(data, "runtime"), { recursive: true, force: true });
|
||||
serve({ url: "https://evil.example.net/favicon.ico" });
|
||||
assert.equal(await iconFor("fav"), null);
|
||||
});
|
||||
|
||||
it("never fetches for a tool without an allowed domain", async () => {
|
||||
serve();
|
||||
assert.equal(await iconFor("ip"), null);
|
||||
assert.equal(await iconFor("nope"), null);
|
||||
assert.equal(calls.length, 0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user