Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/testHelpers.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { vi } from 'vitest';
7
import type { ICopilotCLISessionTracker } from '../copilotCLISessionTracker';
8
9
type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
10
11
interface MockToolEntry {
12
handler: ToolHandler;
13
schema?: unknown;
14
}
15
16
/**
17
* A mock MCP server that captures tool registrations for testing.
18
*/
19
export class MockMcpServer {
20
private readonly _tools = new Map<string, MockToolEntry>();
21
22
/**
23
* Mimics the McpServer.tool() registration method.
24
* Handles both overloads: (name, desc, handler) and (name, desc, schema, handler).
25
*/
26
tool(name: string, _description: string, ...rest: unknown[]): void {
27
const handler = rest.length === 1
28
? rest[0] as ToolHandler
29
: rest[1] as ToolHandler;
30
const schema = rest.length === 2 ? rest[0] : undefined;
31
this._tools.set(name, { handler, schema });
32
}
33
34
/**
35
* Mimics the McpServer.registerTool() registration method.
36
* Signature: registerTool(name, config, callback)
37
*/
38
registerTool(name: string, config: { description?: string; inputSchema?: unknown }, handler: ToolHandler): void {
39
this._tools.set(name, { handler, schema: config.inputSchema });
40
}
41
42
getToolHandler(name: string): ToolHandler | undefined {
43
return this._tools.get(name)?.handler;
44
}
45
46
getToolSchema(name: string): unknown | undefined {
47
return this._tools.get(name)?.schema;
48
}
49
50
hasToolRegistered(name: string): boolean {
51
return this._tools.has(name);
52
}
53
}
54
55
/**
56
* Parses the text content from an MCP tool result.
57
* Returns the parsed JSON value from the first text content block.
58
*/
59
export function parseToolResult<T = unknown>(result: unknown): T {
60
const typed = result as { content: [{ type: string; text: string }] };
61
return JSON.parse(typed.content[0].text) as T;
62
}
63
64
/**
65
* Creates a mock VS Code text editor for testing selection and text retrieval.
66
*/
67
export function createMockEditor(
68
filePath: string,
69
content: string,
70
startLine: number,
71
startChar: number,
72
endLine: number,
73
endChar: number,
74
) {
75
const lines = content.split('\n');
76
return {
77
document: {
78
uri: {
79
fsPath: filePath,
80
scheme: 'file',
81
toString: () => `file://${filePath}`,
82
},
83
getText: (range?: { start: { line: number; character: number }; end: { line: number; character: number } }) => {
84
if (!range) {
85
return content;
86
}
87
const resultLines: string[] = [];
88
for (let i = range.start.line; i <= range.end.line; i++) {
89
const line = lines[i] || '';
90
const start = i === range.start.line ? range.start.character : 0;
91
const end = i === range.end.line ? range.end.character : line.length;
92
resultLines.push(line.substring(start, end));
93
}
94
return resultLines.join('\n');
95
},
96
},
97
selection: {
98
start: { line: startLine, character: startChar },
99
end: { line: endLine, character: endChar },
100
isEmpty: startLine === endLine && startChar === endChar,
101
},
102
};
103
}
104
105
/**
106
* Creates a mock VS Code URI for testing.
107
*/
108
export function createMockUri(path: string) {
109
return {
110
toString: () => `file://${path}`,
111
fsPath: path,
112
scheme: 'file',
113
};
114
}
115
116
/**
117
* Creates a mock VS Code text editor with a specific URI scheme for testing.
118
*/
119
export function createMockEditorWithScheme(
120
filePath: string,
121
content: string,
122
startLine: number,
123
startChar: number,
124
endLine: number,
125
endChar: number,
126
scheme: string,
127
) {
128
const editor = createMockEditor(filePath, content, startLine, startChar, endLine, endChar);
129
return {
130
...editor,
131
document: {
132
...editor.document,
133
uri: {
134
...editor.document.uri,
135
scheme,
136
toString: () => `${scheme}://${filePath}`,
137
},
138
},
139
};
140
}
141
142
/**
143
* Creates a mock VS Code Diagnostic for testing.
144
*/
145
export function createMockDiagnostic(
146
message: string,
147
severity: number,
148
startLine: number,
149
startChar: number,
150
endLine: number,
151
endChar: number,
152
source?: string,
153
code?: string | number,
154
) {
155
return {
156
message,
157
severity,
158
range: {
159
start: { line: startLine, character: startChar },
160
end: { line: endLine, character: endChar },
161
},
162
source,
163
code,
164
};
165
}
166
167
/**
168
* A mock InProcHttpServer that tracks broadcast notifications.
169
*/
170
export class MockHttpServer {
171
readonly broadcastedNotifications: Array<{ method: string; params: Record<string, unknown> }> = [];
172
readonly sentNotifications: Array<{ sessionId: string; method: string; params: Record<string, unknown> }> = [];
173
private _connectedSessionIds: readonly string[] = [];
174
175
readonly broadcastNotification = vi.fn((method: string, params: Record<string, unknown>) => {
176
this.broadcastedNotifications.push({ method, params });
177
});
178
179
readonly sendNotification = vi.fn((sessionId: string, method: string, params: Record<string, unknown>) => {
180
this.sentNotifications.push({ sessionId, method, params });
181
});
182
183
readonly getConnectedSessionIds = vi.fn((): readonly string[] => {
184
return this._connectedSessionIds;
185
});
186
187
setConnectedSessionIds(ids: readonly string[]): void {
188
this._connectedSessionIds = ids;
189
}
190
191
getNotifications(method: string) {
192
return this.broadcastedNotifications.filter(n => n.method === method);
193
}
194
195
clear() {
196
this.broadcastedNotifications.length = 0;
197
this.sentNotifications.length = 0;
198
this.broadcastNotification.mockClear();
199
this.sendNotification.mockClear();
200
this.getConnectedSessionIds.mockClear();
201
}
202
}
203
204
/**
205
* A mock session tracker for testing session picker logic.
206
*/
207
export class MockSessionTracker {
208
declare _serviceBrand: undefined;
209
private readonly _displayNames = new Map<string, string>();
210
211
readonly registerSession = vi.fn().mockReturnValue({ dispose: () => { } });
212
readonly getTerminal = vi.fn().mockResolvedValue(undefined);
213
readonly setSessionTerminal = vi.fn();
214
public readonly setSessionName = vi.fn((sessionId: string, name: string) => {
215
this._displayNames.set(sessionId, name);
216
});
217
218
getSessionDisplayName(sessionId: string): string {
219
return this._displayNames.get(sessionId) || sessionId;
220
}
221
222
getSessionIds(): readonly string[] {
223
return Array.from(this._displayNames.keys());
224
}
225
226
asTracker(): ICopilotCLISessionTracker {
227
return this as unknown as ICopilotCLISessionTracker;
228
}
229
}
230
231