Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/testHelpers.ts
13406 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 { vi } from 'vitest';6import type { ICopilotCLISessionTracker } from '../copilotCLISessionTracker';78type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;910interface MockToolEntry {11handler: ToolHandler;12schema?: unknown;13}1415/**16* A mock MCP server that captures tool registrations for testing.17*/18export class MockMcpServer {19private readonly _tools = new Map<string, MockToolEntry>();2021/**22* Mimics the McpServer.tool() registration method.23* Handles both overloads: (name, desc, handler) and (name, desc, schema, handler).24*/25tool(name: string, _description: string, ...rest: unknown[]): void {26const handler = rest.length === 127? rest[0] as ToolHandler28: rest[1] as ToolHandler;29const schema = rest.length === 2 ? rest[0] : undefined;30this._tools.set(name, { handler, schema });31}3233/**34* Mimics the McpServer.registerTool() registration method.35* Signature: registerTool(name, config, callback)36*/37registerTool(name: string, config: { description?: string; inputSchema?: unknown }, handler: ToolHandler): void {38this._tools.set(name, { handler, schema: config.inputSchema });39}4041getToolHandler(name: string): ToolHandler | undefined {42return this._tools.get(name)?.handler;43}4445getToolSchema(name: string): unknown | undefined {46return this._tools.get(name)?.schema;47}4849hasToolRegistered(name: string): boolean {50return this._tools.has(name);51}52}5354/**55* Parses the text content from an MCP tool result.56* Returns the parsed JSON value from the first text content block.57*/58export function parseToolResult<T = unknown>(result: unknown): T {59const typed = result as { content: [{ type: string; text: string }] };60return JSON.parse(typed.content[0].text) as T;61}6263/**64* Creates a mock VS Code text editor for testing selection and text retrieval.65*/66export function createMockEditor(67filePath: string,68content: string,69startLine: number,70startChar: number,71endLine: number,72endChar: number,73) {74const lines = content.split('\n');75return {76document: {77uri: {78fsPath: filePath,79scheme: 'file',80toString: () => `file://${filePath}`,81},82getText: (range?: { start: { line: number; character: number }; end: { line: number; character: number } }) => {83if (!range) {84return content;85}86const resultLines: string[] = [];87for (let i = range.start.line; i <= range.end.line; i++) {88const line = lines[i] || '';89const start = i === range.start.line ? range.start.character : 0;90const end = i === range.end.line ? range.end.character : line.length;91resultLines.push(line.substring(start, end));92}93return resultLines.join('\n');94},95},96selection: {97start: { line: startLine, character: startChar },98end: { line: endLine, character: endChar },99isEmpty: startLine === endLine && startChar === endChar,100},101};102}103104/**105* Creates a mock VS Code URI for testing.106*/107export function createMockUri(path: string) {108return {109toString: () => `file://${path}`,110fsPath: path,111scheme: 'file',112};113}114115/**116* Creates a mock VS Code text editor with a specific URI scheme for testing.117*/118export function createMockEditorWithScheme(119filePath: string,120content: string,121startLine: number,122startChar: number,123endLine: number,124endChar: number,125scheme: string,126) {127const editor = createMockEditor(filePath, content, startLine, startChar, endLine, endChar);128return {129...editor,130document: {131...editor.document,132uri: {133...editor.document.uri,134scheme,135toString: () => `${scheme}://${filePath}`,136},137},138};139}140141/**142* Creates a mock VS Code Diagnostic for testing.143*/144export function createMockDiagnostic(145message: string,146severity: number,147startLine: number,148startChar: number,149endLine: number,150endChar: number,151source?: string,152code?: string | number,153) {154return {155message,156severity,157range: {158start: { line: startLine, character: startChar },159end: { line: endLine, character: endChar },160},161source,162code,163};164}165166/**167* A mock InProcHttpServer that tracks broadcast notifications.168*/169export class MockHttpServer {170readonly broadcastedNotifications: Array<{ method: string; params: Record<string, unknown> }> = [];171readonly sentNotifications: Array<{ sessionId: string; method: string; params: Record<string, unknown> }> = [];172private _connectedSessionIds: readonly string[] = [];173174readonly broadcastNotification = vi.fn((method: string, params: Record<string, unknown>) => {175this.broadcastedNotifications.push({ method, params });176});177178readonly sendNotification = vi.fn((sessionId: string, method: string, params: Record<string, unknown>) => {179this.sentNotifications.push({ sessionId, method, params });180});181182readonly getConnectedSessionIds = vi.fn((): readonly string[] => {183return this._connectedSessionIds;184});185186setConnectedSessionIds(ids: readonly string[]): void {187this._connectedSessionIds = ids;188}189190getNotifications(method: string) {191return this.broadcastedNotifications.filter(n => n.method === method);192}193194clear() {195this.broadcastedNotifications.length = 0;196this.sentNotifications.length = 0;197this.broadcastNotification.mockClear();198this.sendNotification.mockClear();199this.getConnectedSessionIds.mockClear();200}201}202203/**204* A mock session tracker for testing session picker logic.205*/206export class MockSessionTracker {207declare _serviceBrand: undefined;208private readonly _displayNames = new Map<string, string>();209210readonly registerSession = vi.fn().mockReturnValue({ dispose: () => { } });211readonly getTerminal = vi.fn().mockResolvedValue(undefined);212readonly setSessionTerminal = vi.fn();213public readonly setSessionName = vi.fn((sessionId: string, name: string) => {214this._displayNames.set(sessionId, name);215});216217getSessionDisplayName(sessionId: string): string {218return this._displayNames.get(sessionId) || sessionId;219}220221getSessionIds(): readonly string[] {222return Array.from(this._displayNames.keys());223}224225asTracker(): ICopilotCLISessionTracker {226return this as unknown as ICopilotCLISessionTracker;227}228}229230231