11import { describe , expect , it } from "@effect/vitest" ;
22
33import {
4- integrationFaviconFallbackUrl ,
54 integrationFaviconSrc ,
65 integrationFaviconUrl ,
76 integrationInferredUrl ,
@@ -10,22 +9,15 @@ import {
109} from "./integration-favicon" ;
1110
1211describe ( "IntegrationFavicon" , ( ) => {
13- it ( "uses the integrations.sh logo proxy as the primary favicon source " , ( ) => {
12+ it ( "resolves favicons only through the integrations.sh logo proxy" , ( ) => {
1413 expect ( integrationFaviconUrl ( "https://api.github.com/graphql" , 20 ) ) . toBe (
1514 "https://integrations.sh/logo/github.com?sz=40" ,
1615 ) ;
1716 } ) ;
1817
19- it ( "falls back to the Google favicon service" , ( ) => {
20- expect ( integrationFaviconFallbackUrl ( "https://api.github.com/graphql" , 20 ) ) . toBe (
21- "https://www.google.com/s2/favicons?domain=github.com&sz=40" ,
22- ) ;
23- } ) ;
24-
2518 it ( "does not request favicons for local URLs" , ( ) => {
2619 expect ( integrationFaviconUrl ( "http://localhost:3000/private" , 20 ) ) . toBeNull ( ) ;
2720 expect ( integrationFaviconUrl ( "http://127.0.0.1:3000/private" , 20 ) ) . toBeNull ( ) ;
28- expect ( integrationFaviconFallbackUrl ( "http://localhost:3000/private" , 20 ) ) . toBeNull ( ) ;
2921 } ) ;
3022
3123 it ( "sends only the registrable domain to the logo proxy" , ( ) => {
@@ -34,13 +26,11 @@ describe("IntegrationFavicon", () => {
3426 ) ;
3527 } ) ;
3628
37- it ( "walks the cascade from the proxy to the favicon service on failure " , ( ) => {
29+ it ( "falls through to the placeholder when the proxy fails " , ( ) => {
3830 const url = "https://api.github.com/graphql" ;
3931 const primary = integrationFaviconSrc ( { url, size : 20 } ) ;
4032 expect ( primary ) . toBe ( "https://integrations.sh/logo/github.com?sz=40" ) ;
41- expect ( integrationFaviconSrc ( { url, size : 20 , failedSrcs : [ primary ?? "" ] } ) ) . toBe (
42- "https://www.google.com/s2/favicons?domain=github.com&sz=40" ,
43- ) ;
33+ expect ( integrationFaviconSrc ( { url, size : 20 , failedSrcs : [ primary ?? "" ] } ) ) . toBeNull ( ) ;
4434 } ) ;
4535
4636 it ( "uses the Executor favicon for the built-in executor integration" , ( ) => {
@@ -102,63 +92,11 @@ describe("IntegrationFavicon", () => {
10292 ) . toBe ( "https://example.com/sheets.svg" ) ;
10393 } ) ;
10494
105- it ( "finds preset icons from display names with suffixes" , ( ) => {
106- expect (
107- integrationPresetIconUrl (
108- {
109- id : "google_search_console_api" ,
110- kind : "googleDiscovery" ,
111- name : "Google Search Console API" ,
112- } ,
113- [
114- {
115- key : "google" ,
116- label : "Google" ,
117- add : ( ) => null ,
118- edit : ( ) => null ,
119- presets : [
120- {
121- id : "google-search-console" ,
122- name : "Google Search Console" ,
123- summary : "Search performance." ,
124- icon : "https://example.com/google.svg" ,
125- } ,
126- ] ,
127- } ,
128- ] ,
129- ) ,
130- ) . toBe ( "https://example.com/google.svg" ) ;
131- } ) ;
132-
133- it ( "finds preset icons from an integration id when the URL is missing" , ( ) => {
134- expect (
135- integrationPresetIconUrl (
136- {
137- id : "sentry" ,
138- kind : "mcp" ,
139- name : "Sentry MCP" ,
140- } ,
141- [
142- {
143- key : "mcp" ,
144- label : "MCP" ,
145- add : ( ) => null ,
146- edit : ( ) => null ,
147- presets : [
148- {
149- id : "sentry" ,
150- name : "Sentry" ,
151- summary : "Errors." ,
152- icon : "https://example.com/sentry.png" ,
153- } ,
154- ] ,
155- } ,
156- ] ,
157- ) ,
158- ) . toBe ( "https://example.com/sentry.png" ) ;
159- } ) ;
160-
161- it ( "matches migrated MCP slugs with host/suffix noise" , ( ) => {
95+ it ( "does not fuzzy-match preset icons from names or slugs" , ( ) => {
96+ // Name/slug token matching is gone: without an exact defaultSlug or URL
97+ // match, the preset matcher declines and the cascade resolves through the
98+ // integrations.sh favicon instead, which is derived from the integration's
99+ // own domain and therefore cannot render the wrong brand.
162100 const presets = [
163101 {
164102 key : "mcp" ,
@@ -167,63 +105,25 @@ describe("IntegrationFavicon", () => {
167105 edit : ( ) => null ,
168106 presets : [
169107 {
170- id : "posthog" ,
171- name : "PostHog" ,
172- summary : "Analytics." ,
173- icon : "https://example.com/posthog.png" ,
174- } ,
175- {
176- id : "linear" ,
177- name : "Linear" ,
178- summary : "Issues." ,
179- icon : "https://example.com/linear.png" ,
180- } ,
181- {
182- id : "planetscale" ,
183- name : "PlanetScale" ,
184- summary : "Databases." ,
185- icon : "https://example.com/pscale.png" ,
108+ id : "sentry" ,
109+ name : "Sentry" ,
110+ summary : "Errors." ,
111+ icon : "https://example.com/sentry.png" ,
186112 } ,
187113 ] ,
188114 } ,
189115 ] ;
190116
191117 expect (
192- integrationPresetIconUrl (
193- { id : "mcp_posthog_com" , kind : "mcp" , name : "mcp.posthog.com" } ,
194- presets ,
195- ) ,
196- ) . toBe ( "https://example.com/posthog.png" ) ;
197- expect (
198- integrationPresetIconUrl (
199- { id : "mcp_linear_app" , kind : "mcp" , name : "mcp.linear.app" } ,
200- presets ,
201- ) ,
202- ) . toBe ( "https://example.com/linear.png" ) ;
203- expect (
204- integrationPresetIconUrl ( { id : "pscale_mcp" , kind : "mcp" , name : "Pscale MCP" } , presets ) ,
205- ) . toBe ( "https://example.com/pscale.png" ) ;
206- } ) ;
207-
208- it ( "matches migrated OpenAPI slugs with API/REST suffixes" , ( ) => {
209- expect (
210- integrationPresetIconUrl ( { id : "stripe_api" , kind : "openapi" , name : "Stripe API" } , [
211- {
212- key : "openapi" ,
213- label : "OpenAPI" ,
214- add : ( ) => null ,
215- edit : ( ) => null ,
216- presets : [
217- {
218- id : "stripe" ,
219- name : "Stripe" ,
220- summary : "Payments." ,
221- icon : "https://example.com/stripe.png" ,
222- } ,
223- ] ,
224- } ,
225- ] ) ,
226- ) . toBe ( "https://example.com/stripe.png" ) ;
118+ integrationPresetIconUrl ( { id : "sentry" , kind : "mcp" , name : "Sentry MCP" } , presets ) ,
119+ ) . toBeNull ( ) ;
120+
121+ // Migrated host-shaped integrations resolve through the inferred URL:
122+ const inferred = integrationInferredUrl ( { id : "mcp_posthog_com" , name : "mcp.posthog.com" } ) ;
123+ expect ( inferred ) . toBe ( "https://mcp.posthog.com" ) ;
124+ expect ( integrationFaviconSrc ( { url : inferred ?? undefined , size : 16 } ) ) . toBe (
125+ "https://integrations.sh/logo/posthog.com?sz=32" ,
126+ ) ;
227127 } ) ;
228128
229129 it ( "prefers exact catalog defaultSlug icons for OpenAPI provider services" , ( ) => {
@@ -248,7 +148,25 @@ describe("IntegrationFavicon", () => {
248148 ) . toBe ( "https://example.com/gmail.png" ) ;
249149 } ) ;
250150
251- it ( "does not split generic words into brand matches" , ( ) => {
151+ it ( "matches presets on exact URL equality, not names" , ( ) => {
152+ const presets = [
153+ {
154+ key : "openapi" ,
155+ label : "OpenAPI" ,
156+ add : ( ) => null ,
157+ edit : ( ) => null ,
158+ presets : [
159+ {
160+ id : "spotify" ,
161+ name : "Spotify" ,
162+ summary : "Music." ,
163+ url : "https://api.spotify.com/v1" ,
164+ icon : "https://example.com/spotify.png" ,
165+ } ,
166+ ] ,
167+ } ,
168+ ] ;
169+
252170 expect (
253171 integrationPresetIconUrl (
254172 {
@@ -257,30 +175,43 @@ describe("IntegrationFavicon", () => {
257175 name : "Spotify Web API" ,
258176 url : "https://api.spotify.com/v1" ,
259177 } ,
260- [
261- {
262- key : "openapi" ,
263- label : "OpenAPI" ,
264- add : ( ) => null ,
265- edit : ( ) => null ,
266- presets : [
267- {
268- id : "exa-websets" ,
269- name : "Exa Websets" ,
270- summary : "Web data." ,
271- icon : "https://example.com/exa.png" ,
272- } ,
273- {
274- id : "spotify" ,
275- name : "Spotify" ,
276- summary : "Music." ,
277- icon : "https://example.com/spotify.png" ,
278- } ,
279- ] ,
280- } ,
281- ] ,
178+ presets ,
282179 ) ,
283180 ) . toBe ( "https://example.com/spotify.png" ) ;
181+
182+ // Same name, different URL: no match — the URL-derived favicon takes over.
183+ expect (
184+ integrationPresetIconUrl (
185+ {
186+ id : "spotify_web_api" ,
187+ kind : "openapi" ,
188+ name : "Spotify Web API" ,
189+ url : "https://api.spotify.com/v2" ,
190+ } ,
191+ presets ,
192+ ) ,
193+ ) . toBeNull ( ) ;
194+ } ) ;
195+
196+ it ( "does not match a different brand sharing a word fragment (ClickHouse Cloud vs Cloudflare)" , ( ) => {
197+ expect (
198+ integrationPresetIconUrl ( { id : "clickhouse" , kind : "mcp" , name : "ClickHouse Cloud" } , [
199+ {
200+ key : "mcp" ,
201+ label : "MCP" ,
202+ add : ( ) => null ,
203+ edit : ( ) => null ,
204+ presets : [
205+ {
206+ id : "cloudflare" ,
207+ name : "Cloudflare" ,
208+ summary : "Workers, KV, D1, R2, and DNS management via MCP." ,
209+ icon : "https://integrations.sh/logo/cloudflare.com" ,
210+ } ,
211+ ] ,
212+ } ,
213+ ] ) ,
214+ ) . toBeNull ( ) ;
284215 } ) ;
285216
286217 it ( "infers favicon URLs from migrated host-shaped MCP names and slugs" , ( ) => {
0 commit comments