Path: blob/main/src/vs/workbench/contrib/mcp/test/common/mcpIcons.test.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as assert from 'assert';6import { URI } from '../../../../../base/common/uri.js';7import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';8import { NullLogger } from '../../../../../platform/log/common/log.js';9import { McpIcons, parseAndValidateMcpIcon } from '../../common/mcpIcons.js';10import { McpServerTransportHTTP, McpServerTransportStdio, McpServerTransportType } from '../../common/mcpTypes.js';1112const createHttpLaunch = (url: string): McpServerTransportHTTP => ({13type: McpServerTransportType.HTTP,14uri: URI.parse(url),15headers: []16});1718const createStdioLaunch = (): McpServerTransportStdio => ({19type: McpServerTransportType.Stdio,20cwd: undefined,21command: 'cmd',22args: [],23env: {},24envFile: undefined25});2627suite('MCP Icons', () => {28suite('parseAndValidateMcpIcon', () => {29ensureNoDisposablesAreLeakedInTestSuite();3031test('includes supported icons and sorts sizes ascending', () => {32const logger = new NullLogger();33const launch = createHttpLaunch('https://example.com');3435const result = parseAndValidateMcpIcon({36icons: [37{ src: 'ftp://example.com/ignored.png', mimeType: 'image/png' },38{ src: 'data:image/png;base64,AAA', mimeType: 'image/png', sizes: ['64x64', '16x16'] },39{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['128x128'] }40]41}, launch, logger);4243assert.strictEqual(result.length, 2);44assert.strictEqual((result[0].src as URI).toString(true), 'data:image/png;base64,AAA');45assert.deepStrictEqual(result[0].sizes.map(s => s.width), [16, 64]);46assert.strictEqual(result[1].src.toString(), 'https://example.com/icon.png');47assert.deepStrictEqual(result[1].sizes, [{ width: 128, height: 128 }]);48});4950test('requires http transport with matching authority for remote icons', () => {51const logger = new NullLogger();52const httpLaunch = createHttpLaunch('https://example.com');53const stdioLaunch = createStdioLaunch();5455const icons = {56icons: [57{ src: 'https://example.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] },58{ src: 'https://other.com/icon.png', mimeType: 'image/png', sizes: ['64x64'] }59]60};6162const httpResult = parseAndValidateMcpIcon(icons, httpLaunch, logger);63assert.deepStrictEqual(httpResult.map(icon => icon.src.toString()), ['https://example.com/icon.png']);6465const stdioResult = parseAndValidateMcpIcon(icons, stdioLaunch, logger);66assert.strictEqual(stdioResult.length, 0);67});6869test('accepts file icons only for stdio transport', () => {70const logger = new NullLogger();71const stdioLaunch = createStdioLaunch();72const httpLaunch = createHttpLaunch('https://example.com');7374const icons = {75icons: [76{ src: 'file:///tmp/icon.png', mimeType: 'image/png', sizes: ['32x32'] }77]78};7980const stdioResult = parseAndValidateMcpIcon(icons, stdioLaunch, logger);81assert.strictEqual(stdioResult.length, 1);82assert.strictEqual(stdioResult[0].src.scheme, 'file');8384const httpResult = parseAndValidateMcpIcon(icons, httpLaunch, logger);85assert.strictEqual(httpResult.length, 0);86});87});8889suite('McpIcons', () => {90ensureNoDisposablesAreLeakedInTestSuite();9192test('getUrl returns undefined when no icons are available', () => {93const icons = McpIcons.fromParsed(undefined);94assert.strictEqual(icons.getUrl(16), undefined);95});9697test('getUrl prefers theme-specific icons and keeps light fallback', () => {98const logger = new NullLogger();99const launch = createHttpLaunch('https://example.com');100const parsed = parseAndValidateMcpIcon({101icons: [102{ src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: ['16x16', '48x48'], theme: 'dark' },103{ src: 'https://example.com/any.png', mimeType: 'image/png', sizes: ['24x24'] },104{ src: 'https://example.com/light.png', mimeType: 'image/png', sizes: ['64x64'], theme: 'light' }105]106}, launch, logger);107const icons = McpIcons.fromParsed(parsed);108const result = icons.getUrl(32);109110assert.ok(result);111assert.strictEqual(result!.dark.toString(), 'https://example.com/dark.png');112assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png');113});114115test('getUrl falls back to any-theme icons when no exact size exists', () => {116const logger = new NullLogger();117const launch = createHttpLaunch('https://example.com');118const parsed = parseAndValidateMcpIcon({119icons: [120{ src: 'https://example.com/dark.png', mimeType: 'image/png', sizes: ['16x16'], theme: 'dark' },121{ src: 'https://example.com/any.png', mimeType: 'image/png', sizes: ['64x64'] }122]123}, launch, logger);124const icons = McpIcons.fromParsed(parsed);125const result = icons.getUrl(60);126127assert.ok(result);128assert.strictEqual(result!.dark.toString(), 'https://example.com/any.png');129assert.strictEqual(result!.light, undefined);130});131132test('getUrl reuses light icons when dark theme assets are missing', () => {133const logger = new NullLogger();134const launch = createHttpLaunch('https://example.com');135const parsed = parseAndValidateMcpIcon({136icons: [137{ src: 'https://example.com/light.png', mimeType: 'image/png', sizes: ['32x32'], theme: 'light' }138]139}, launch, logger);140const icons = McpIcons.fromParsed(parsed);141const result = icons.getUrl(16);142143assert.ok(result);144assert.strictEqual(result!.dark.toString(), 'https://example.com/light.png');145assert.strictEqual(result!.light?.toString(), 'https://example.com/light.png');146});147});148});149150151