112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
/**
|
|
* @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);
|
|
});
|
|
});
|