/** * @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ĩ */ 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('') }); 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); }); });