import { describe, expect, it } from "@effect/vitest"; import { integrationFaviconSrc, integrationFaviconUrl, integrationInferredUrl, integrationLocalIconUrl, integrationPresetIconUrl,} from "./integration-favicon"; describe("IntegrationFavicon", () => { it("resolves favicons only through the integrations.sh logo proxy", () => { expect(integrationFaviconUrl("https://api.github.com/graphql", 20)).toBe( "https://integrations.sh/logo/github.com?sz=40", ); }); it("does not request favicons for local URLs", () => { expect(integrationFaviconUrl("http://localhost:3000/private", 20)).toBeNull(); expect(integrationFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull(); }); it("sends only the registrable domain to the logo proxy", () => { expect(integrationFaviconUrl("https://api.github.com/private", 20)).toBe( "https://integrations.sh/logo/github.com?sz=40", ); }); it("falls through to the placeholder when the proxy fails", () => { const url = "https://api.github.com/graphql"; const primary = integrationFaviconSrc({ url, size: 20 }); expect(primary).toBe("https://integrations.sh/logo/github.com?sz=40"); expect(integrationFaviconSrc({ url, size: 20, failedSrcs: [primary ?? ""] })).toBeNull(); }); it("uses the Executor favicon for the built-in executor integration", () => { expect(integrationLocalIconUrl("executor")).toBe("/favicon-32.png"); expect(integrationLocalIconUrl("openapi")).toBeNull(); }); it("resolves the Executor sidebar icon only when the integration id is threaded (cloud/self-host repro)", () => { // Reconstruct the props the multiplayer shell derives for the built-in // executor integration (EXECUTOR_INTEGRATION: kind "built-in", name "Executor", no // displayUrl). The sidebar's IntegrationList builds icon/url exactly this way. const slug = "executor"; const name = "Executor"; const icon = integrationPresetIconUrl({ id: slug, kind: "built-in", name, url: undefined }, []); const url = integrationInferredUrl({ id: slug, name }) ?? undefined; // The built-in integration matches no preset and has no inferable host, so the // integrationId branch is the only thing that can resolve its icon. expect(icon).toBeNull(); expect(url).toBeUndefined(); // Bug: cloud/self-host dropped integrationId, so the cascade fell through to null // and rendered the neutral BoxIcon placeholder. expect(integrationFaviconSrc({ icon, url, size: 16 })).toBeNull(); // Fix: threading integrationId resolves the bundled Executor favicon. expect(integrationFaviconSrc({ icon, integrationId: slug, url, size: 16 })).toBe( "/favicon-32.png", ); }); it("finds preset icons from an integration URL", () => { expect( integrationPresetIconUrl( { id: "google_sheets", kind: "googleDiscovery", name: "Google Sheets API", url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", }, [ { key: "google", label: "Google", add: () => null, edit: () => null, presets: [ { id: "google-sheets", name: "Google Sheets", summary: "Spreadsheets.", url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", icon: "https://example.com/sheets.svg", }, ], }, ], ), ).toBe("https://example.com/sheets.svg"); }); it("does not fuzzy-match preset icons from names or slugs", () => { // Name/slug token matching is gone: without an exact defaultSlug or URL // match, the preset matcher declines and the cascade resolves through the // integrations.sh favicon instead, which is derived from the integration's // own domain and therefore cannot render the wrong brand. const presets = [ { key: "mcp", label: "MCP", add: () => null, edit: () => null, presets: [ { id: "sentry", name: "Sentry", summary: "Errors.", icon: "https://example.com/sentry.png", }, ], }, ]; expect( integrationPresetIconUrl({ id: "sentry", kind: "mcp", name: "Sentry MCP" }, presets), ).toBeNull(); // Migrated host-shaped integrations resolve through the inferred URL: const inferred = integrationInferredUrl({ id: "mcp_posthog_com", name: "mcp.posthog.com" }); expect(inferred).toBe("https://mcp.posthog.com"); expect(integrationFaviconSrc({ url: inferred ?? undefined, size: 16 })).toBe( "https://integrations.sh/logo/posthog.com?sz=32", ); }); it("prefers exact catalog defaultSlug icons for OpenAPI provider services", () => { expect( integrationPresetIconUrl({ id: "google_gmail", kind: "openapi", name: "Gmail" }, [ { key: "openapi", label: "OpenAPI", add: () => null, edit: () => null, presets: [ { id: "google-gmail", name: "Gmail", summary: "Messages.", defaultSlug: "google_gmail", icon: "https://example.com/gmail.png", }, ], }, ]), ).toBe("https://example.com/gmail.png"); }); it("matches presets on exact URL equality, not names", () => { const presets = [ { key: "openapi", label: "OpenAPI", add: () => null, edit: () => null, presets: [ { id: "spotify", name: "Spotify", summary: "Music.", url: "https://api.spotify.com/v1", icon: "https://example.com/spotify.png", }, ], }, ]; expect( integrationPresetIconUrl( { id: "spotify_web_api", kind: "openapi", name: "Spotify Web API", url: "https://api.spotify.com/v1", }, presets, ), ).toBe("https://example.com/spotify.png"); // Same name, different URL: no match — the URL-derived favicon takes over. expect( integrationPresetIconUrl( { id: "spotify_web_api", kind: "openapi", name: "Spotify Web API", url: "https://api.spotify.com/v2", }, presets, ), ).toBeNull(); }); it("does not match a different brand sharing a word fragment (ClickHouse Cloud vs Cloudflare)", () => { expect( integrationPresetIconUrl({ id: "clickhouse", kind: "mcp", name: "ClickHouse Cloud" }, [ { key: "mcp", label: "MCP", add: () => null, edit: () => null, presets: [ { id: "cloudflare", name: "Cloudflare", summary: "Workers, KV, D1, R2, and DNS management via MCP.", icon: "https://integrations.sh/logo/cloudflare.com", }, ], }, ]), ).toBeNull(); }); it("infers favicon URLs from migrated host-shaped MCP names and slugs", () => { expect(integrationInferredUrl({ id: "mcp_posthog_com", name: "mcp.posthog.com" })).toBe( "https://mcp.posthog.com", ); expect(integrationInferredUrl({ id: "ai_todoist_net", name: "ai.todoist.net" })).toBe( "https://ai.todoist.net", ); expect(integrationInferredUrl({ id: "mcp_pscale_dev", name: "mcp.pscale.dev" })).toBe( "https://mcp.pscale.dev", ); expect(integrationInferredUrl({ id: "stripe_api", name: "Stripe API" })).toBeNull(); });});
import { describe, expect, it } from "@effect/vitest";
import {
integrationFaviconSrc,
integrationFaviconUrl,
integrationInferredUrl,
integrationLocalIconUrl,
integrationPresetIconUrl,
} from "./integration-favicon";
describe("IntegrationFavicon", () => {
it("resolves favicons only through the integrations.sh logo proxy", () => {
expect(integrationFaviconUrl("https://api.github.com/graphql", 20)).toBe(
"https://integrations.sh/logo/github.com?sz=40",
);
});
it("does not request favicons for local URLs", () => {
expect(integrationFaviconUrl("http://localhost:3000/private", 20)).toBeNull();
expect(integrationFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull();
});
it("sends only the registrable domain to the logo proxy", () => {
expect(integrationFaviconUrl("https://api.github.com/private", 20)).toBe(
"https://integrations.sh/logo/github.com?sz=40",
);
});
it("falls through to the placeholder when the proxy fails", () => {
const url = "https://api.github.com/graphql";
const primary = integrationFaviconSrc({ url, size: 20 });
expect(primary).toBe("https://integrations.sh/logo/github.com?sz=40");
expect(integrationFaviconSrc({ url, size: 20, failedSrcs: [primary ?? ""] })).toBeNull();
});
it("uses the Executor favicon for the built-in executor integration", () => {
expect(integrationLocalIconUrl("executor")).toBe("/favicon-32.png");
expect(integrationLocalIconUrl("openapi")).toBeNull();
});
it("resolves the Executor sidebar icon only when the integration id is threaded (cloud/self-host repro)", () => {
// Reconstruct the props the multiplayer shell derives for the built-in
// executor integration (EXECUTOR_INTEGRATION: kind "built-in", name "Executor", no
// displayUrl). The sidebar's IntegrationList builds icon/url exactly this way.
const slug = "executor";
const name = "Executor";
const icon = integrationPresetIconUrl({ id: slug, kind: "built-in", name, url: undefined }, []);
const url = integrationInferredUrl({ id: slug, name }) ?? undefined;
// The built-in integration matches no preset and has no inferable host, so the
// integrationId branch is the only thing that can resolve its icon.
expect(icon).toBeNull();
expect(url).toBeUndefined();
// Bug: cloud/self-host dropped integrationId, so the cascade fell through to null
// and rendered the neutral BoxIcon placeholder.
expect(integrationFaviconSrc({ icon, url, size: 16 })).toBeNull();
// Fix: threading integrationId resolves the bundled Executor favicon.
expect(integrationFaviconSrc({ icon, integrationId: slug, url, size: 16 })).toBe(
"/favicon-32.png",
);
});
it("finds preset icons from an integration URL", () => {
expect(
integrationPresetIconUrl(
{
id: "google_sheets",
kind: "googleDiscovery",
name: "Google Sheets API",
url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest",
},
[
{
key: "google",
label: "Google",
add: () => null,
edit: () => null,
presets: [
{
id: "google-sheets",
name: "Google Sheets",
summary: "Spreadsheets.",
url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest",
icon: "https://example.com/sheets.svg",
},
],
},
],
),
).toBe("https://example.com/sheets.svg");
});
it("does not fuzzy-match preset icons from names or slugs", () => {
// Name/slug token matching is gone: without an exact defaultSlug or URL
// match, the preset matcher declines and the cascade resolves through the
// integrations.sh favicon instead, which is derived from the integration's
// own domain and therefore cannot render the wrong brand.
const presets = [
{
key: "mcp",
label: "MCP",
add: () => null,
edit: () => null,
presets: [
{
id: "sentry",
name: "Sentry",
summary: "Errors.",
icon: "https://example.com/sentry.png",
},
],
},
];
expect(
integrationPresetIconUrl({ id: "sentry", kind: "mcp", name: "Sentry MCP" }, presets),
).toBeNull();
// Migrated host-shaped integrations resolve through the inferred URL:
const inferred = integrationInferredUrl({ id: "mcp_posthog_com", name: "mcp.posthog.com" });
expect(inferred).toBe("https://mcp.posthog.com");
expect(integrationFaviconSrc({ url: inferred ?? undefined, size: 16 })).toBe(
"https://integrations.sh/logo/posthog.com?sz=32",
);
});
it("prefers exact catalog defaultSlug icons for OpenAPI provider services", () => {
expect(
integrationPresetIconUrl({ id: "google_gmail", kind: "openapi", name: "Gmail" }, [
{
key: "openapi",
label: "OpenAPI",
add: () => null,
edit: () => null,
presets: [
{
id: "google-gmail",
name: "Gmail",
summary: "Messages.",
defaultSlug: "google_gmail",
icon: "https://example.com/gmail.png",
},
],
},
]),
).toBe("https://example.com/gmail.png");
});
it("matches presets on exact URL equality, not names", () => {
const presets = [
{
key: "openapi",
label: "OpenAPI",
add: () => null,
edit: () => null,
presets: [
{
id: "spotify",
name: "Spotify",
summary: "Music.",
url: "https://api.spotify.com/v1",
icon: "https://example.com/spotify.png",
},
],
},
];
expect(
integrationPresetIconUrl(
{
id: "spotify_web_api",
kind: "openapi",
name: "Spotify Web API",
url: "https://api.spotify.com/v1",
},
presets,
),
).toBe("https://example.com/spotify.png");
// Same name, different URL: no match — the URL-derived favicon takes over.
expect(
integrationPresetIconUrl(
{
id: "spotify_web_api",
kind: "openapi",
name: "Spotify Web API",
url: "https://api.spotify.com/v2",
},
presets,
),
).toBeNull();
});
it("does not match a different brand sharing a word fragment (ClickHouse Cloud vs Cloudflare)", () => {
expect(
integrationPresetIconUrl({ id: "clickhouse", kind: "mcp", name: "ClickHouse Cloud" }, [
{
key: "mcp",
label: "MCP",
add: () => null,
edit: () => null,
presets: [
{
id: "cloudflare",
name: "Cloudflare",
summary: "Workers, KV, D1, R2, and DNS management via MCP.",
icon: "https://integrations.sh/logo/cloudflare.com",
},
],
},
]),
).toBeNull();
});
it("infers favicon URLs from migrated host-shaped MCP names and slugs", () => {
expect(integrationInferredUrl({ id: "mcp_posthog_com", name: "mcp.posthog.com" })).toBe(
"https://mcp.posthog.com",
);
expect(integrationInferredUrl({ id: "ai_todoist_net", name: "ai.todoist.net" })).toBe(
"https://ai.todoist.net",
);
expect(integrationInferredUrl({ id: "mcp_pscale_dev", name: "mcp.pscale.dev" })).toBe(
"https://mcp.pscale.dev",
);
expect(integrationInferredUrl({ id: "stripe_api", name: "Stripe API" })).toBeNull();
});
});