Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/lib/vscode-node/test/getInlineCompletions.spec.ts
13399 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
// Load env
7
import * as dotenv from 'dotenv';
8
dotenv.config({ path: '../.env' });
9
10
import { readFile } from 'fs/promises';
11
import { join } from 'path';
12
import { assert, describe, expect, it } from 'vitest';
13
import type { AuthenticationGetSessionOptions, AuthenticationSession, ChatRequest, LanguageModelChat } from 'vscode';
14
import { ResultType } from '../../../extension/completions-core/vscode-node/lib/src/ghostText/resultType';
15
import { createTextDocument } from '../../../extension/completions-core/vscode-node/lib/src/test/textDocument';
16
import { TextDocumentIdentifier } from '../../../extension/completions-core/vscode-node/lib/src/textDocument';
17
import { TextDocumentChangeEvent, TextDocumentCloseEvent, TextDocumentFocusedEvent, TextDocumentOpenEvent, WorkspaceFoldersChangeEvent } from '../../../extension/completions-core/vscode-node/lib/src/textDocumentManager';
18
import { CopilotToken, createTestExtendedTokenInfo } from '../../../platform/authentication/common/copilotToken';
19
import { ChatEndpointFamily, EmbeddingsEndpointFamily } from '../../../platform/endpoint/common/endpointProvider';
20
import { MutableObservableWorkspace } from '../../../platform/inlineEdits/common/observableWorkspace';
21
import { FetchOptions, IAbortController, IHeaders, PaginationOptions, Response } from '../../../platform/networking/common/fetcherService';
22
import { IChatEndpoint, IEmbeddingsEndpoint, IFetcher } from '../../../platform/networking/common/networking';
23
import { Emitter, Event } from '../../../util/vs/base/common/event';
24
import { Disposable } from '../../../util/vs/base/common/lifecycle';
25
import { URI } from '../../../util/vs/base/common/uri';
26
import { createInlineCompletionsProvider, IActionItem, IAuthenticationService, ICompletionsStatusChangedEvent, ICompletionsTextDocumentManager, IEndpointProvider, ILogTarget, ITelemetrySender, LogLevel } from '../../node/chatLibMain';
27
28
class TestFetcher implements IFetcher {
29
private _fetched = new Map<string, number>();
30
31
constructor(private readonly responses: Record<string, string>) { }
32
33
getUserAgentLibrary(): string {
34
return 'TestFetcher'; // matches the naming convention inside of completions
35
}
36
37
async fetch(url: string, options: FetchOptions): Promise<Response> {
38
const uri = URI.parse(url);
39
this._markFetched(uri.path);
40
const responseText = this.responses[uri.path];
41
42
const headers = new class implements IHeaders {
43
get(name: string): string | null {
44
return null;
45
}
46
*[Symbol.iterator](): Iterator<[string, string]> {
47
// Empty headers for test
48
}
49
};
50
51
const found = typeof responseText === 'string';
52
const text = responseText || '';
53
return Response.fromText(
54
found ? 200 : 404,
55
found ? 'OK' : 'Not Found',
56
headers,
57
text,
58
'node-http'
59
);
60
}
61
62
private _markFetched(urlPath: string): void {
63
const count = this.fetchCount(urlPath);
64
this._fetched.set(urlPath, count + 1);
65
}
66
67
fetchCount(urlPath: string): number {
68
return this._fetched.get(urlPath) || 0;
69
}
70
71
fetchWithPagination<T>(baseUrl: string, options: PaginationOptions<T>): Promise<T[]> {
72
throw new Error('Method not implemented.');
73
}
74
75
async disconnectAll(): Promise<unknown> {
76
return Promise.resolve();
77
}
78
79
makeAbortController(): IAbortController {
80
return new AbortController();
81
}
82
83
isAbortError(e: any): boolean {
84
return e && e.name === 'AbortError';
85
}
86
87
isInternetDisconnectedError(e: any): boolean {
88
return false;
89
}
90
91
isFetcherError(e: any): boolean {
92
return false;
93
}
94
95
isNetworkProcessCrashedError(e: any): boolean {
96
return false;
97
}
98
99
getUserMessageForFetcherError(err: any): string {
100
return `Test fetcher error: ${err.message}`;
101
}
102
}
103
104
function createTestCopilotToken(): CopilotToken {
105
return new CopilotToken(createTestExtendedTokenInfo({
106
token: `test token ${Math.ceil(Math.random() * 100)}`,
107
}));
108
}
109
110
class TestAuthService extends Disposable implements IAuthenticationService {
111
readonly _serviceBrand: undefined;
112
readonly isMinimalMode = true;
113
readonly anyGitHubSession = undefined;
114
readonly permissiveGitHubSession = undefined;
115
readonly copilotToken = createTestCopilotToken();
116
speculativeDecodingEndpointToken: string | undefined;
117
118
private readonly _onDidAuthenticationChange = this._register(new Emitter<void>());
119
readonly onDidAuthenticationChange: Event<void> = this._onDidAuthenticationChange.event;
120
121
private readonly _onDidAccessTokenChange = this._register(new Emitter<void>());
122
readonly onDidAccessTokenChange = this._onDidAccessTokenChange.event;
123
124
private readonly _onDidAdoAuthenticationChange = this._register(new Emitter<void>());
125
readonly onDidAdoAuthenticationChange = this._onDidAdoAuthenticationChange.event;
126
127
async getGitHubSession(kind: 'permissive' | 'any', options?: AuthenticationGetSessionOptions): Promise<AuthenticationSession> {
128
throw new Error('Method not implemented.');
129
}
130
131
async getCopilotToken(force?: boolean): Promise<CopilotToken> {
132
return this.copilotToken;
133
}
134
135
resetCopilotToken(httpError?: number): void { }
136
137
async getAdoAccessTokenBase64(options?: AuthenticationGetSessionOptions): Promise<string | undefined> {
138
return undefined;
139
}
140
}
141
142
class TestTelemetrySender implements ITelemetrySender {
143
events: { eventName: string; properties?: Record<string, string | undefined>; measurements?: Record<string, number | undefined> }[] = [];
144
sendTelemetryEvent(eventName: string, properties?: Record<string, string | undefined>, measurements?: Record<string, number | undefined>): void {
145
this.events.push({ eventName, properties, measurements });
146
}
147
}
148
149
class TestEndpointProvider implements IEndpointProvider {
150
readonly _serviceBrand: undefined;
151
readonly onDidModelsRefresh = Event.None;
152
153
async getAllCompletionModels(forceRefresh?: boolean) {
154
return [];
155
}
156
157
async getAllChatEndpoints() {
158
return [];
159
}
160
161
async getChatEndpoint(requestOrFamily: LanguageModelChat | ChatRequest | ChatEndpointFamily): Promise<IChatEndpoint> {
162
throw new Error('Method not implemented.');
163
}
164
165
async getEmbeddingsEndpoint(family?: EmbeddingsEndpointFamily): Promise<IEmbeddingsEndpoint> {
166
throw new Error('Method not implemented.');
167
}
168
}
169
170
class TestDocumentManager extends Disposable implements ICompletionsTextDocumentManager {
171
private readonly _onDidChangeTextDocument = this._register(new Emitter<TextDocumentChangeEvent>());
172
readonly onDidChangeTextDocument = this._onDidChangeTextDocument.event;
173
174
private readonly _onDidOpenTextDocument = this._register(new Emitter<TextDocumentOpenEvent>());
175
readonly onDidOpenTextDocument = this._onDidOpenTextDocument.event;
176
177
private readonly _onDidCloseTextDocument = this._register(new Emitter<TextDocumentCloseEvent>());
178
readonly onDidCloseTextDocument = this._onDidCloseTextDocument.event;
179
180
private readonly _onDidFocusTextDocument = this._register(new Emitter<TextDocumentFocusedEvent>());
181
readonly onDidFocusTextDocument = this._onDidFocusTextDocument.event;
182
183
private readonly _onDidChangeWorkspaceFolders = this._register(new Emitter<WorkspaceFoldersChangeEvent>());
184
readonly onDidChangeWorkspaceFolders = this._onDidChangeWorkspaceFolders.event;
185
186
getTextDocumentsUnsafe() {
187
return [];
188
}
189
190
findNotebook(doc: TextDocumentIdentifier) {
191
return undefined;
192
}
193
194
getWorkspaceFolders() {
195
return [];
196
}
197
}
198
199
class NullLogTarget implements ILogTarget {
200
logIt(level: LogLevel, metadataStr: string, ...extra: any[]): void { }
201
}
202
203
describe('getInlineCompletions', () => {
204
const completionsPath = '/v1/engines/gpt-41-copilot/completions';
205
let fetcher: TestFetcher;
206
207
async function getCompletionsProvider() {
208
fetcher = new TestFetcher({ [completionsPath]: await readFile(join(__dirname, 'getInlineCompletions.reply.txt'), 'utf8') });
209
210
return createInlineCompletionsProvider({
211
fetcher,
212
authService: new TestAuthService(),
213
telemetrySender: new TestTelemetrySender(),
214
logTarget: new NullLogTarget(),
215
isRunningInTest: true,
216
contextProviderMatch: async () => 0,
217
statusHandler: new class { didChange(_: ICompletionsStatusChangedEvent) { } },
218
documentManager: new TestDocumentManager(),
219
workspace: new MutableObservableWorkspace(),
220
urlOpener: new class {
221
async open(_url: string) { }
222
},
223
editorInfo: { name: 'test-editor', version: '1.0.0' },
224
editorPluginInfo: { name: 'test-plugin', version: '1.0.0' },
225
relatedPluginInfo: [],
226
editorSession: {
227
sessionId: 'test-session-id',
228
machineId: 'test-machine-id',
229
},
230
notificationSender: new class {
231
async showWarningMessage(_message: string, ..._items: IActionItem[]) { return undefined; }
232
},
233
endpointProvider: new TestEndpointProvider(),
234
});
235
}
236
237
it('should return completions for a document and position', async () => {
238
const provider = await getCompletionsProvider();
239
const doc = createTextDocument('file:///test.txt', 'javascript', 1, 'function main() {\n\n}\n');
240
241
const result = await provider.getInlineCompletions(doc, { line: 1, character: 0 });
242
243
assert(result);
244
expect(result.length).toBe(1);
245
expect(result[0].resultType).toBe(ResultType.Async);
246
expect(result[0].displayText).toBe(' console.log("Hello, World!");');
247
});
248
249
it('makes any pending speculative requests when a completion is shown', async () => {
250
const provider = await getCompletionsProvider();
251
const doc = createTextDocument('file:///test.txt', 'javascript', 1, 'function main() {\n\n}\n');
252
253
const result = await provider.getInlineCompletions(doc, { line: 1, character: 0 });
254
255
assert(result);
256
expect(result.length).toBe(1);
257
258
await provider.inlineCompletionShown(result[0].clientCompletionId);
259
260
expect(fetcher.fetchCount(completionsPath)).toBe(2);
261
});
262
});
263
264