-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathworkspace-folder-change.integration.test.ts
More file actions
229 lines (199 loc) · 7.82 KB
/
workspace-folder-change.integration.test.ts
File metadata and controls
229 lines (199 loc) · 7.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/**
* Integration tests for workspace folder change handling.
*
* These run inside the Extension Development Host with the REAL VS Code API.
* They verify that the MCP server definition version changes when workspace
* folders are added or removed, which signals VS Code to restart the server
* with the updated environment rather than simply stopping it.
*
* Bug: Prior to the fix, fireDidChange() notified VS Code that the MCP server
* definitions changed, causing it to stop the running server. However, because
* the returned McpStdioServerDefinition had the same version as before, VS Code
* did not restart the server — it only stopped it.
*/
import * as assert from 'assert';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
const EXTENSION_ID = 'advanced-security.vscode-codeql-development-mcp-server';
suite('Workspace Folder Change Tests', () => {
let api: any;
suiteSetup(async () => {
const ext = vscode.extensions.getExtension(EXTENSION_ID);
assert.ok(ext, `Extension ${EXTENSION_ID} not found`);
api = ext.isActive ? ext.exports : await ext.activate();
});
test('MCP definition version should change after workspace folder is added', async function () {
const provider = api.mcpProvider;
if (!provider) {
this.skip();
return;
}
const token: vscode.CancellationToken = {
isCancellationRequested: false,
onCancellationRequested: () => ({ dispose: () => {} }),
};
// Get initial definitions
const beforeDefs = await provider.provideMcpServerDefinitions(token);
assert.ok(beforeDefs && beforeDefs.length >= 1, 'Should provide at least one definition');
const versionBefore = beforeDefs[0].version;
// Create a real temporary directory to add as a workspace folder
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-mcp-test-'));
// Listen for the onDidChangeMcpServerDefinitions event.
// Mocha's test timeout (60 s) handles the failure case.
const changePromise = new Promise<void>((resolve) => {
const disposable = provider.onDidChangeMcpServerDefinitions(() => {
disposable.dispose();
resolve();
});
});
// Add the temporary folder to the workspace
const folders = vscode.workspace.workspaceFolders ?? [];
const added = vscode.workspace.updateWorkspaceFolders(
folders.length,
0,
{ uri: vscode.Uri.file(tempDir) },
);
if (!added) {
// Clean up and skip if updateWorkspaceFolders is not supported in this profile
fs.rmdirSync(tempDir);
this.skip();
return;
}
try {
// Wait for the MCP definition change event
await changePromise;
// Get definitions after the workspace folder change
const afterDefs = await provider.provideMcpServerDefinitions(token);
assert.ok(afterDefs && afterDefs.length >= 1, 'Should still provide definitions after folder change');
const versionAfter = afterDefs[0].version;
// The version MUST be different so VS Code knows to restart the server
assert.notStrictEqual(
versionAfter,
versionBefore,
'MCP server definition version must change after workspace folder update ' +
'to signal VS Code to restart the server instead of only stopping it',
);
} finally {
// Cleanup: remove the added folder and temp directory
const updatedFolders = vscode.workspace.workspaceFolders ?? [];
const idx = updatedFolders.findIndex((f) => f.uri.fsPath === tempDir);
if (idx >= 0) {
vscode.workspace.updateWorkspaceFolders(idx, 1);
}
try {
fs.rmdirSync(tempDir);
} catch {
// Best-effort cleanup
}
}
});
test('MCP definition version should change after workspace folder is removed', async function () {
const provider = api.mcpProvider;
const folders = vscode.workspace.workspaceFolders;
if (!provider || !folders || folders.length < 2) {
// Need at least 2 folders to remove one without closing the workspace
this.skip();
return;
}
const token: vscode.CancellationToken = {
isCancellationRequested: false,
onCancellationRequested: () => ({ dispose: () => {} }),
};
// Get initial definitions
const beforeDefs = await provider.provideMcpServerDefinitions(token);
assert.ok(beforeDefs && beforeDefs.length >= 1, 'Should provide at least one definition');
const versionBefore = beforeDefs[0].version;
// Remember the last folder so we can re-add it after removal
const lastFolder = folders[folders.length - 1];
const removedUri = lastFolder.uri;
// Listen for the onDidChangeMcpServerDefinitions event.
// Mocha's test timeout (60 s) handles the failure case.
const changePromise = new Promise<void>((resolve) => {
const disposable = provider.onDidChangeMcpServerDefinitions(() => {
disposable.dispose();
resolve();
});
});
// Remove the last workspace folder
const removed = vscode.workspace.updateWorkspaceFolders(folders.length - 1, 1);
if (!removed) {
this.skip();
return;
}
try {
// Wait for the MCP definition change event
await changePromise;
// Get definitions after the workspace folder change
const afterDefs = await provider.provideMcpServerDefinitions(token);
assert.ok(afterDefs && afterDefs.length >= 1, 'Should still provide definitions after folder removal');
const versionAfter = afterDefs[0].version;
// The version MUST be different so VS Code knows to restart the server
assert.notStrictEqual(
versionAfter,
versionBefore,
'MCP server definition version must change after workspace folder removal ' +
'to signal VS Code to restart the server instead of only stopping it',
);
} finally {
// Cleanup: re-add the removed folder
const currentFolders = vscode.workspace.workspaceFolders ?? [];
vscode.workspace.updateWorkspaceFolders(
currentFolders.length,
0,
{ uri: removedUri },
);
}
});
test('Environment should reflect updated workspace folders after change', async function () {
const envBuilder = api.environmentBuilder;
const folders = vscode.workspace.workspaceFolders;
if (!envBuilder || !folders || folders.length === 0) {
this.skip();
return;
}
// Create a real temporary directory
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-mcp-env-test-'));
// Listen for workspace folder changes through the VS Code API
const changePromise = new Promise<void>((resolve) => {
const disposable = vscode.workspace.onDidChangeWorkspaceFolders(() => {
disposable.dispose();
resolve();
});
});
// Add the temporary folder
const added = vscode.workspace.updateWorkspaceFolders(
folders.length,
0,
{ uri: vscode.Uri.file(tempDir) },
);
if (!added) {
fs.rmdirSync(tempDir);
this.skip();
return;
}
try {
await changePromise;
// invalidate() was called by the event handler, so build() returns fresh env
const env = await envBuilder.build();
assert.ok(env.CODEQL_MCP_WORKSPACE_FOLDERS, 'CODEQL_MCP_WORKSPACE_FOLDERS should be set');
assert.ok(
env.CODEQL_MCP_WORKSPACE_FOLDERS.includes(tempDir),
`CODEQL_MCP_WORKSPACE_FOLDERS should include the newly added folder: ${env.CODEQL_MCP_WORKSPACE_FOLDERS}`,
);
} finally {
// Cleanup
const updatedFolders = vscode.workspace.workspaceFolders ?? [];
const idx = updatedFolders.findIndex((f) => f.uri.fsPath === tempDir);
if (idx >= 0) {
vscode.workspace.updateWorkspaceFolders(idx, 1);
}
try {
fs.rmdirSync(tempDir);
} catch {
// Best-effort cleanup
}
}
});
});