Open History to see the last commit
1import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
2
3import {
4 loadCachedAcpRegistryIcon,
5 officialAcpRegistryIconUrlForAgentId,
6 resolveOfficialAcpRegistryIconUrl,
7} from "./AcpRegistryIcon";
8
9describe("ACP Registry icon cache", () => {
10 const stored = new Map<string, Response>();
11 const match = vi.fn(async (url: string) => stored.get(url)?.clone());
12 const put = vi.fn(async (url: string, response: Response) => {
13 stored.set(url, response.clone());
14 });
15 const fetchIcon = vi.fn();
16
17 beforeEach(() => {
18 stored.clear();
19 match.mockClear();
20 put.mockClear();
21 fetchIcon.mockReset();
22 vi.stubGlobal("caches", { open: vi.fn(async () => ({ match, put })) });
23 vi.stubGlobal("fetch", fetchIcon);
24 });
25
26 it("single-flights the first fetch and serves later reads from persistent cache", async () => {
27 const url = "https://cdn.agentclientprotocol.com/registry/icons/cache-test.svg";
28 fetchIcon.mockResolvedValue(
29 new Response("<svg/>", {
30 status: 200,
31 headers: { "content-type": "image/svg+xml" },
32 }),
33 );
34
35 const [first, concurrent] = await Promise.all([
36 loadCachedAcpRegistryIcon(url),
37 loadCachedAcpRegistryIcon(url),
38 ]);
39 const cached = await loadCachedAcpRegistryIcon(url);
40
41 expect(first.type).toBe("image/svg+xml");
42 expect(concurrent.size).toBe(first.size);
43 expect(cached.size).toBe(first.size);
44 expect(fetchIcon).toHaveBeenCalledOnce();
45 expect(put).toHaveBeenCalledOnce();
46 });
47
48 it("rejects oversized image responses before caching", async () => {
49 const url = "https://cdn.agentclientprotocol.com/registry/icons/oversized.svg";
50 fetchIcon.mockResolvedValue(
51 new Response("too large", {
52 status: 200,
53 headers: {
54 "content-length": String(513 * 1_024),
55 "content-type": "image/svg+xml",
56 },
57 }),
58 );
59
60 await expect(loadCachedAcpRegistryIcon(url)).rejects.toThrow("too large");
61 expect(put).not.toHaveBeenCalled();
62 });
63
64 it("accepts only credential-free HTTPS URLs on the official CDN", () => {
65 expect(
66 resolveOfficialAcpRegistryIconUrl(
67 "https://cdn.agentclientprotocol.com/registry/icons/gemini.png",
68 ),
69 ).toBe("https://cdn.agentclientprotocol.com/registry/icons/gemini.png");
70 expect(
71 resolveOfficialAcpRegistryIconUrl("http://cdn.agentclientprotocol.com/icon.png"),
72 ).toBeNull();
73 expect(
74 resolveOfficialAcpRegistryIconUrl("https://cdn.agentclientprotocol.com.evil/icon.png"),
75 ).toBeNull();
76 expect(
77 resolveOfficialAcpRegistryIconUrl("https://user@cdn.agentclientprotocol.com/icon.png"),
78 ).toBeNull();
79 expect(
80 resolveOfficialAcpRegistryIconUrl("https://cdn.agentclientprotocol.com:8443/icon.png"),
81 ).toBeNull();
82 expect(officialAcpRegistryIconUrlForAgentId("kilo")).toBe(
83 "https://cdn.agentclientprotocol.com/registry/v1/latest/kilo.svg",
84 );
85 expect(officialAcpRegistryIconUrlForAgentId("../kilo")).toBeNull();
86 });
87});